stk-code_catmod/src/robots/default_robot.cpp
mbjornstk fc46307f54 Fixing all remaining "executable" flags.
git-svn-id: svn+ssh://svn.code.sf.net/p/supertuxkart/code/trunk/supertuxkart@3205 178a84e3-b1eb-0310-8ba1-8eac791a3b58
2009-02-27 02:54:25 +00:00

1188 lines
42 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2004-2005 Steve Baker <sjbaker1@airmail.net>
// Copyright (C) 2006-2007 Eduardo Hernandez Munoz
// Copyright (C) 2008 Joerg Henrichs
//
// 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.
//The AI debugging works best with just 1 AI kart, so set the number of karts
//to 2 in main.cpp with quickstart and run supertuxkart with the arg -N.
#undef AI_DEBUG
#ifdef AI_DEBUG
#define SHOW_FUTURE_PATH //If defined, it will put a bunch of spheres when it
//checks for crashes with the outside of the track.
#define ERASE_PATH //If not defined, the spheres drawn in the future path
//won't be erased the next time the function is called.
#define SHOW_NON_CRASHING_POINT //If defined, draws a green sphere where the
//n farthest non-crashing point is.
#define _WINSOCKAPI_
#include <plib/ssgAux.h>
#endif
#include "default_robot.hpp"
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <iostream>
#include "race_manager.hpp"
#include "graphics/scene.hpp"
#include "modes/linear_world.hpp"
#include "network/network_manager.hpp"
#include "robots/track_info.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
const TrackInfo *DefaultRobot::m_track_info = NULL;
int DefaultRobot::m_num_of_track_info_instances = 0;
DefaultRobot::DefaultRobot(const std::string& kart_name,
int position, const btTransform& init_pos,
const Track *track ) :
AutoKart( kart_name, position, init_pos )
{
if(m_num_of_track_info_instances==0)
{
m_track_info = new TrackInfo(track);
m_num_of_track_info_instances++;
}
reset();
m_kart_length = m_kart_properties->getKartModel()->getLength();
m_kart_width = m_kart_properties->getKartModel()->getWidth();
m_track = RaceManager::getTrack();
m_world = dynamic_cast<LinearWorld*>(RaceManager::getWorld());
assert(m_world != NULL);
switch( race_manager->getDifficulty())
{
case RaceManager::RD_EASY:
m_wait_for_players = true;
m_max_handicap_accel = 0.9f;
m_fallback_tactic = FT_AVOID_TRACK_CRASH;
m_item_tactic = IT_TEN_SECONDS;
m_max_start_delay = 0.5f;
m_min_steps = 0;
m_skidding_threshold = 4.0f;
m_nitro_level = NITRO_NONE;
m_handle_bomb = false;
break;
case RaceManager::RD_MEDIUM:
m_wait_for_players = true;
m_max_handicap_accel = 0.95f;
// FT_PARALLEL had problems on some tracks when suddenly a smaller
// section occurred (e.g. bridge in stone track): the AI would drive
// over and over into the river
m_fallback_tactic = FT_FAREST_POINT;
m_item_tactic = IT_CALCULATE;
m_max_start_delay = 0.4f;
m_min_steps = 1;
m_skidding_threshold = 2.0f;
m_nitro_level = NITRO_SOME;
m_handle_bomb = true;
break;
case RaceManager::RD_HARD:
m_wait_for_players = false;
m_max_handicap_accel = 1.0f;
m_fallback_tactic = FT_FAREST_POINT;
m_item_tactic = IT_CALCULATE;
m_max_start_delay = 0.1f;
m_min_steps = 2;
m_skidding_threshold = 1.3f;
m_nitro_level = NITRO_ALL;
m_handle_bomb = true;
break;
}
} // DefaultRobot
//-----------------------------------------------------------------------------
/** The destructor deletes the shared TrackInfo objects if no more DefaultRobot
* instances are around.
*/
DefaultRobot::~DefaultRobot()
{
m_num_of_track_info_instances--;
if(m_num_of_track_info_instances==0)
{
delete m_track_info;
}
} // ~DefaultRobot
//-----------------------------------------------------------------------------
//TODO: if the AI is crashing constantly, make it move backwards in a straight
//line, then move forward while turning.
void DefaultRobot::update(float dt)
{
// This is used to enable firing an item backwards.
m_controls.m_look_back = false;
m_controls.m_nitro = false;
m_track_sector = m_world->m_kart_info[ getWorldKartId() ].m_track_sector;
// The client does not do any AI computations.
if(network_manager->getMode()==NetworkManager::NW_CLIENT)
{
AutoKart::update(dt);
return;
}
if( m_world->isStartPhase() )
{
handleRaceStart();
AutoKart::update(dt);
return;
}
/*Get information that is needed by more than 1 of the handling funcs*/
//Detect if we are going to crash with the track and/or kart
int steps = 0;
// This should not happen (anymore :) ), but it keeps the game running
// in case that m_future_sector becomes undefined.
if(m_future_sector == Track::UNKNOWN_SECTOR )
{
#ifdef DEBUG
fprintf(stderr,"DefaultRobot: m_future_sector is undefined.\n");
fprintf(stderr,"This shouldn't happen, but can be ignored.\n");
#endif
forceRescue();
m_future_sector = 0;
}
else
{
steps = calcSteps();
}
computeNearestKarts();
checkCrashes( steps, getXYZ() );
findCurve();
// Special behaviour if we have a bomb attach: try to hit the kart ahead
// of us.
bool commands_set = false;
if(m_handle_bomb && getAttachment()->getType()==ATTACH_BOMB &&
m_kart_ahead )
{
// Use nitro if the kart is far ahead, or faster than this kart
m_controls.m_nitro = m_distance_ahead>10.0f ||
m_kart_ahead->getSpeed() > getSpeed();
// If we are close enough, try to hit this kart
if(m_distance_ahead<=10)
{
Vec3 target = m_kart_ahead->getXYZ();
// If we are faster, try to predict the point where we will hit
// the other kart
if(m_kart_ahead->getSpeed() < getSpeed())
{
float time_till_hit = m_distance_ahead
/ (getSpeed()-m_kart_ahead->getSpeed());
target += m_kart_ahead->getVelocity()*time_till_hit;
}
float steer_angle = steerToPoint(m_kart_ahead->getXYZ().toFloat(),
dt);
setSteering(steer_angle, dt);
commands_set = true;
}
handleRescue(dt);
}
if(!commands_set)
{
/*Response handling functions*/
handleAcceleration(dt);
handleSteering(dt);
handleItems(dt, steps);
handleRescue(dt);
handleBraking();
// If a bomb is attached, nitro might already be set.
if(!m_controls.m_nitro)
handleNitroAndZipper();
}
// If we are supposed to use nitro, but have a zipper,
// use the zipper instead
if(m_controls.m_nitro && m_powerup.getType()==POWERUP_ZIPPER &&
getSpeed()>1.0f && m_zipper_time_left<=0)
{
// Make sure that not all AI karts use the zipper at the same
// time in time trial at start up, so during the first 5 seconds
// this is done at random only.
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_TIME_TRIAL ||
(m_world->getTime()<3.0f && rand()%50==1) )
{
m_controls.m_nitro = false;
m_controls.m_fire = true;
}
}
/*And obviously general kart stuff*/
AutoKart::update(dt);
m_collided = false;
} // update
//-----------------------------------------------------------------------------
void DefaultRobot::handleBraking()
{
// In follow the leader mode, the kart should brake if they are ahead of
// the leader (and not the leader, i.e. don't have initial position 1)
if(race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER &&
getPosition() < RaceManager::getKart(0)->getPosition() &&
getInitialPosition()>1 )
{
m_controls.m_brake = true;
return;
}
const float MIN_SPEED = m_track->getWidth()[m_track_sector];
KartInfo &kart_info = m_world->m_kart_info[ getWorldKartId() ];
//We may brake if we are about to get out of the road, but only if the
//kart is on top of the road, and if we won't slow down below a certain
//limit.
if ( m_crashes.m_road && kart_info.m_on_road && getVelocityLC().getY() > MIN_SPEED)
{
float kart_ang_diff = m_track->m_angle[m_track_sector] -
RAD_TO_DEGREE(getHPR().getHeading());
kart_ang_diff = normalizeAngle(kart_ang_diff);
kart_ang_diff = fabsf(kart_ang_diff);
const float MIN_TRACK_ANGLE = 20.0f;
const float CURVE_INSIDE_PERC = 0.25f;
//Brake only if the road does not goes somewhat straight.
if(m_curve_angle > MIN_TRACK_ANGLE) //Next curve is left
{
//Avoid braking if the kart is in the inside of the curve, but
//if the curve angle is bigger than what the kart can steer, brake
//even if we are in the inside, because the kart would be 'thrown'
//out of the curve.
if(!(m_world->getDistanceToCenterForKart(getWorldKartId()) > m_track->getWidth()[m_track_sector] *
-CURVE_INSIDE_PERC || m_curve_angle > RAD_TO_DEGREE(getMaxSteerAngle())))
{
m_controls.m_brake = false;
return;
}
}
else if( m_curve_angle < -MIN_TRACK_ANGLE ) //Next curve is right
{
if(!(m_world->getDistanceToCenterForKart( getWorldKartId() ) < m_track->getWidth()[m_track_sector] *
CURVE_INSIDE_PERC || m_curve_angle < -RAD_TO_DEGREE(getMaxSteerAngle())))
{
m_controls.m_brake = false;
return;
}
}
//Brake if the kart's speed is bigger than the speed we need
//to go through the curve at the widest angle, or if the kart
//is not going straight in relation to the road.
if(getVelocityLC().getY() > m_curve_target_speed ||
kart_ang_diff > MIN_TRACK_ANGLE )
{
#ifdef AI_DEBUG
std::cout << "BRAKING" << std::endl;
#endif
m_controls.m_brake = true;
return;
}
}
m_controls.m_brake = false;
} // handleBraking
//-----------------------------------------------------------------------------
void DefaultRobot::handleSteering(float dt)
{
const unsigned int DRIVELINE_SIZE = (unsigned int)m_track->m_driveline.size();
const size_t NEXT_SECTOR = (unsigned int)m_track_sector + 1 < DRIVELINE_SIZE
? m_track_sector + 1 : 0;
float steer_angle = 0.0f;
/*The AI responds based on the information we just gathered, using a
*finite state machine.
*/
//Reaction to being outside of the road
if( fabsf(m_world->getDistanceToCenterForKart( getWorldKartId() )) + 0.5f >
m_track->getWidth()[m_track_sector] )
{
steer_angle = steerToPoint( m_track->m_driveline[NEXT_SECTOR], dt );
#ifdef AI_DEBUG
std::cout << "- Outside of road: steer to center point." <<
std::endl;
#endif
}
//If we are going to crash against a kart, avoid it if it doesn't
//drives the kart out of the road
else if( m_crashes.m_kart != -1 && !m_crashes.m_road )
{
//-1 = left, 1 = right, 0 = no crash.
if( m_start_kart_crash_direction == 1 )
{
steer_angle = steerToAngle( NEXT_SECTOR, -M_PI*0.5f );
m_start_kart_crash_direction = 0;
}
else if(m_start_kart_crash_direction == -1)
{
steer_angle = steerToAngle( NEXT_SECTOR, M_PI*0.5f);
m_start_kart_crash_direction = 0;
}
else
{
if(m_world->getDistanceToCenterForKart( getWorldKartId() ) >
m_world->getDistanceToCenterForKart( m_crashes.m_kart ))
{
steer_angle = steerToAngle( NEXT_SECTOR, -M_PI*0.5f );
m_start_kart_crash_direction = 1;
}
else
{
steer_angle = steerToAngle( NEXT_SECTOR, M_PI*0.5f );
m_start_kart_crash_direction = -1;
}
}
#ifdef AI_DEBUG
std::cout << "- Velocity vector crashes with kart and doesn't " <<
"crashes with road : steer 90 degrees away from kart." <<
std::endl;
#endif
}
else
{
m_start_kart_crash_direction = 0;
switch( m_fallback_tactic )
{
case FT_FAREST_POINT:
{
sgVec2 straight_point;
findNonCrashingPoint( straight_point );
steer_angle = steerToPoint(straight_point, dt);
}
break;
case FT_PARALLEL:
steer_angle = steerToAngle( NEXT_SECTOR, 0.0f );
break;
case FT_AVOID_TRACK_CRASH:
if( m_crashes.m_road )
{
steer_angle = steerToAngle( m_track_sector, 0.0f );
}
else steer_angle = 0.0f;
break;
}
#ifdef AI_DEBUG
std::cout << "- Fallback." << std::endl;
#endif
}
// avoid steer vibrations
//if (fabsf(steer_angle) < 1.0f*3.1415/180.0f)
// steer_angle = 0.f;
setSteering(steer_angle, dt);
} // handleSteering
//-----------------------------------------------------------------------------
void DefaultRobot::handleItems( const float DELTA, const int STEPS )
{
m_controls.m_fire = false;
if(isRescue() || m_powerup.getType() == POWERUP_NOTHING ) return;
m_time_since_last_shot += DELTA;
// Tactic 1: wait ten seconds, then use item
// -----------------------------------------
if(m_item_tactic==IT_TEN_SECONDS)
{
if( m_time_since_last_shot > 10.0f )
{
m_controls.m_fire = true;
m_time_since_last_shot = 0.0f;
}
return;
}
// Tactic 2: calculate
// -------------------
switch( m_powerup.getType() )
{
case POWERUP_ZIPPER:
// Do nothing. Further up a zipper is used if nitro should be selected,
// saving the (potential more valuable nitro) for later
break;
case POWERUP_BUBBLEGUM:
// Either use the bubble gum after 10 seconds, or if the next kart
// behind is 'close' but not too close (too close likely means that the
// kart is not behind but more to the side of this kart and so won't
// be hit by the bubble gum anyway). Should we check the speed of the
// kart as well? I.e. only drop if the kart behind is faster? Otoh
// this approach helps preventing an overtaken kart to overtake us
// again.
m_controls.m_fire = (m_distance_behind < 15.0f &&
m_distance_behind > 3.0f ) ||
m_time_since_last_shot>10.0f;
if(m_distance_behind < 10.0f && m_distance_behind > 2.0f )
m_distance_behind *= 1.0f;
break;
// All the thrown/fired items might be improved by considering the angle
// towards m_kart_ahead. And some of them can fire backwards, too - which
// isn't yet supported for AI karts.
case POWERUP_CAKE:
m_controls.m_fire = (m_kart_ahead && m_distance_ahead < 20.0f) ||
m_time_since_last_shot > 10.0f;
break;
case POWERUP_BOWLING:
{
// Bowling balls slower, so only fire on closer karts - but when
// firing backwards, the kart can be further away, since the ball
// acts a bit like a mine (and the kart is racing towards it, too)
bool fire_backwards = (m_kart_behind && m_kart_ahead &&
m_distance_behind < m_distance_ahead) ||
!m_kart_ahead;
float distance = fire_backwards ? m_distance_behind
: m_distance_ahead;
m_controls.m_fire = (fire_backwards && distance < 30.0f) ||
(!fire_backwards && distance <10.0f) ||
m_time_since_last_shot > 10.0f;
if(m_controls.m_fire)
m_controls.m_look_back = fire_backwards;
break;
}
case POWERUP_PLUNGER:
{
// Plungers can be fired backwards and are faster,
// so allow more distance for shooting.
bool fire_backwards = (m_kart_behind && m_kart_ahead &&
m_distance_behind < m_distance_ahead) ||
!m_kart_ahead;
float distance = fire_backwards ? m_distance_behind
: m_distance_ahead;
m_controls.m_fire = distance < 30.0f ||
m_time_since_last_shot > 10.0f;
if(m_controls.m_fire)
m_controls.m_look_back = fire_backwards;
break;
}
case POWERUP_ANVIL:
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
{
m_controls.m_fire = m_world->getTime()<1.0f && getPosition()>2;
}
else
{
m_controls.m_fire = m_time_since_last_shot > 3.0f &&
getPosition()>1;
}
default:
m_controls.m_fire = true;
}
if(m_controls.m_fire) m_time_since_last_shot = 0.0f;
return;
} // handleItems
//-----------------------------------------------------------------------------
/** Determines the closest karts just behind and in front of this kart. The
* 'closeness' is for now simply based on the position, i.e. if a kart is
* more than one lap behind or ahead, it is not considered to be closest.
*/
void DefaultRobot::computeNearestKarts()
{
bool need_to_check = false;
int my_position = getPosition();
// See if the kart ahead has changed:
if( ( m_kart_ahead && m_kart_ahead->getPosition()+1!=my_position ) ||
(!m_kart_ahead && my_position>1 ) )
need_to_check = true;
// See if the kart behind has changed:
if( ( m_kart_behind && m_kart_behind->getPosition()-1!=my_position ) ||
(!m_kart_behind && my_position<(int)m_world->getCurrentNumKarts()) )
need_to_check = true;
if(!need_to_check) return;
m_kart_behind = m_kart_ahead = NULL;
m_distance_ahead = m_distance_behind = 9999999.9f;
float my_dist = m_world->getDistanceDownTrackForKart(getWorldKartId());
for(unsigned int i=0; i<race_manager->getNumKarts(); i++)
{
Kart *k = m_world->getKart(i);
if(k->isEliminated() || k==this) continue;
if(k->getPosition()==my_position+1)
{
m_kart_behind = k;
m_distance_behind = my_dist - m_world->getDistanceDownTrackForKart(i);
if(m_distance_behind<0.0f)
m_distance_behind += m_track->getTrackLength();
}
else
if(k->getPosition()==my_position-1)
{
m_kart_ahead = k;
m_distance_ahead = m_world->getDistanceDownTrackForKart(i) - my_dist;
if(m_distance_ahead<0.0f)
m_distance_ahead += m_track->getTrackLength();
}
} // for i<world->getNumKarts()
} // computeNearestKarts
//-----------------------------------------------------------------------------
void DefaultRobot::handleAcceleration( const float DELTA )
{
//Do not accelerate until we have delayed the start enough
if( m_time_till_start > 0.0f )
{
m_time_till_start -= DELTA;
m_controls.m_accel = 0.0f;
return;
}
if( m_controls.m_brake == true )
{
m_controls.m_accel = 0.0f;
return;
}
if(hasViewBlockedByPlunger())
{
if(!(getSpeed() > getMaxSpeedOnTerrain() / 2))
m_controls.m_accel = 0.05f;
else
m_controls.m_accel = 0.0f;
return;
}
if( m_wait_for_players )
{
//Find if any player is ahead of this kart
bool player_winning = false;
for(unsigned int i = 0; i < race_manager->getNumPlayers(); ++i )
if( m_race_position > RaceManager::getPlayerKart(i)->getPosition() )
{
player_winning = true;
break;
}
if( player_winning )
{
m_controls.m_accel = m_max_handicap_accel;
return;
}
}
m_controls.m_accel = 1.0f;
} // handleAcceleration
//-----------------------------------------------------------------------------
void DefaultRobot::handleRaceStart()
{
//FIXME: make karts able to get a penalty for accelerating too soon
//like players, should happen to about 20% of the karts in easy,
//5% in medium and less than 1% of the karts in hard.
if( m_time_till_start < 0.0f )
{
srand(( unsigned ) time( 0 ));
//Each kart starts at a different, random time, and the time is
//smaller depending on the difficulty.
m_time_till_start = ( float ) rand() / RAND_MAX * m_max_start_delay;
}
} // handleRaceStart
//-----------------------------------------------------------------------------
void DefaultRobot::handleRescue(const float DELTA)
{
// check if kart is stuck
if(getSpeed()<2.0f && !isRescue() && !m_world->isStartPhase())
{
m_time_since_stuck += DELTA;
if(m_time_since_stuck > 2.0f)
{
forceRescue();
m_time_since_stuck=0.0f;
} // m_time_since_stuck > 2.0f
}
else
{
m_time_since_stuck = 0.0f;
}
} // handleRescue
//-----------------------------------------------------------------------------
/** Decides wether to use nitro or not.
*/
void DefaultRobot::handleNitroAndZipper()
{
m_controls.m_nitro = false;
// If we are already very fast, save nitro.
if(getSpeed() > 0.95f*getMaxSpeedOnTerrain())
return;
// Don't use nitro when the AI has a plunger in the face!
if(hasViewBlockedByPlunger()) return;
// Don't use nitro if the kart doesn't have any or is not on ground.
if(!isOnGround() || hasFinishedRace()) return;
// Don't compute nitro usage if we don't have nitro or are not supposed
// to use it, and we don't have a zipper or are not supposed to use
// it (calculated).
if( (getEnergy()==0 || m_nitro_level==NITRO_NONE) &&
(m_powerup.getType()!=POWERUP_ZIPPER || m_item_tactic==IT_TEN_SECONDS) )
return;
// If a parachute or anvil is attached, the nitro doesn't give much
// benefit. Better wait till later.
const bool has_slowdown_attachment =
m_attachment.getType()==ATTACH_PARACHUTE ||
m_attachment.getType()==ATTACH_ANVIL;
if(has_slowdown_attachment) return;
// If the kart is very slow (e.g. after rescue), use nitro
if(getSpeed()<5)
{
m_controls.m_nitro = true;
return;
}
// If this kart is the last kart, and we have enough
// (i.e. more than 2) nitro, use it.
// -------------------------------------------------
const unsigned int num_karts = m_world->getCurrentNumKarts();
if(getPosition()== (int)num_karts && getEnergy()>2.0f)
{
m_controls.m_nitro = true;
return;
}
// On the last track shortly before the finishing line, use nitro
// anyway. Since the kart is faster with nitro, estimate a 50% time
// decrease (additionally some nitro will be saved when top speed
// is reached).
if(m_world->getLapForKart(getWorldKartId())==race_manager->getNumLaps()-1 &&
m_nitro_level == NITRO_ALL)
{
float finish = m_world->getEstimatedFinishTime(getWorldKartId());
if( 1.5f*getEnergy() >= finish - m_world->getTime() )
{
m_controls.m_nitro = true;
return;
}
}
// A kart within this distance is considered to be overtaking (or to be
// overtaken).
const float overtake_distance = 10.0f;
// Try to overtake a kart that is close ahead, except
// when we are already much faster than that kart
// --------------------------------------------------
if(m_kart_ahead &&
m_distance_ahead < overtake_distance &&
m_kart_ahead->getSpeed()+5.0f > getSpeed() )
{
m_controls.m_nitro = true;
return;
}
if(m_kart_behind &&
m_distance_behind < overtake_distance &&
m_kart_behind->getSpeed() > getSpeed() )
{
// Only prevent overtaking on highest level
m_controls.m_nitro = m_nitro_level==NITRO_ALL;
return;
}
} // handleNitroAndZipper
//-----------------------------------------------------------------------------
float DefaultRobot::steerToAngle(const size_t SECTOR, const float ANGLE)
{
float angle = m_track->m_angle[SECTOR];
//Desired angle minus current angle equals how many angles to turn
float steer_angle = angle - getHPR().getHeading();
if(hasViewBlockedByPlunger())
steer_angle += ANGLE/5;
else
steer_angle += ANGLE;
steer_angle = normalizeAngle( steer_angle );
return steer_angle;
} // steerToAngle
//-----------------------------------------------------------------------------
/** Computes the steering angle to reach a certain point. Note that the
* steering angle depends on the velocity of the kart (simple setting the
* steering angle towards the angle the point has is not correct: a slower
* kart will obviously turn less in one time step than a faster kart).
* \param point Point to steer towards.
* \param dt Time step.
*/
float DefaultRobot::steerToPoint(const sgVec2 point, float dt)
{
// No sense steering if we are not driving.
if(getSpeed()==0) return 0.0f;
const float dx = point[0] - getXYZ().getX();
const float dy = point[1] - getXYZ().getY();
/** Angle from the kart position to the point in world coordinates. */
float theta = -atan2(dx, dy);
// Angle is the point is relative to the heading - but take the current
// angular velocity into account, too. The value is multiplied by two
// to avoid 'oversteering' - experimentally found.
float angle_2_point = theta - getHPR().getHeading()
- dt*m_body->getAngularVelocity().getZ()*2.0f;
angle_2_point = normalizeAngle(angle_2_point);
if(fabsf(angle_2_point)<0.1) return 0.0f;
/** To understand this code, consider how a given steering angle determines
* the angle the kart is facing after one timestep:
* sin(steer_angle) = wheel_base / radius; --> compute radius of turn
* circumference = radius * 2 * M_PI; --> circumference of turn circle
* The kart drives dt*V units during a timestep of size dt. So the ratio
* of the driven distance to the circumference is the same as the angle
* the whole circle, or:
* angle / (2*M_PI) = dt*V / circumference
* Reversly, if the angle to drive to is given, the circumference can be
* computed, and from that the turn radius, and then the steer angle.
* (note: the 2*M_PI can be removed from the computations)
*/
float radius = dt*getSpeed()/angle_2_point;
float sin_steer_angle = m_kart_properties->getWheelBase()/radius;
#ifdef DEBUG_OUTPUT
printf("theta %f a2p %f angularv %f radius %f ssa %f\n",
theta, angle_2_point, m_body->getAngularVelocity().getZ(),
radius, sin_steer_angle);
#endif
// Add 0.1 since rouding errors will otherwise result in the kart
// not using drifting.
if(sin_steer_angle <= -1.0f) return -getMaxSteerAngle()*m_skidding_threshold-0.1f;
if(sin_steer_angle >= 1.0f) return getMaxSteerAngle()*m_skidding_threshold+0.1f;
float steer_angle = asin(sin_steer_angle);
return steer_angle;
} // steerToPoint
//-----------------------------------------------------------------------------
void DefaultRobot::checkCrashes( const int STEPS, const Vec3& pos )
{
//Right now there are 2 kind of 'crashes': with other karts and another
//with the track. The sight line is used to find if the karts crash with
//each other, but the first step is twice as big as other steps to avoid
//having karts too close in any direction. The crash with the track can
//tell when a kart is going to get out of the track so it steers.
Vec3 vel_normal;
//in this func we use it as a 2d-vector, but later on it is passed
//to m_track->findRoadSector, there it is used as a 3d-vector
//to find distance to plane, so z must be initialized to zero
Vec3 step_coord;
float kart_distance;
step_coord.setZ(0.0);
m_crashes.clear();
const size_t NUM_KARTS = race_manager->getNumKarts();
//Protection against having vel_normal with nan values
const Vec3 &VEL = getVelocity();
vel_normal.setValue(VEL.getX(), VEL.getY(), 0.0);
float len=vel_normal.length();
// If the velocity is zero, no sense in checking for crashes in time
if(len==0) return;
// Time it takes to drive for m_kart_length units.
float dt = m_kart_length / len;
vel_normal/=len;
for(int i = 1; STEPS > i; ++i)
{
step_coord = pos + vel_normal* m_kart_length * float(i);
/* Find if we crash with any kart, as long as we haven't found one
* yet
*/
if( m_crashes.m_kart == -1 )
{
for( unsigned int j = 0; j < NUM_KARTS; ++j )
{
const Kart* kart = RaceManager::getKart(j);
if(kart==this||kart->isEliminated()) continue; // ignore eliminated karts
const Kart *other_kart = RaceManager::getKart(j);
Vec3 other_kart_xyz = other_kart->getXYZ() + other_kart->getVelocity()*(i*dt);
kart_distance = (step_coord - other_kart_xyz).length_2d();
if( kart_distance < m_kart_length &&
getVelocityLC().getY() > other_kart->getVelocityLC().getY())
m_crashes.m_kart = j;
}
}
/*Find if we crash with the drivelines*/
m_track->findRoadSector(step_coord, &m_sector);
#undef SHOW_FUTURE_PATH
#ifdef SHOW_FUTURE_PATH
ssgaSphere *sphere = new ssgaSphere;
#ifdef ERASE_PATH
static ssgaSphere *last_sphere = 0;
if( last_sphere ) scene->remove( last_sphere );
last_sphere = sphere;
#endif
sgVec3 center;
center[0] = step_coord[0];
center[1] = step_coord[1];
center[2] = pos[2];
sphere->setCenter( center );
sphere->setSize( m_kart_properties->getKartModel()->getLength() );
if( m_sector == Track::UNKNOWN_SECTOR )
{
sgVec4 colour;
colour[0] = colour[3] = 255;
colour[1] = colour[2] = 0;
sphere->setColour(colour);
}
else if( i == 1 )
{
sgVec4 colour;
colour[0] = colour[1] = colour[2] = 0;
colour[3] = 255;
sphere->setColour( colour );
}
scene->add( sphere );
#endif
m_future_location[0] = step_coord[0];
m_future_location[1] = step_coord[1];
if( m_sector == Track::UNKNOWN_SECTOR )
{
m_future_sector = m_track->findOutOfRoadSector( step_coord,
Track::RS_DONT_KNOW, m_future_sector );
m_crashes.m_road = true;
break;
}
else
{
m_future_sector = m_sector;
}
}
} // checkCrashes
//-----------------------------------------------------------------------------
/** Find the sector that at the longest distance from the kart, that can be
* driven to without crashing with the track, then find towards which of
* the two edges of the track is closest to the next curve after wards,
* and return the position of that edge.
*/
void DefaultRobot::findNonCrashingPoint( sgVec2 result )
{
const unsigned int DRIVELINE_SIZE = (unsigned int)m_track->m_driveline.size();
unsigned int sector = (unsigned int)m_track_sector + 1 < DRIVELINE_SIZE
? m_track_sector + 1 : 0;
int target_sector;
Vec3 direction;
Vec3 step_track_coord;
float distance;
int steps;
//We exit from the function when we have found a solution
while( 1 )
{
//target_sector is the sector at the longest distance that we can drive
//to without crashing with the track.
target_sector = sector + 1 < DRIVELINE_SIZE ? sector + 1 : 0;
//direction is a vector from our kart to the sectors we are testing
direction = m_track->m_driveline[target_sector] - getXYZ();
float len=direction.length_2d();
steps = int( len / m_kart_length );
if( steps < 3 ) steps = 3;
//Protection against having vel_normal with nan values
if(len>0.0f) {
direction*= 1.0f/len;
}
Vec3 step_coord;
//Test if we crash if we drive towards the target sector
for( int i = 2; i < steps; ++i )
{
step_coord = getXYZ()+direction*m_kart_length * float(i);
m_track->spatialToTrack( step_track_coord, step_coord,
sector );
distance = step_track_coord[0] > 0.0f ? step_track_coord[0]
: -step_track_coord[0];
//If we are outside, the previous sector is what we are looking for
if ( distance + m_kart_width * 0.5f > m_track->getWidth()[sector] )
{
sgCopyVec2( result, m_track->m_driveline[sector] );
#ifdef SHOW_NON_CRASHING_POINT
ssgaSphere *sphere = new ssgaSphere;
static ssgaSphere *last_sphere = 0;
if(last_sphere) scene->remove( last_sphere );
last_sphere = sphere;
sgVec3 center;
center[0] = result[0];
center[1] = result[1];
center[2] = getXYZ().getZ();
sphere->setCenter( center );
sphere->setSize( 0.5f );
sgVec4 colour;
colour[1] = colour[3] = 255;
colour[0] = colour[2] = 0;
sphere->setColour( colour );
scene->add( sphere );
#endif
return;
}
}
sector = target_sector;
}
} // findNonCrashingPoint
//-----------------------------------------------------------------------------
void DefaultRobot::reset()
{
m_time_since_last_shot = 0.0f;
m_start_kart_crash_direction = 0;
m_sector = Track::UNKNOWN_SECTOR;
m_inner_curve = 0;
m_curve_target_speed = getMaxSpeedOnTerrain();
m_curve_angle = 0.0;
m_future_location[0] = 0.0;
m_future_location[1] = 0.0;
m_future_sector = 0;
m_time_till_start = -1.0f;
m_crash_time = 0.0f;
m_collided = false;
m_time_since_stuck = 0.0f;
m_kart_ahead = NULL;
m_distance_ahead = 0.0f;
m_kart_behind = NULL;
m_distance_behind = 0.0f;
AutoKart::reset();
} // reset
//-----------------------------------------------------------------------------
inline float DefaultRobot::normalizeAngle(float angle)
{
while( angle > 2*M_PI ) angle -= 2*M_PI;
while( angle < -2*M_PI ) angle += 2*M_PI;
if( angle > M_PI ) angle -= 2*M_PI;
else if( angle < -M_PI ) angle += 2*M_PI;
return angle;
}
//-----------------------------------------------------------------------------
/** calc_steps() divides the velocity vector by the lenght of the kart,
* and gets the number of steps to use for the sight line of the kart.
* The calling sequence guarantees that m_future_sector is not UNKNOWN.
*/
int DefaultRobot::calcSteps()
{
int steps = int( getVelocityLC().getY() / m_kart_length );
if( steps < m_min_steps ) steps = m_min_steps;
//Increase the steps depending on the width, if we steering hard,
//mostly for curves.
if( fabsf(m_controls.m_steer) > 0.95 )
{
const int WIDTH_STEPS =
(int)( m_track->getWidth()[m_future_sector]
/( m_kart_length * 2.0 ) );
steps += WIDTH_STEPS;
}
return steps;
} // calcSteps
//-----------------------------------------------------------------------------
/** Converts the steering angle to a lr steering in the range of -1 to 1.
* If the steering angle is too great, it will also trigger skidding. This
* function uses a 'time till full steer' value specifying the time it takes
* for the wheel to reach full left/right steering similar to player karts
* when using a digital input device. This is done to remove shaking of
* AI karts (which happens when the kart frequently changes the direction
* of a turn). The parameter is defined in the kart properties.
* \param angle Steering angle.
* \param dt Time step.
*/
void DefaultRobot::setSteering(float angle, float dt)
{
float steer_fraction = angle / getMaxSteerAngle();
m_controls.m_drift = fabsf(steer_fraction)>=m_skidding_threshold;
if(hasViewBlockedByPlunger()) m_controls.m_drift = false;
float old_steer = m_controls.m_steer;
if (steer_fraction > 1.0f) steer_fraction = 1.0f;
else if(steer_fraction < -1.0f) steer_fraction = -1.0f;
if(hasViewBlockedByPlunger())
{
if (steer_fraction > 0.5f) steer_fraction = 0.5f;
else if(steer_fraction < -0.5f) steer_fraction = -0.5f;
}
// The AI has its own 'time full steer' value (which is the time
float max_steer_change = dt/m_kart_properties->getTimeFullSteerAI();
if(old_steer < steer_fraction)
{
m_controls.m_steer = (old_steer+max_steer_change > steer_fraction)
? steer_fraction : old_steer+max_steer_change;
}
else
{
m_controls.m_steer = (old_steer-max_steer_change < steer_fraction)
? steer_fraction : old_steer-max_steer_change;
}
} // setSteering
//-----------------------------------------------------------------------------
/** Finds the approximate radius of a track's curve. It needs two arguments,
* the number of the drivepoint that marks the beginning of the curve, and
* the number of the drivepoint that marks the ending of the curve.
*
* Based on that you can construct any circle out of 3 points in it, we use
* the two arguments to use the drivelines as the first and last point; the
* middle sector is averaged.
*/
float DefaultRobot::getApproxRadius(const int START, const int END) const
{
const int MIDDLE = (START + END) / 2;
//If the START and END sectors are very close, their average will be one
//of them, and using twice the same point just generates a huge radius
//(too big to be of any use) but it also can generate a division by zero,
//so here is some special handling for that case.
if (MIDDLE == START || MIDDLE == END ) return 99999.0f;
float X1, Y1, X2, Y2, X3, Y3;
//The next line is just to avoid compiler warnings.
X1 = X2 = X3 = Y1 = Y2 = Y3 = 0.0;
if(m_inner_curve == -1)
{
X1 = m_track->m_left_driveline[START][0];
Y1 = m_track->m_left_driveline[START][1];
X2 = m_track->m_left_driveline[MIDDLE][0];
Y2 = m_track->m_left_driveline[MIDDLE][1];
X3 = m_track->m_left_driveline[END][0];
Y3 = m_track->m_left_driveline[END][1];
}
else if (m_inner_curve == 0)
{
X1 = m_track->m_driveline[START][0];
Y1 = m_track->m_driveline[START][1];
X2 = m_track->m_driveline[MIDDLE][0];
Y2 = m_track->m_driveline[MIDDLE][1];
X3 = m_track->m_driveline[END][0];
Y3 = m_track->m_driveline[END][1];
}
else if (m_inner_curve == 1)
{
X1 = m_track->m_right_driveline[START][0];
Y1 = m_track->m_right_driveline[START][1];
X2 = m_track->m_right_driveline[MIDDLE][0];
Y2 = m_track->m_right_driveline[MIDDLE][1];
X3 = m_track->m_right_driveline[END][0];
Y3 = m_track->m_right_driveline[END][1];
}
const float A = X2 - X1;
const float B = Y2 - Y1;
const float C = X3 - X1;
const float D = Y3 - Y1;
const float E = A * ( X1 + X2) + B * (Y1 + Y2);
const float F = C * ( X1 + X3) + D * (Y1 + Y3);
const float G = 2.0f * ( A*( Y3-Y2 ) - B*( X3 - X2 ) );
const float pX = ( D*E - B*F) / G;
const float pY = ( A*F - C*E) / G;
const float radius = sqrt( ( X1 - pX) * ( X1 - pX) + (Y1 - pY) * (Y1 - pY) );
return radius;
} // getApproxRadius
//-----------------------------------------------------------------------------
/**FindCurve() gathers info about the closest sectors ahead: the curve
* angle, the direction of the next turn, and the optimal speed at which the
* curve can be travelled at it's widest angle.
*
* The number of sectors that form the curve is dependant on the kart's speed.
*/
void DefaultRobot::findCurve()
{
const int DRIVELINE_SIZE = (unsigned int)m_track->m_driveline.size();
float total_dist = 0.0f;
int next_hint = m_track_sector;
int i;
for(i = m_track_sector; total_dist < getVelocityLC().getY(); i = next_hint)
{
next_hint = i + 1 < DRIVELINE_SIZE ? i + 1 : 0;
total_dist += sgDistanceVec2(m_track->m_driveline[i], m_track->m_driveline[next_hint]);
}
m_curve_angle = normalizeAngle(m_track->m_angle[i] - m_track->m_angle[m_track_sector]);
m_inner_curve = m_curve_angle > 0.0 ? -1 : 1;
m_curve_target_speed = getMaxSpeedOnTerrain();
} // findCurve