Merge remote-tracking branch 'origin/battleAI' into battleAI

This commit is contained in:
Benau 2015-11-15 12:29:01 +08:00
commit f4b723de82
25 changed files with 2219 additions and 343 deletions

View File

@ -24,7 +24,7 @@
#include "karts/kart_properties.hpp"
#include "karts/skidding_properties.hpp"
#include "karts/controller/ai_properties.hpp"
#include "modes/linear_world.hpp"
#include "modes/world.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
@ -32,59 +32,6 @@
bool AIBaseController::m_ai_debug = false;
/**
This is the base class for all AIs. At this stage there are two similar
AIs: one is the SkiddingAI, which is the AI used in lap based races
(including follow-the-leader mode), the other one is the end controller,
I.e. the controller that takes over from a player (or AI) when the race is
finished.
This base class defines some basic operations:
- It takes care on which part of the QuadGraph the AI currently is.
- It determines which path the AI should take (in case of shortcuts
or forks in the road).
At race start and every time a new lap is started, the AI will compute the
path the kart is taking this lap (computePath). At this stage the decision
which road in case of shortcut to take is purely random. It stores the
information in two arrays:
m_successor_index[i] stores which successor to take from node i.
The successor is a number between 0 and number_of_successors - 1.
m_next_node_index[i] stores the actual index of the graph node that
follows after node i.
Depending on operation one of the other data is more useful, so this
class stores both information to avoid looking it up over and over.
Once this is done (still in computePath), the array m_all_look_aheads is
computed. This array stores for each quad a list of the next (atm) 10 quads.
This is used when the AI is selecting where to drive next, and it will just
pass the list of next quads to findRoadSector.
Note that the quad graph information is stored for every quad in the quad
graph, even if the quad is not on the path chosen. This is necessary since
it can happen that a kart ends up on a path not choses (e.g. perhaps it was
pushed on that part, or couldn't get a sharp corner).
In update(), which gets called one per frame per AI, this object will
determine the quad the kart is currently on (which is then used to determine
where the kart will be driving to). This uses the m_all_look_aheads to
speed up this process (since the kart is likely to be either on the same
quad as it was before, or the next quad in the m_all_look_aheads list).
It will also check if the kart is stuck:
this is done by maintaining a list of times when the kart hits the track. If
(atm) more than 3 collisions happen in 1.5 seconds, the kart is considered
stuck and will trigger a rescue (due to the pushback from the track it will
take some time if a kart is really stuck before it will hit the track again).
This base class also contains some convenience functions which are useful
in all AIs, e.g.:
- steerToPoint: determine the steering angle to use depending on the
current location and the point the kart is driving to.
- normalizeAngle: To normalise the steering angle to be in [-PI,PI].
- setSteering: Converts the steering angle into a steering fraction
in [-1,1].
*/
AIBaseController::AIBaseController(AbstractKart *kart,
StateManager::ActivePlayer *player)
: Controller(kart, player)
@ -95,35 +42,20 @@ AIBaseController::AIBaseController(AbstractKart *kart,
m_ai_properties =
m_kart->getKartProperties()->getAIPropertiesForDifficulty();
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
{
m_world = dynamic_cast<LinearWorld*>(World::getWorld());
m_track = m_world->getTrack();
computePath();
}
else
{
// Those variables are not defined in a battle mode (m_world is
// a linear world, since it assumes the existance of drivelines)
m_world = NULL;
m_track = NULL;
m_next_node_index.clear();
m_all_look_aheads.clear();
m_successor_index.clear();
} // if battle mode
// Don't call our own setControllerName, since this will add a
// billboard showing 'aibasecontroller' to the kar.
Controller::setControllerName("AIBaseController");
} // AIBaseController
}
//-----------------------------------------------------------------------------
void AIBaseController::reset()
{
m_stuck_trigger_rescue = false;
m_collision_times.clear();
m_stuck = false;
m_collision_times.clear();
} // reset
void AIBaseController::update(float dt)
{
m_stuck = false;
}
//-----------------------------------------------------------------------------
/** In debug mode when the user specified --ai-debug on the command line set
* the name of the controller as on-screen text, so that the different AI
@ -139,210 +71,6 @@ void AIBaseController::setControllerName(const std::string &name)
Controller::setControllerName(name);
} // setControllerName
//-----------------------------------------------------------------------------
/** Triggers a recomputation of the path to use, so that the AI does not
* always use the same way.
*/
void AIBaseController::newLap(int lap)
{
if(lap>0)
{
computePath();
}
} // newLap
//-----------------------------------------------------------------------------
/** Computes a path for the AI to follow. This function is called at race
* start and every time a new lap is started. Recomputing the path every
* time will mean that the kart will not always take the same path, but
* (potentially) vary from lap to lap. At this stage the decision is done
* randomly. The AI could be improved by collecting more information about
* each branch of a track, and selecting the 'appropriate' one (e.g. if the
* AI is far ahead, chose a longer/slower path).
*/
void AIBaseController::computePath()
{
m_next_node_index.resize(QuadGraph::get()->getNumNodes());
m_successor_index.resize(QuadGraph::get()->getNumNodes());
std::vector<unsigned int> next;
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
{
next.clear();
// Get all successors the AI is allowed to take.
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/true);
// In case of short cuts hidden for the AI it can be that a node
// might not have a successor (since the first and last edge of
// a hidden shortcut is ignored). Since in the case that the AI
// ends up on a short cut (e.g. by accident) and doesn't have an
// allowed way to drive, it should still be able to drive, so add
// the non-AI successors of that node in this case.
if(next.size()==0)
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/false);
// For now pick one part on random, which is not adjusted during the
// race. Long term statistics might be gathered to determine the
// best way, potentially depending on race position etc.
int r = rand();
int indx = (int)( r / ((float)(RAND_MAX)+1.0f) * next.size() );
// In case of rounding errors0
if(indx>=(int)next.size()) indx--;
m_successor_index[i] = indx;
assert(indx <(int)next.size() && indx>=0);
m_next_node_index[i] = next[indx];
}
const unsigned int look_ahead=10;
// Now compute for each node in the graph the list of the next 'look_ahead'
// graph nodes. This is the list of node that is tested in checkCrashes.
// If the look_ahead is too big, the AI can skip loops (see
// QuadGraph::findRoadSector for details), if it's too short the AI won't
// find too good a driveline. Note that in general this list should
// be computed recursively, but since the AI for now is using only
// (randomly picked) path this is fine
m_all_look_aheads.resize(QuadGraph::get()->getNumNodes());
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
{
std::vector<int> l;
int current = i;
for(unsigned int j=0; j<look_ahead; j++)
{
assert(current < (int)m_next_node_index.size());
l.push_back(m_next_node_index[current]);
current = m_next_node_index[current];
} // for j<look_ahead
m_all_look_aheads[i] = l;
}
} // computePath
//-----------------------------------------------------------------------------
/** Updates the ai base controller each time step. Note that any calls to
* isStuck() must be done before update is called, since update will reset
* the isStuck flag!
* \param dt Time step size.
*/
void AIBaseController::update(float dt)
{
m_stuck_trigger_rescue = false;
if(QuadGraph::get())
{
// Update the current node:
int old_node = m_track_node;
if(m_track_node!=QuadGraph::UNKNOWN_SECTOR)
{
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node,
&m_all_look_aheads[m_track_node]);
}
// If we can't find a proper place on the track, to a broader search
// on off-track locations.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
{
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
// IF the AI is off track (or on a branch of the track it did not
// select to be on), keep the old position.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR ||
m_next_node_index[m_track_node]==-1)
m_track_node = old_node;
}
} // update
//-----------------------------------------------------------------------------
/** This is called when the kart crashed with the terrain. This subroutine
* tries to detect if the AI is stuck by determining if a certain number
* of collisions happened in a certain amount of time, and if so rescues
* the kart.
* \paran m Pointer to the material that was hit (NULL if no specific
* material was used for the part of the track that was hit).
*/
void AIBaseController::crashed(const Material *m)
{
// Defines how many collision in what time will trigger a rescue.
// Note that typically it takes ~0.5 seconds for the AI to hit
// the track again if it is stuck (i.e. time for the push back plus
// time for the AI to accelerate and hit the terrain again).
const unsigned int NUM_COLLISION = 3;
const float COLLISION_TIME = 1.5f;
float time = World::getWorld()->getTime();
if(m_collision_times.size()==0)
{
m_collision_times.push_back(time);
return;
}
// Filter out multiple collisions report caused by single collision
// (bullet can report a collision more than once per frame, and
// resolving it can take a few frames as well, causing more reported
// collisions to happen). The time of 0.2 seconds was experimentally
// found, typically it takes 0.5 seconds for a kart to be pushed back
// from the terrain and accelerate to hit the same terrain again.
if(time - m_collision_times.back() < 0.2f)
return;
// Remove all outdated entries, i.e. entries that are older than the
// collision time plus 1 second. Older entries must be deleted,
// otherwise a collision that happened (say) 10 seconds ago could
// contribute to a stuck condition.
while(m_collision_times.size()>0 &&
time - m_collision_times[0] > 1.0f+COLLISION_TIME)
m_collision_times.erase(m_collision_times.begin());
m_collision_times.push_back(time);
// Now detect if there are enough collision records in the
// specified time interval.
if(time - m_collision_times.front() > COLLISION_TIME
&& m_collision_times.size()>=NUM_COLLISION)
{
// We can't call m_kart->forceRescue here, since crased() is
// called during physics processing, and forceRescue() removes the
// chassis from the physics world, which would then cause
// inconsistencies and potentially a crash during the physics
// processing. So only set a flag, which is tested during update.
m_stuck_trigger_rescue = true;
}
} // crashed(Material)
//-----------------------------------------------------------------------------
/** Returns the next sector of the given sector index. This is used
* for branches in the quad graph to select which way the AI kart should
* go. This is a very simple implementation that always returns the first
* successor, but it can be overridden to allow a better selection.
* \param index Index of the graph node for which the successor is searched.
* \return Returns the successor of this graph node.
*/
unsigned int AIBaseController::getNextSector(unsigned int index)
{
std::vector<unsigned int> successors;
QuadGraph::get()->getSuccessors(index, successors);
return successors[0];
} // getNextSector
//-----------------------------------------------------------------------------
/** This function steers towards a given angle. It also takes a plunger
** attached to this kart into account by modifying the actual steer angle
* somewhat to simulate driving without seeing.
*/
float AIBaseController::steerToAngle(const unsigned int sector,
const float add_angle)
{
float angle = QuadGraph::get()->getAngleToNext(sector,
getNextSector(sector));
//Desired angle minus current angle equals how many angles to turn
float steer_angle = angle - m_kart->getHeading();
if(m_kart->getBlockedByPlungerTime()>0)
steer_angle += add_angle*0.2f;
else
steer_angle += add_angle;
steer_angle = normalizeAngle( steer_angle );
return steer_angle;
} // steerToAngle
//-----------------------------------------------------------------------------
/** Computes the steering angle to reach a certain point. The function will
* request steering by setting the steering angle to maximum steer angle
@ -493,6 +221,17 @@ void AIBaseController::setSteering(float angle, float dt)
* AIBaseController.
* \return True if the kart should skid.
*/
// ------------------------------------------------------------------------
/** Certain AI levels will not receive a slipstream bonus in order to
* be not as hard.
*/
bool AIBaseController::disableSlipstreamBonus() const
{
return m_ai_properties->disableSlipstreamUsage();
} // disableSlipstreamBonus
bool AIBaseController::doSkid(float steer_fraction)
{
// Disable skidding when a plunger is in the face
@ -502,18 +241,70 @@ bool AIBaseController::doSkid(float steer_fraction)
// code is activated, since the AI can not handle this
// properly.
if(m_kart->getKartProperties()->getSkiddingProperties()
->getSkidVisualTime()>0)
->getSkidVisualTime()>0)
return false;
// Otherwise return if we need a sharp turn (which is
// for the old skidding implementation).
return fabsf(steer_fraction)>=m_ai_properties->m_skidding_threshold;
} // doSkid
// ------------------------------------------------------------------------
/** Certain AI levels will not receive a slipstream bonus in order to
* be not as hard.
//-----------------------------------------------------------------------------
/** This is called when the kart crashed with the terrain. This subroutine
* tries to detect if the AI is stuck by determining if a certain number
* of collisions happened in a certain amount of time, and if so rescues
* the kart.
* \paran m Pointer to the material that was hit (NULL if no specific
* material was used for the part of the track that was hit).
*/
bool AIBaseController::disableSlipstreamBonus() const
void AIBaseController::crashed(const Material *m)
{
return m_ai_properties->disableSlipstreamUsage();
} // disableSlipstreamBonus
// Defines how many collision in what time will trigger a rescue.
// Note that typically it takes ~0.5 seconds for the AI to hit
// the track again if it is stuck (i.e. time for the push back plus
// time for the AI to accelerate and hit the terrain again).
const unsigned int NUM_COLLISION = 3;
const float COLLISION_TIME = 1.5f;
float time = World::getWorld()->getTime();
if(m_collision_times.size()==0)
{
m_collision_times.push_back(time);
return;
}
// Filter out multiple collisions report caused by single collision
// (bullet can report a collision more than once per frame, and
// resolving it can take a few frames as well, causing more reported
// collisions to happen). The time of 0.2 seconds was experimentally
// found, typically it takes 0.5 seconds for a kart to be pushed back
// from the terrain and accelerate to hit the same terrain again.
if(time - m_collision_times.back() < 0.2f)
return;
// Remove all outdated entries, i.e. entries that are older than the
// collision time plus 1 second. Older entries must be deleted,
// otherwise a collision that happened (say) 10 seconds ago could
// contribute to a stuck condition.
while(m_collision_times.size()>0 &&
time - m_collision_times[0] > 1.0f+COLLISION_TIME)
m_collision_times.erase(m_collision_times.begin());
m_collision_times.push_back(time);
// Now detect if there are enough collision records in the
// specified time interval.
if(time - m_collision_times.front() > COLLISION_TIME
&& m_collision_times.size()>=NUM_COLLISION)
{
// We can't call m_kart->forceRescue here, since crased() is
// called during physics processing, and forceRescue() removes the
// chassis from the physics world, which would then cause
// inconsistencies and potentially a crash during the physics
// processing. So only set a flag, which is tested during update.
m_stuck = true;
}
} // crashed(Material)

View File

@ -22,18 +22,18 @@
#include "karts/controller/controller.hpp"
#include "states_screens/state_manager.hpp"
class AIProperties;
class LinearWorld;
class ThreeStrikesBattle;
class QuadGraph;
class BattleGraph;
class Track;
class Vec3;
/** A base class for all AI karts. This class basically provides some
* common low level functions.
* \ingroup controller
*/
class AIBaseController : public Controller
{
private:
/** Stores the last N times when a collision happened. This is used
* to detect when the AI is stuck, i.e. N collisions happened in
@ -42,24 +42,26 @@ private:
/** A flag that is set during the physics processing to indicate that
* this kart is stuck and needs to be rescued. */
bool m_stuck_trigger_rescue;
bool m_stuck;
protected:
/** Length of the kart, storing it here saves many function calls. */
/** Length of the kart, storing it here saves many function calls. */
float m_kart_length;
/** Cache width of kart. */
float m_kart_width;
/** Keep a pointer to the track to reduce calls */
Track *m_track;
/** Keep a pointer to world. */
LinearWorld *m_world;
/** A pointer to the AI properties for this kart. */
const AIProperties *m_ai_properties;
<<<<<<< HEAD
/** The current node the kart is on. This can be different from the value
* in LinearWorld, since it takes the chosen path of the AI into account
* (e.g. the closest point in LinearWorld might be on a branch not
@ -77,46 +79,35 @@ protected:
/** For each graph node this list contains a list of the next X
* graph nodes. */
std::vector<std::vector<int> > m_all_look_aheads;
=======
>>>>>>> origin/battleAI
static bool m_ai_debug;
virtual void update (float delta) ;
virtual unsigned int getNextSector(unsigned int index);
virtual void newLap (int lap);
virtual void setControllerName(const std::string &name);
virtual void setSteering (float angle, float dt);
float steerToAngle (const unsigned int sector, const float angle);
float steerToPoint (const Vec3 &point);
void setControllerName(const std::string &name);
float steerToPoint(const Vec3 &point);
float normalizeAngle(float angle);
void computePath();
virtual bool doSkid(float steer_fraction);
// ------------------------------------------------------------------------
/** Nothing special to do when the race is finished. */
virtual void raceFinished() {};
// ------------------------------------------------------------------------
/** This can be called to detect if the kart is stuck (i.e. repeatedly
* hitting part of the track). */
bool isStuck() const { return m_stuck_trigger_rescue; }
bool isStuck() const { return m_stuck; }
static bool m_ai_debug;
public:
AIBaseController(AbstractKart *kart,
AIBaseController(AbstractKart *kart,
StateManager::ActivePlayer *player=NULL);
virtual ~AIBaseController() {};
virtual void reset();
static void enableDebug() {m_ai_debug = true; }
virtual void crashed(const AbstractKart *k) {};
virtual void crashed(const Material *m);
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual bool disableSlipstreamBonus() const;
}; // AIBaseController
virtual void crashed(const Material *m);
#endif
/* EOF */
};
#endif

View File

@ -0,0 +1,266 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2006-2009 Eduardo Hernandez Munoz
// Copyright (C) 2009, 2010 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.
#include "karts/controller/ai_base_lap_controller.hpp"
#include <assert.h>
#include "karts/abstract_kart.hpp"
#include "karts/kart_properties.hpp"
#include "karts/skidding_properties.hpp"
#include "karts/controller/ai_properties.hpp"
#include "modes/linear_world.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
/**
This is the base class for all AIs. At this stage there are two similar
AIs: one is the SkiddingAI, which is the AI used in lap based races
(including follow-the-leader mode), the other one is the end controller,
I.e. the controller that takes over from a player (or AI) when the race is
finished.
This base class defines some basic operations:
- It takes care on which part of the QuadGraph the AI currently is.
- It determines which path the AI should take (in case of shortcuts
or forks in the road).
At race start and every time a new lap is started, the AI will compute the
path the kart is taking this lap (computePath). At this stage the decision
which road in case of shortcut to take is purely random. It stores the
information in two arrays:
m_successor_index[i] stores which successor to take from node i.
The successor is a number between 0 and number_of_successors - 1.
m_next_node_index[i] stores the actual index of the graph node that
follows after node i.
Depending on operation one of the other data is more useful, so this
class stores both information to avoid looking it up over and over.
Once this is done (still in computePath), the array m_all_look_aheads is
computed. This array stores for each quad a list of the next (atm) 10 quads.
This is used when the AI is selecting where to drive next, and it will just
pass the list of next quads to findRoadSector.
Note that the quad graph information is stored for every quad in the quad
graph, even if the quad is not on the path chosen. This is necessary since
it can happen that a kart ends up on a path not choses (e.g. perhaps it was
pushed on that part, or couldn't get a sharp corner).
In update(), which gets called one per frame per AI, this object will
determine the quad the kart is currently on (which is then used to determine
where the kart will be driving to). This uses the m_all_look_aheads to
speed up this process (since the kart is likely to be either on the same
quad as it was before, or the next quad in the m_all_look_aheads list).
It will also check if the kart is stuck:
this is done by maintaining a list of times when the kart hits the track. If
(atm) more than 3 collisions happen in 1.5 seconds, the kart is considered
stuck and will trigger a rescue (due to the pushback from the track it will
take some time if a kart is really stuck before it will hit the track again).
This base class also contains some convenience functions which are useful
in all AIs, e.g.:
- steerToPoint: determine the steering angle to use depending on the
current location and the point the kart is driving to.
- normalizeAngle: To normalise the steering angle to be in [-PI,PI].
- setSteering: Converts the steering angle into a steering fraction
in [-1,1].
*/
AIBaseLapController::AIBaseLapController(AbstractKart *kart,
StateManager::ActivePlayer *player)
: AIBaseController(kart, player)
{
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
{
m_world = dynamic_cast<LinearWorld*>(World::getWorld());
m_track = m_world->getTrack();
computePath();
}
else
{
// Those variables are not defined in a battle mode (m_world is
// a linear world, since it assumes the existance of drivelines)
m_world = NULL;
m_track = NULL;
m_next_node_index.clear();
m_all_look_aheads.clear();
m_successor_index.clear();
} // if battle mode
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseLapController' to the kar.
Controller::setControllerName("AIBaseLapController");
} // AIBaseLapController
//-----------------------------------------------------------------------------
void AIBaseLapController::reset()
{
AIBaseController::reset();
} // reset
//-----------------------------------------------------------------------------
/** Triggers a recomputation of the path to use, so that the AI does not
* always use the same way.
*/
void AIBaseLapController::newLap(int lap)
{
if(lap>0)
{
computePath();
}
} // newLap
//-----------------------------------------------------------------------------
/** Computes a path for the AI to follow. This function is called at race
* start and every time a new lap is started. Recomputing the path every
* time will mean that the kart will not always take the same path, but
* (potentially) vary from lap to lap. At this stage the decision is done
* randomly. The AI could be improved by collecting more information about
* each branch of a track, and selecting the 'appropriate' one (e.g. if the
* AI is far ahead, chose a longer/slower path).
*/
void AIBaseLapController::computePath()
{
m_next_node_index.resize(QuadGraph::get()->getNumNodes());
m_successor_index.resize(QuadGraph::get()->getNumNodes());
std::vector<unsigned int> next;
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
{
next.clear();
// Get all successors the AI is allowed to take.
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/true);
// In case of short cuts hidden for the AI it can be that a node
// might not have a successor (since the first and last edge of
// a hidden shortcut is ignored). Since in the case that the AI
// ends up on a short cut (e.g. by accident) and doesn't have an
// allowed way to drive, it should still be able to drive, so add
// the non-AI successors of that node in this case.
if(next.size()==0)
QuadGraph::get()->getSuccessors(i, next, /*for_ai*/false);
// For now pick one part on random, which is not adjusted during the
// race. Long term statistics might be gathered to determine the
// best way, potentially depending on race position etc.
int r = rand();
int indx = (int)( r / ((float)(RAND_MAX)+1.0f) * next.size() );
// In case of rounding errors0
if(indx>=(int)next.size()) indx--;
m_successor_index[i] = indx;
assert(indx <(int)next.size() && indx>=0);
m_next_node_index[i] = next[indx];
}
const unsigned int look_ahead=10;
// Now compute for each node in the graph the list of the next 'look_ahead'
// graph nodes. This is the list of node that is tested in checkCrashes.
// If the look_ahead is too big, the AI can skip loops (see
// QuadGraph::findRoadSector for details), if it's too short the AI won't
// find too good a driveline. Note that in general this list should
// be computed recursively, but since the AI for now is using only
// (randomly picked) path this is fine
m_all_look_aheads.resize(QuadGraph::get()->getNumNodes());
for(unsigned int i=0; i<QuadGraph::get()->getNumNodes(); i++)
{
std::vector<int> l;
int current = i;
for(unsigned int j=0; j<look_ahead; j++)
{
assert(current < (int)m_next_node_index.size());
l.push_back(m_next_node_index[current]);
current = m_next_node_index[current];
} // for j<look_ahead
m_all_look_aheads[i] = l;
}
} // computePath
//-----------------------------------------------------------------------------
/** Updates the ai base controller each time step. Note that any calls to
* isStuck() must be done before update is called, since update will call
* AIBaseController::update() which will reset the isStuck flag!
* \param dt Time step size.
*/
void AIBaseLapController::update(float dt)
{
AIBaseController::update(dt);
if(QuadGraph::get())
{
// Update the current node:
int old_node = m_track_node;
if(m_track_node!=QuadGraph::UNKNOWN_SECTOR)
{
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node,
&m_all_look_aheads[m_track_node]);
}
// If we can't find a proper place on the track, to a broader search
// on off-track locations.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
{
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
}
// IF the AI is off track (or on a branch of the track it did not
// select to be on), keep the old position.
if(m_track_node==QuadGraph::UNKNOWN_SECTOR ||
m_next_node_index[m_track_node]==-1)
m_track_node = old_node;
}
} // update
//-----------------------------------------------------------------------------
/** Returns the next sector of the given sector index. This is used
* for branches in the quad graph to select which way the AI kart should
* go. This is a very simple implementation that always returns the first
* successor, but it can be overridden to allow a better selection.
* \param index Index of the graph node for which the successor is searched.
* \return Returns the successor of this graph node.
*/
unsigned int AIBaseLapController::getNextSector(unsigned int index)
{
std::vector<unsigned int> successors;
QuadGraph::get()->getSuccessors(index, successors);
return successors[0];
} // getNextSector
//-----------------------------------------------------------------------------
/** This function steers towards a given angle. It also takes a plunger
** attached to this kart into account by modifying the actual steer angle
* somewhat to simulate driving without seeing.
*/
float AIBaseLapController::steerToAngle(const unsigned int sector,
const float add_angle)
{
float angle = QuadGraph::get()->getAngleToNext(sector,
getNextSector(sector));
//Desired angle minus current angle equals how many angles to turn
float steer_angle = angle - m_kart->getHeading();
if(m_kart->getBlockedByPlungerTime()>0)
steer_angle += add_angle*0.2f;
else
steer_angle += add_angle;
steer_angle = normalizeAngle( steer_angle );
return steer_angle;
} // steerToAngle

View File

@ -0,0 +1,96 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2010 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.
#ifndef HEADER_AI_BASE_LAP_CONTROLLER_HPP
#define HEADER_AI_BASE_LAP_CONTROLLER_HPP
#include "karts/controller/ai_base_controller.hpp"
#include "states_screens/state_manager.hpp"
class AIProperties;
class LinearWorld;
class QuadGraph;
class Track;
class Vec3;
/** A base class for all AI karts. This class basically provides some
* common low level functions.
* \ingroup controller
*/
class AIBaseLapController : public AIBaseController
{
protected:
/** The current node the kart is on. This can be different from the value
* in LinearWorld, since it takes the chosen path of the AI into account
* (e.g. the closest point in LinearWorld might be on a branch not
* chosen by the AI). */
int m_track_node;
/** Keep a pointer to world. */
LinearWorld *m_world;
/** Which of the successors of a node was selected by the AI. */
std::vector<int> m_successor_index;
/** For each node in the graph this list contains the chosen next node.
* For normal lap track without branches we always have
* m_next_node_index[i] = (i+1) % size;
* but if a branch is possible, the AI will select one option here.
* If the node is not used, m_next_node_index will be -1. */
std::vector<int> m_next_node_index;
/** For each graph node this list contains a list of the next X
* graph nodes. */
std::vector<std::vector<int> > m_all_look_aheads;
virtual void update (float delta) ;
virtual unsigned int getNextSector(unsigned int index);
virtual void newLap (int lap);
//virtual void setControllerName(const std::string &name);
float steerToAngle (const unsigned int sector, const float angle);
void computePath();
// ------------------------------------------------------------------------
/** Nothing special to do when the race is finished. */
virtual void raceFinished() {};
public:
AIBaseLapController(AbstractKart *kart,
StateManager::ActivePlayer *player=NULL);
virtual ~AIBaseLapController() {};
virtual void reset();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
}; // AIBaseLapController
#endif
/* EOF */

View File

@ -43,6 +43,7 @@ public:
protected:
// Give them access to the members
friend class AIBaseController;
friend class AIBaseLapController;
friend class SkiddingAI;
/** Used to check that all values are defined in the xml file. */

View File

@ -0,0 +1,587 @@
//
// 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-2012 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.
#define AI_DEBUG
#include "karts/controller/battle_ai.hpp"
#ifdef AI_DEBUG
# include "graphics/irr_driver.hpp"
#endif
#include "items/item_manager.hpp"
#include "items/powerup.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "karts/max_speed.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/skidding.hpp"
#include "karts/skidding_properties.hpp"
#include "modes/three_strikes_battle.hpp"
#include "tracks/nav_poly.hpp"
#include "tracks/navmesh.hpp"
#ifdef AI_DEBUG
# include "irrlicht.h"
using namespace irr;
#endif
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
# define isnan _isnan
#else
# include <math.h>
#endif
#include <iostream>
BattleAI::BattleAI(AbstractKart *kart,
StateManager::ActivePlayer *player)
: AIBaseController(kart, player)
{
reset();
#ifdef AI_DEBUG
video::SColor col_debug(128, 128,0,0);
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
m_debug_sphere->setVisible(true);
//m_item_sphere = irr_driver->addSphere(1.0f);
#endif
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
{
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
m_track = m_world->getTrack();
}
else
{
// Those variables are not defined in a battle mode (m_world is
// a linear world, since it assumes the existance of drivelines)
m_world = NULL;
m_track = NULL;
}
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseController' to the kar.
Controller::setControllerName("BattleAI");
} // BattleAI
BattleAI::~BattleAI()
{
#ifdef AI_DEBUG
irr_driver->removeNode(m_debug_sphere);
#endif
} // ~BattleAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void BattleAI::reset()
{
m_current_node = BattleGraph::UNKNOWN_POLY;
m_next_node = BattleGraph::UNKNOWN_POLY;
m_target_node = BattleGraph::UNKNOWN_POLY;
m_target_point = Vec3(0,0,0);
m_target_angle = 0.0f;
m_time_since_stuck = 0.0f;
m_currently_reversing = false;
AIBaseController::reset();
}
//-----------------------------------------------------------------------------
/** This is the main entry point for the AI.
* It is called once per frame for each AI and determines the behaviour of
* the AI, e.g. steering, accelerating/braking, firing.
*/
void BattleAI::update(float dt)
{
handleAcceleration(dt);
handleSteering(dt);
handleItems(dt);
handleBraking();
handleGetUnstuck(dt);
AIBaseController::update(dt);
} //update
//-----------------------------------------------------------------------------
/** Handles acceleration. It also takes the plunger into account.
* \param dt Time step size.
*/
void BattleAI::handleAcceleration( const float dt)
{
//Do not accelerate until we have delayed the start enough
/* if( m_start_delay > 0.0f )
{
m_start_delay -= dt;
m_controls->m_accel = 0.0f;
return;
}
*/
if( m_controls->m_brake )
{
m_controls->m_accel = 0.0f;
return;
}
if(m_kart->getBlockedByPlungerTime()>0)
{
if(m_kart->getSpeed() < m_kart->getCurrentMaxSpeed() / 2)
m_controls->m_accel = 0.05f;
else
m_controls->m_accel = 0.0f;
return;
}
m_controls->m_accel = stk_config->m_ai_acceleration;
} // handleAcceleration
//-----------------------------------------------------------------------------
/** This function sets the steering.
* NOTE: The Battle AI is in development and currently this function is a
* sandbox for testing out the AI. It may actually be doing a lot more than
* just steering to a point, which means this function could be messy.
*/
void BattleAI::handleSteering(const float dt)
{
const AbstractKart* kart = m_world->getPlayerKart(0);
PlayerController* pcontroller = (PlayerController*)kart->getController();
int player_node = pcontroller->getCurrentNode();
std::cout<<"PLayer node " << player_node<<" This cpu kart node" << m_current_node<<std::endl;
if(player_node == BattleGraph::UNKNOWN_POLY || m_current_node == BattleGraph::UNKNOWN_POLY) return;
m_target_node = player_node;
m_target_point = kart->getXYZ();
//handleItemCollection(&m_target_point, &m_target_node);
m_debug_sphere->setPosition(m_target_point.toIrrVector());
if(m_target_node == m_current_node)
{
m_target_point=kart->getXYZ();
// std::cout<<"Aiming at sire nixt\n";
}
else
{
m_next_node = BattleGraph::get()->getNextShortestPathPoly(m_current_node, m_target_node);
// std::cout<<"Aiming at "<<next_node<<"\n";
if(m_next_node == -1) return;
//target_point = NavMesh::get()->getCenterOfPoly(m_next_node);
findPortals(m_current_node, m_target_node);
stringPull(m_kart->getXYZ(),m_target_point);
if(m_path_corners.size()>0)
{
m_debug_sphere->setPosition(m_path_corners[0].toIrrVector());
m_target_point = m_path_corners.front();
}
else
{
std::cout<<" ZERO CORNERS \n";
}
// target_point = m_path_corners[0];
}
m_target_angle = steerToPoint(m_target_point);
// std::cout<<"Target nalge: "<<m_target_angle << " normalized:"<<normalizeAngle(m_target_angle)<<std::endl;
setSteering(m_target_angle,dt);
#ifdef AI_DEBUG
// m_debug_sphere->setPosition(target_point.toIrrVector());
Log::debug("skidding_ai","-Outside of road: steer to center point.\n");
#endif
} // handleSteering
//-----------------------------------------------------------------------------
/** This function finds the polyon edges(portals) that the AI will cross before
* reaching its destination. We start from the current polygon and call
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
* path to the destination. Then find the common edge between the current
* poly and the next poly, store it and step through the channel.
*
* 1----2----3 In this case, the portals are:
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start The start node(polygon) of the channel.
* \param end The end node(polygon) of the channel.
*/
void BattleAI::findPortals(int start, int end)
{
int this_node = start;
// We can't use NULL because NULL==0 which is a valid node, so we initialize
// with a value that is always invalid.
int next_node = -999;
m_portals.clear();
while(next_node != end && this_node != -1 && next_node != -1 && this_node != end)
{
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
std::vector<int> this_node_verts =
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
std::vector<int> next_node_verts=
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
// We reverse next_node_verts so it becomes easy to compare edges in the next step
std::reverse(next_node_verts.begin(),next_node_verts.end());
Vec3 portalLeft, portalRight;
//bool flag = 0;
for(unsigned int n_i=0; n_i<next_node_verts.size(); n_i++)
{
for(unsigned int t_i=0; t_i< this_node_verts.size(); t_i++)
{
if((next_node_verts[n_i] == this_node_verts[t_i])&&
(next_node_verts[(n_i+1)%next_node_verts.size()]==
this_node_verts[(t_i+1)%this_node_verts.size()]))
{
portalLeft = NavMesh::get()->
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
}
}
}
m_portals.push_back(std::make_pair(portalLeft,portalRight));
// for debugging:
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
this_node=next_node;
}
} // findPortals
//-----------------------------------------------------------------------------
/** This function implements the funnel algorithm for finding shortest paths
* through a polygon channel. This means that we should move from corner to
* corner to move on the most straight and shortest path to the destination.
* This can be visualized as pulling a string from the end point to the start.
* The string will bend at the corners, and this algorithm will find those
* corners using portals from findPortals(). The AI will aim at the first
* corner and the rest can be used for estimating the curve (braking).
*
* 1----2----3 In this case, the corners are:
* |strt| | <5,10,end>
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start_pos The start position (usually the AI's current position).
* \param end_pos The end position (m_target_point).
*/
void BattleAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
{
Vec3 funnel_apex = start_pos;
Vec3 funnel_left = m_portals[0].first;
Vec3 funnel_right = m_portals[0].second;
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
m_portals.push_back(std::make_pair(end_pos,end_pos));
m_path_corners.clear();
const float eps=0.0001f;
for(unsigned int i=0; i<m_portals.size(); i++)
{
Vec3 portal_left = m_portals[i].first;
Vec3 portal_right = m_portals[i].second;
//Compute for left edge
if((funnel_left==funnel_apex) || portal_left.sideOfLine2D(funnel_apex,funnel_left)<=-eps)
{
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
//funnel_left = portal_left;
fun_left_index = i;
if( portal_left.sideOfLine2D(funnel_apex,funnel_right)<-eps)
{
funnel_apex = funnel_right;
apex_index = fun_right_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
//Compute for right edge
if( (funnel_right==funnel_apex) ||portal_right.sideOfLine2D(funnel_apex,funnel_right)>=eps)
{
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
//funnel_right = portal_right;
fun_right_index = i;
if( portal_right.sideOfLine2D(funnel_apex,funnel_left)>eps)
{
funnel_apex = funnel_left;
apex_index = fun_left_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i=apex_index;
continue;
}
}
}
//Push end_pos to m_path_corners so if no corners, we aim at target
m_path_corners.push_back(end_pos);
} // stringPull
//-----------------------------------------------------------------------------
/** Calls AIBaseController::isStuck() to determine if the AI is stuck.
* If the AI is stuck then it will override the controls and start reverse
* the kart while turning.
*/
void BattleAI::handleGetUnstuck(const float dt)
{
if(isStuck() == true)
{
m_time_since_stuck = 0.0f;
m_currently_reversing = true;
m_controls->reset();
}
if(m_currently_reversing == true)
{
setSteering(-1.0f*m_target_angle,dt);
setSteering(-2.0f*m_target_angle,dt);
setSteering(-2.0f*m_target_angle,dt);
m_controls->m_accel = -0.35f;
/*
if(m_target_angle > 0)
setSteering(M_PI,dt);
else setSteering(-M_PI,dt);
*/
m_time_since_stuck += dt;
if(m_time_since_stuck >= 0.6f)
{
m_currently_reversing = false;
m_time_since_stuck = 0.0f;
}
}
} // handleGetUnstuck
//-----------------------------------------------------------------------------
/** This function handles braking. It calls determineTurnRadius() to find out
* the curve radius. Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void BattleAI::handleBraking()
{
m_controls->m_brake = false;
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
std::vector<Vec3> points;
if(m_current_node == -1 || m_next_node == -1 || m_target_node == -1)
return;
points.push_back(m_kart->getXYZ());
points.push_back(m_path_corners[0]);
points.push_back((m_path_corners.size()>=2)?m_path_corners[1]:m_path_corners[0]);
float current_curve_radius = BattleAI::determineTurnRadius(points);
Vec3 d1 = m_kart->getXYZ() - m_target_point; Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
if (d1.length2_2d() < d2.length2_2d())
current_curve_radius = d1.length_2d();
//std::cout<<"\n Radius: " << current_curve_radius;
float max_turn_speed =
m_kart->getKartProperties()
->getSpeedForTurnRadius(current_curve_radius);
if(m_kart->getSpeed() > max_turn_speed &&
m_kart->getSpeed()>MIN_SPEED )// &&
//fabsf(m_controls->m_steer) > 0.95f )
{
m_controls->m_brake = true;
std::cout<<"Braking"<<std::endl;
#ifdef DEBUG
if(m_ai_debug)
Log::debug("SkiddingAI",
"speed %f too tight curve: radius %f ",
m_kart->getSpeed(),
m_kart->getIdent().c_str(),
current_curve_radius);
#endif
}
return;
} // handleBraking
//-----------------------------------------------------------------------------
/** The turn radius is determined by fitting a parabola to 3 points: current
* location of AI, first corner and the second corner. Once the constants are
* computed, a formula is used to find the radius of curvature at the kart's
* current location.
* NOTE: This method does not apply enough braking, should think of something
* else.
*/
float BattleAI::determineTurnRadius( std::vector<Vec3>& points )
{
// Declaring variables
float a,b,c;
irr::core::CMatrix4<float> A;
irr::core::CMatrix4<float> X;
irr::core::CMatrix4<float> B;
//Populating matrices
for(unsigned int i=0; i<3; i++)
{
A(i,0)= points[i].x()*points[i].x();
A(i,1)= points[i].x();
// std::cout<<"X"<<points[i].x();
A(i,2)= 1.0f;
A(i,3)= 0.0f;
}
A(3,0)=A(3,1)=A(3,2) = 0.0f;
A(3,3) = 1.0f;
for(unsigned int i=0; i<3; i++)
{
B(i,0)= points[i].z();
//std::cout<<"Z"<<points[i].z()<<"\n";
B(i,1)= 0.0f;
B(i,2)= 0.0f;
B(i,3)= 0.0f;
}
B(3,0)=B(3,1)=B(3,2)=B(3,3) = 0.0f;
//Computing inverse : X = inv(A)*B
irr::core::CMatrix4<float> invA;
if(!A.getInverse(invA))
{
return -1;
}
X = invA*B;
a = X(0,0);
b = X(0,1);
c = X(0,2);
float x = points.front().x();
float z = a*pow(x,2) + b*x + c;
float dx_by_dz = 2*a*x + b;
float d2x_by_dz = 2*a;
float radius = pow(abs(1 + pow(dx_by_dz,2)),1.5f)/ abs(d2x_by_dz);
return radius;
}
// Alternative implementation of isStuck()
/*
float BattleAI::isStuck(const float dt)
{
// check if kart is stuck
if(m_kart->getSpeed()<2.0f && !m_kart->getKartAnimation() &&
!m_world->isStartPhase())
{
m_time_since_stuck += dt;
if(m_time_since_stuck > 2.0f)
{
return true;
m_time_since_stuck=0.0f;
} // m_time_since_stuck > 2.0f
}
else
{
m_time_since_stuck = 0.0f;
return false;
}
}
*/
void BattleAI::handleItems(const float dt)
{
m_controls->m_fire = true;
}
void BattleAI::handleItemCollection(Vec3 *aim_point, int* target_node)
{
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_BOWLING) return;
Vec3 old_aim_point = *aim_point;
float distance = 5.0f;
bool found_suitable_item = false;
const std::vector< std::pair<Item*, int> >& item_list =
BattleGraph::get()->getItemList();
int items_count = item_list.size();
for (unsigned int j = 0; j < 50; j++)
{
for (unsigned int i = 0; i < items_count; ++i)
{
Item* item = item_list[i].first;
Vec3 d = item->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance)
{
if (item->getType() == Item::ITEM_BONUS_BOX && !item->wasCollected())
{
m_item_to_collect = item;
found_suitable_item = true;
*aim_point = item->getXYZ();
*target_node = item_list[i].second;
break;
}
}
}
distance = 2.0f * distance;
}
}

View File

@ -0,0 +1,126 @@
//
// 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) 2010 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.
#ifndef HEADER_BATTLE_AI_HPP
#define HEADER_BATTLE_AI__HPP
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/random_generator.hpp"
class AIProperties;
class ThreeStrikesBattle;
class BattleGraph;
class Track;
class Vec3;
class Item;
namespace irr
{
namespace scene { class ISceneNode; }
namespace video { class ITexture; }
}
class BattleAI : public AIBaseController
{
private:
/** Holds the current position of the AI on the battle graph. Sets to
* BattleGraph::UNKNOWN_POLY if the location is unknown. This variable is
* updated in ThreeStrikesBattle::updateKartNodes() */
int m_current_node;
/** Holds the next node the kart is expected to drive to. Currently unused. */
int m_next_node;
/** The node(poly) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
/** The steering angle required to reach the target point. */
float m_target_angle;
/** Holds the set of portals that the kart will cross when moving through
* polygon channel. See findPortals() */
std::vector<std::pair<Vec3,Vec3> > m_portals;
/** Holds the corner points computed using the funnel algorithm that the AI
* will eventaully move through. See stringPull() */
std::vector<Vec3> m_path_corners;
/** This is a timer that counts down when the kart is reversing to get unstuck */
float m_time_since_stuck;
/** Indicates that the kart is currently reversing, and m_time_since_stuck is
* counting down. */
bool m_currently_reversing;
const Item *m_item_to_collect;
float determineTurnRadius(std::vector<Vec3>& points);
void findPortals(int start, int end);
void stringPull(const Vec3&, const Vec3&);
void handleAcceleration(const float dt) ;
void handleSteering(const float dt);
void handleBraking();
void handleGetUnstuck(const float dt);
void handleItems(const float dt);
void handleItemCollection(Vec3*, int*);
protected:
/** Keep a pointer to world. */
ThreeStrikesBattle *m_world;
#ifdef AI_DEBUG
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere;
#endif
public:
BattleAI(AbstractKart *kart,
StateManager::ActivePlayer *player=NULL);
~BattleAI();
unsigned int getCurrentNode() const { return m_current_node; }
void setCurrentNode(int i) { m_current_node = i; }
virtual void update (float delta);
virtual void reset ();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual bool disableSlipstreamBonus() const {return 0;}
virtual void newLap(int lap) {};
};
#endif

View File

@ -52,13 +52,13 @@
EndController::EndController(AbstractKart *kart, StateManager::ActivePlayer *player,
Controller *prev_controller)
: AIBaseController(kart, player)
: AIBaseLapController(kart, player)
{
m_previous_controller = prev_controller;
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
{
// Overwrite the random selected default path from AIBaseController
// Overwrite the random selected default path from AIBaseLapController
// with a path that always picks the first branch (i.e. it follows
// the main driveline).
std::vector<unsigned int> next;
@ -124,7 +124,7 @@ EndController::~EndController()
//-----------------------------------------------------------------------------
void EndController::reset()
{
AIBaseController::reset();
AIBaseLapController::reset();
m_crash_time = 0.0f;
m_time_since_stuck = 0.0f;
@ -179,7 +179,7 @@ void EndController::update(float dt)
m_controls->m_brake = false;
m_controls->m_accel = 1.0f;
AIBaseController::update(dt);
AIBaseLapController::update(dt);
// In case of battle mode: don't do anything
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||

View File

@ -21,7 +21,7 @@
#ifndef HEADER_END_CONTROLLER_HPP
#define HEADER_END_CONTROLLER_HPP
#include "karts/controller/ai_base_controller.hpp"
#include "karts/controller/ai_base_lap_controller.hpp"
class Camera;
class LinearWorld;
@ -40,7 +40,7 @@ namespace irr
/**
* \ingroup controller
*/
class EndController : public AIBaseController
class EndController : public AIBaseLapController
{
private:
/** Stores the type of the previous controller. This is necessary so that

View File

@ -37,6 +37,7 @@
#include "network/network_world.hpp"
#include "race/history.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/constants.hpp"
#include "utils/log.hpp"
#include "utils/translation.hpp"
@ -94,6 +95,7 @@ void PlayerController::reset()
m_prev_nitro = false;
m_sound_schedule = false;
m_penalty_time = 0;
m_current_node = BattleGraph::UNKNOWN_POLY;
} // reset
// ----------------------------------------------------------------------------

View File

@ -44,6 +44,9 @@ private:
float m_penalty_time;
/** This variable is required for battle mode **/
int m_current_node;
/** The camera attached to the kart for this controller. The camera
* object is managed in the Camera class, so no need to free it. */
Camera *m_camera;
@ -65,6 +68,8 @@ public:
void handleZipper (bool play_sound);
void collectedItem (const Item &item, int add_info=-1,
float previous_energy=0);
unsigned int getCurrentNode() const { return m_current_node; }
void setCurrentNode(int i) { m_current_node = i; }
virtual void skidBonusTriggered();
virtual void setPosition (int p);
virtual bool isPlayerController() const {return true;}

View File

@ -43,7 +43,7 @@
#endif
#include "karts/controller/ai_base_controller.hpp"
#include "karts/controller/ai_base_lap_controller.hpp"
#include "race/race_manager.hpp"
#include "tracks/graph_node.hpp"
#include "utils/random_generator.hpp"
@ -111,7 +111,7 @@ the AI does the following steps:
\ingroup controller
*/
class SkiddingAI : public AIBaseController
class SkiddingAI : public AIBaseLapController
{
private:

View File

@ -169,7 +169,7 @@
#include "items/attachment_manager.hpp"
#include "items/item_manager.hpp"
#include "items/projectile_manager.hpp"
#include "karts/controller/ai_base_controller.hpp"
#include "karts/controller/ai_base_lap_controller.hpp"
#include "karts/kart_properties.hpp"
#include "karts/kart_properties_manager.hpp"
#include "modes/demo_world.hpp"

View File

@ -25,6 +25,8 @@
#include "graphics/irr_driver.hpp"
#include "io/file_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/battle_ai.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/kart_model.hpp"
#include "karts/kart_properties.hpp"
#include "physics/physics.hpp"
@ -57,10 +59,12 @@ void ThreeStrikesBattle::init()
m_display_rank = false;
// check for possible problems if AI karts were incorrectly added
if(getNumKarts() > race_manager->getNumPlayers())
// FIXME : remove this bit of code in future since ai will be added
/* if(getNumKarts() > race_manager->getNumPlayers())
{
Log::fatal("[Three Strikes Battle]", "No AI exists for this game mode");
}
*/
m_kart_info.resize(m_karts.size());
} // ThreeStrikesBattle
@ -296,7 +300,8 @@ void ThreeStrikesBattle::update(float dt)
WorldWithRank::update(dt);
WorldWithRank::updateTrack(dt);
// insert blown away tire(s) now if was requested
PhysicalObject::BodyTypes body_shape;
updateKartNodes(); // insert blown away tire(s) now if was requested
while (m_insert_tire > 0)
{
std::string tire;
@ -429,6 +434,127 @@ bool ThreeStrikesBattle::isRaceOver()
return getCurrentNumKarts()==1 || getCurrentNumPlayers()==0;
} // isRaceOver
//-----------------------------------------------------------------------------
/** Updates the m_current_node value of each kart controller to localize it
* on the navigation mesh.
*/
void ThreeStrikesBattle::updateKartNodes()
{
const unsigned int n = getNumKarts();
for(unsigned int i=0; i<n; i++)
{
if(m_karts[i]->isEliminated()) continue;
const AbstractKart* kart = m_karts[i];
if(!kart->getController()->isPlayerController())
{
BattleAI* controller = (BattleAI*)(kart->getController());
int saved_current_node = controller->getCurrentNode();
if(controller->getCurrentNode()!= BattleGraph::UNKNOWN_POLY)
{
//check if the kart is still on the same node
const NavPoly& p = BattleGraph::get()->getPolyOfNode(controller->getCurrentNode());
if(p.pointInPoly(kart->getXYZ())) continue;
//if not then check all adjacent polys
const std::vector<int>& adjacents =
NavMesh::get()->getAdjacentPolys(controller->getCurrentNode());
// Set m_current_node to unknown so that if no adjacent poly checks true
// we look everywhere the next time updateCurrentNode is called. This is
// useful in cases when you are "teleported" to some other poly, ex. rescue
controller->setCurrentNode(BattleGraph::UNKNOWN_POLY);
for(unsigned int i=0; i<adjacents.size(); i++)
{
const NavPoly& p_temp =
BattleGraph::get()->getPolyOfNode(adjacents[i]);
if(p_temp.pointInPoly(kart->getXYZ()))
controller->setCurrentNode(adjacents[i]);
}
}
//Current node is still unkown
if(controller->getCurrentNode() == BattleGraph::UNKNOWN_POLY)
{
bool flag = 0;
unsigned int max_count = BattleGraph::get()->getNumNodes();
//float min_dist = 9999.99f;
for(unsigned int i =0; i<max_count; i++)
{
const NavPoly& p = BattleGraph::get()->getPolyOfNode(i);
if((p.pointInPoly(kart->getXYZ())))
{
controller->setCurrentNode(i);
flag = 1;
//min_dist = (p.getCenter() - m_kart->getXYZ()).length_2d();
}
}
if(flag == 0) controller->setCurrentNode(saved_current_node);
}
}
else
{
PlayerController* controller = (PlayerController*)(kart->getController());
int saved_current_node = controller->getCurrentNode();
if(controller->getCurrentNode()!= BattleGraph::UNKNOWN_POLY)
{
//check if the kart is still on the same node
const NavPoly& p = BattleGraph::get()->getPolyOfNode(controller->getCurrentNode());
if(p.pointInPoly(kart->getXYZ())) continue;
//if not then check all adjacent polys
const std::vector<int>& adjacents =
NavMesh::get()->getAdjacentPolys(controller->getCurrentNode());
// Set m_current_node to unknown so that if no adjacent poly checks true
// we look everywhere the next time updateCurrentNode is called. This is
// useful in cases when you are "teleported" to some other poly, ex. rescue
controller->setCurrentNode(BattleGraph::UNKNOWN_POLY);
for(unsigned int i=0; i<adjacents.size(); i++)
{
const NavPoly& p_temp =
BattleGraph::get()->getPolyOfNode(adjacents[i]);
if(p_temp.pointInPoly(kart->getXYZ()))
controller->setCurrentNode(adjacents[i]);
}
}
if(controller->getCurrentNode() == BattleGraph::UNKNOWN_POLY)
{
bool flag = 0;
unsigned int max_count = BattleGraph::get()->getNumNodes();
//float min_dist = 9999.99f;
for(unsigned int i =0; i<max_count; i++)
{
const NavPoly& p = BattleGraph::get()->getPolyOfNode(i);
if((p.pointInPoly(kart->getXYZ())))
{
controller->setCurrentNode(i);
flag = 1;
//min_dist = (p.getCenter() - m_kart->getXYZ()).length_2d();
}
}
if(flag == 0) controller->setCurrentNode(saved_current_node);
}
}
}
}
//-----------------------------------------------------------------------------
/** Called when the race finishes, i.e. after playing (if necessary) an
* end of race animation. It updates the time for all karts still racing,

View File

@ -70,6 +70,9 @@ private:
PtrVector<TrackObject, REF> m_tires;
/** Function to udpate the locations of all karts on the polygon map */
void updateKartNodes();
public:
/** Used to show a nice graph when battle is over */

View File

@ -31,6 +31,7 @@
#include "input/device_manager.hpp"
#include "input/keyboard_device.hpp"
#include "items/projectile_manager.hpp"
#include "karts/controller/battle_ai.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/end_controller.hpp"
#include "karts/controller/skidding_ai.hpp"
@ -351,13 +352,19 @@ Controller* World::loadAIController(AbstractKart *kart)
{
Controller *controller;
int turn=0;
// If different AIs should be used, adjust turn (or switch randomly
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
turn=1;
// If different AIs 8should be used, adjust turn (or switch randomly
// or dependent on difficulty)
switch(turn)
{
case 0:
controller = new SkiddingAI(kart);
break;
case 1:
controller = new BattleAI(kart);
break;
default:
Log::warn("[World]", "Unknown AI, using default.");
controller = new SkiddingAI(kart);

View File

@ -233,7 +233,7 @@ void RaceSetupScreen::init()
}
#ifdef ENABLE_SOCCER_MODE
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
{
irr::core::stringw name5 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
@ -244,7 +244,6 @@ void RaceSetupScreen::init()
#define ENABLE_EASTER_EGG_MODE
#ifdef ENABLE_EASTER_EGG_MODE
if(race_manager->getNumLocalPlayers() == 1)
{
irr::core::stringw name1 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_EASTER_EGG)) + L"\n";

278
src/tracks/battle_graph.cpp Normal file
View File

@ -0,0 +1,278 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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, B
#include "tracks/battle_graph.hpp"
#include <IMesh.h>
#include <ICameraSceneNode.h>
#include <IMeshSceneNode.h>
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "items/item_manager.hpp"
#include "tracks/navmesh.hpp"
#include "utils/vec3.hpp"
#include <iostream>
const int BattleGraph::UNKNOWN_POLY = -1;
BattleGraph * BattleGraph::m_battle_graph = NULL;
/** Constructor, Creates a navmesh, builds a graph from the navmesh and
* then runs shortest path algorithm to find and store paths to be used
* by the AI. */
BattleGraph::BattleGraph(const std::string &navmesh_file_name)
{
NavMesh::create(navmesh_file_name);
m_navmesh_file = navmesh_file_name;
buildGraph(NavMesh::get());
computeFloydWarshall();
findItemsOnGraphNodes(ItemManager::get());
} // BattleGraph
// -----------------------------------------------------------------------------
/** Builds a graph from an existing NavMesh. The graph is stored as an adjacency
* matrix. */
void BattleGraph::buildGraph(NavMesh* navmesh)
{
unsigned int n_polys = navmesh->getNumberOfPolys();
m_distance_matrix = std::vector< std::vector<float> > (n_polys, std::vector<float>(n_polys, 9999.9f));
for(unsigned int i=0; i<n_polys; i++)
{
NavPoly currentPoly = navmesh->getNavPoly(i);
std::vector<int> adjacents = navmesh->getAdjacentPolys(i);
for(unsigned int j=0; j<adjacents.size(); j++)
{
Vec3 adjacentPolyCenter = navmesh->getCenterOfPoly(adjacents[j]);
float distance = Vec3(adjacentPolyCenter - currentPoly.getCenter()).length_2d();
m_distance_matrix[i][adjacents[j]] = distance;
//m_distance_matrix[adjacents[j]][i] = distance;
}
m_distance_matrix[i][i] = 0.0f;
}
} // buildGraph
// -----------------------------------------------------------------------------
/** computeFloydWarshall() computes the shortest distance between any two nodes.
* At the end of the computation, m_distance_matrix[i][j] stores the shortest path
* distance from i to j and m_parent_poly[i][j] stores the last vertex visited on the
* shortest path from i to j before visiting j. Suppose the shortest path from i to j is
* i->......->k->j then m_parent_poly[i][j] = k
*/
void BattleGraph::computeFloydWarshall()
{
int n = getNumNodes();
// initialize m_parent_poly with unknown_poly so that if no path is found b/w i and j
// then m_parent_poly[i][j] = -1 (UNKNOWN_POLY)
// AI must check this
m_parent_poly = std::vector< std::vector<int> > (n, std::vector<int>(n,BattleGraph::UNKNOWN_POLY));
for(unsigned int i=0; i<n; i++)
for(unsigned int j=0; j<n; j++)
{
if(i == j || m_distance_matrix[i][j]>=9899.9f) m_parent_poly[i][j]=-1;
else m_parent_poly[i][j] = i;
}
for(unsigned int k=0; k<n; k++)
{
for(unsigned int i=0; i<n; i++)
{
for(unsigned int j=0; j<n; j++)
{
if( (m_distance_matrix[i][k] + m_distance_matrix[k][j]) < m_distance_matrix[i][j])
{
m_distance_matrix[i][j] = m_distance_matrix[i][k] + m_distance_matrix[k][j];
m_parent_poly[i][j] = m_parent_poly[k][j];
}
}
}
}
} // computeFloydWarshall
// -----------------------------------------------------------------------------
/** Destructor, destroys NavMesh and the debug mesh if it exists */
BattleGraph::~BattleGraph(void)
{
NavMesh::destroy();
if(UserConfigParams::m_track_debug)
cleanupDebugMesh();
} // ~BattleGraph
// -----------------------------------------------------------------------------
/** Creates the actual mesh that is used by createDebugMesh() */
void BattleGraph::createMesh(bool enable_transparency,
const video::SColor *track_color)
{
// The debug track will not be lighted or culled.
video::SMaterial m;
m.BackfaceCulling = false;
m.Lighting = false;
if(enable_transparency)
m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m_mesh = irr_driver->createQuadMesh(&m);
m_mesh_buffer = m_mesh->getMeshBuffer(0);
assert(m_mesh_buffer->getVertexType()==video::EVT_STANDARD);
// Eps is used to raise the track debug quads a little bit higher than
// the ground, so that they are actually visible.
core::vector3df eps(0, 0.4f, 0);
video::SColor defaultColor(255, 255, 0, 0), c;
// Declare vector to hold new converted vertices, vertices are copied over
// for each polygon, although it results in redundant vertex copies in the
// final vector, this is the only way I know to make each poly have different color.
std::vector<video::S3DVertex> new_v;
// Declare vector to hold indices
std::vector<irr::u16> ind;
// Now add all polygons
int i=0;
for(unsigned int count=0; count<getNumNodes(); count++)
{
///compute colors
if(!track_color)
{
c.setAlpha(178);
//c.setRed ((i%2) ? 255 : 0);
//c.setBlue((i%3) ? 0 : 255);
c.setRed(7*i%256);
c.setBlue((2*i)%256);
c.setGreen((3*i)%256);
}
NavPoly poly = NavMesh::get()->getNavPoly(count);
//std::vector<int> vInd = poly.getVerticesIndex();
const std::vector<Vec3>& v = poly.getVertices();
// Number of triangles in the triangle fan
unsigned int numberOfTriangles = v.size() -2 ;
// Set up the indices for the triangles
for( unsigned int count = 1; count<=numberOfTriangles; count++)
{
video::S3DVertex v1,v2,v3;
v1.Pos=v[0].toIrrVector() + eps;
v2.Pos=v[count].toIrrVector() + eps;
v3.Pos=v[count+1].toIrrVector() + eps;
v1.Color = c;
v2.Color = c;
v3.Color = c;
core::triangle3df tri(v1.Pos, v2.Pos, v3.Pos);
core::vector3df normal = tri.getNormal();
normal.normalize();
v1.Normal = normal;
v2.Normal = normal;
v3.Normal = normal;
new_v.push_back(v1);
new_v.push_back(v2);
new_v.push_back(v3);
ind.push_back(i++);
ind.push_back(i++);
ind.push_back(i++);
}
}
m_mesh_buffer->append(new_v.data(), new_v.size(), ind.data(), ind.size());
// Instead of setting the bounding boxes, we could just disable culling,
// since the debug track should always be drawn.
//m_node->setAutomaticCulling(scene::EAC_OFF);
m_mesh_buffer->recalculateBoundingBox();
m_mesh->setBoundingBox(m_mesh_buffer->getBoundingBox());
} // createMesh
// -----------------------------------------------------------------------------
/** Creates the debug mesh to display the quad graph on top of the track
* model. */
void BattleGraph::createDebugMesh()
{
if(getNumNodes()<=0) return; // no debug output if not graph
createMesh(/*enable_transparency*/true);
m_node = irr_driver->addMesh(m_mesh);
#ifdef DEBUG
m_node->setName("track-debug-mesh");
#endif
} // createDebugMesh
// -----------------------------------------------------------------------------
/** Cleans up the debug mesh */
void BattleGraph::cleanupDebugMesh()
{
if(m_node != NULL)
irr_driver->removeNode(m_node);
m_node = NULL;
// No need to call irr_driber->removeMeshFromCache, since the mesh
// was manually made and so never added to the mesh cache.
m_mesh->drop();
m_mesh = NULL;
}
void BattleGraph::findItemsOnGraphNodes(ItemManager * item_manager)
{
unsigned int item_count = item_manager->getNumberOfItems();
for (unsigned int i = 0; i < item_count; ++i)
{
Item* item = item_manager->getItem(i);
Vec3 xyz = item->getXYZ();
int polygon = BattleGraph::UNKNOWN_POLY;
float min_dist = 999999.9f;
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
{
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz))
{
float dist = xyz.getY() - NavMesh::get()->getCenterOfPoly(j).getY();
if (dist < min_dist && dist>-1.0f)
{
polygon = j;
min_dist = dist;
}
}
}
m_items_on_graph.push_back(std::make_pair(item, polygon));
}
}

132
src/tracks/battle_graph.hpp Normal file
View File

@ -0,0 +1,132 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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, B
#ifndef HEADER_BATTLE_GRAPH_HPP
#define HEADER_BATTLE_GRAPH_HPP
#include <vector>
#include <string>
#include <set>
#include "tracks/navmesh.hpp"
class Navmesh;
class Item;
class ItemManager;
namespace irr
{
namespace scene { class ISceneNode; class IMesh; class IMeshBuffer; }
namespace video { class ITexture; }
}
using namespace irr;
/**
* \ingroup tracks
*
* \brief This class stores a graph constructed from the navigatoin mesh.
* It uses a 'simplified singleton' design pattern: it has a static create
* function to create exactly one instance, a destroy function, and a get
* function (that does not have the side effect of the 'normal singleton'
* design pattern to create an instance).
\ingroup tracks
*/
class BattleGraph
{
private:
static BattleGraph *m_battle_graph;
/** The actual graph data structure, it is an adjacency matrix */
std::vector< std::vector< float > > m_distance_matrix;
/** The matrix that is used to store computed shortest paths */
std::vector< std::vector< int > > m_parent_poly;
/** For debug mode only: the node of the debug mesh. */
scene::ISceneNode *m_node;
/** For debug only: the mesh of the debug mesh. */
scene::IMesh *m_mesh;
/** For debug only: the actual mesh buffer storing the quads. */
scene::IMeshBuffer *m_mesh_buffer;
/** Stores the name of the file containing the NavMesh data */
std::string m_navmesh_file;
std::vector< std::pair<Item*, int> > m_items_on_graph;
void buildGraph(NavMesh*);
void computeFloydWarshall();
void createMesh(bool enable_transparency=false,
const video::SColor *track_color=NULL);
void findItemsOnGraphNodes(ItemManager*);
BattleGraph(const std::string &navmesh_file_name);
~BattleGraph(void);
public:
static const int UNKNOWN_POLY;
/** Returns the one instance of this object. */
static BattleGraph *get() { return m_battle_graph; }
// ----------------------------------------------------------------------
/** Asserts that no BattleGraph instance exists. Then
* creates a BattleGraph instance. */
static void create(const std::string &navmesh_file_name)
{
assert(m_battle_graph==NULL);
m_battle_graph = new BattleGraph(navmesh_file_name);
} // create
// ----------------------------------------------------------------------
/** Cleans up the BattleGraph instance if it exists */
static void destroy()
{
if(m_battle_graph)
{
delete m_battle_graph;
m_battle_graph = NULL;
}
} // destroy
// ----------------------------------------------------------------------
/** Returns the number of nodes in the BattleGraph (equal to the number of
* polygons in the NavMesh */
unsigned int getNumNodes() const { return m_distance_matrix.size(); }
// ----------------------------------------------------------------------
/** Returns the NavPoly corresponding to the i-th node of the BattleGraph */
const NavPoly& getPolyOfNode(int i) const
{ return NavMesh::get()->getNavPoly(i); }
// ----------------------------------------------------------------------
/** Returns the next polygon on the shortest path from i to j.
* Note: m_parent_poly[j][i] contains the parent of i on path from j to i,
* which is the next node on the path from i to j (undirected graph) */
const int & getNextShortestPathPoly(int i, int j) const
{ return m_parent_poly[j][i]; }
const std::vector< std::pair<Item*, int> >& getItemList()
{ return m_items_on_graph; }
void createDebugMesh();
void cleanupDebugMesh();
}; //BattleGraph
#endif

82
src/tracks/nav_poly.cpp Normal file
View File

@ -0,0 +1,82 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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.
#include "tracks/nav_poly.hpp"
#include "tracks/navmesh.hpp"
#include <algorithm>
#include <iostream>
/** Constructor that takes a vector of points and a vector of adjacnet polygons */
NavPoly::NavPoly(const std::vector<int> &polygonVertIndices,
const std::vector<int> &adjacentPolygonIndices)
{
m_vertices = polygonVertIndices;
m_adjacents = adjacentPolygonIndices;
std::vector<Vec3> xyz_points = getVertices();
Vec3 temp(0.0f,0.0f,0.0f);
for(unsigned int i=0; i<xyz_points.size(); i++)
temp = temp + xyz_points[i];
m_center = (temp)*( 1.0f / xyz_points.size());
}
const std::vector<Vec3> NavPoly::getVertices()
{
std::vector<Vec3> points;
for(unsigned int i=0; i<m_vertices.size(); i++)
points.push_back(NavMesh::get()->getVertex(m_vertices[i]));
return points;
}
bool NavPoly::pointInPoly(const Vec3& p) const
{
std::vector<Vec3> points;
for(unsigned int i=0; i<m_vertices.size(); i++)
points.push_back(NavMesh::get()->getVertex(m_vertices[i]));
// The point is on which side of the first edge
float side = p.sideOfLine2D(points[0],points[1]);
// The point is inside the polygon if it is on the same side for all edges
for(unsigned int i=1; i<points.size(); i++)
{
// If it is on different side then product is < 0 , return false
if(p.sideOfLine2D(points[i % points.size()],
points[(i+1)% points.size()]) * side < 0)
return false;
}
return true;
}
// ----------------------------------------------------------------------------
const Vec3& NavPoly::operator[](int i) const
{
return NavMesh::get()->getVertex(m_vertices[i]);
}

72
src/tracks/nav_poly.hpp Normal file
View File

@ -0,0 +1,72 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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.
#ifndef HEADER_NAV_POLY_HPP
#define HEADER_NAV_POLY_HPP
#include <vector>
#include <string>
#include <SColor.h>
#include "utils/vec3.hpp"
/**
* \ingroup tracks
*/
class NavPoly
{
private:
/** Holds the index of vertices for a polygon **/
std::vector<int> m_vertices;
/** Center of this polygon. **/
Vec3 m_center;
/** Holds the index of adjacent polyogns **/
std::vector<int> m_adjacents;
public:
NavPoly(const std::vector<int> &polygonVertIndices,
const std::vector<int> &adjacentPolygonIndices);
// ------------------------------------------------------------------------
/** Returns the center point of a polygon. */
const Vec3& getCenter() const {return m_center;}
// ------------------------------------------------------------------------
/** Returns the adjacent polygons of a polygon. */
const std::vector<int>& getAdjacents() const {return m_adjacents;}
// ------------------------------------------------------------------------
/** Returns the vertices(Vec3) of this polygon. */
const std::vector<Vec3> getVertices();
// ------------------------------------------------------------------------
/** Returns the indices of the vertices of this polygon */
const std::vector<int> getVerticesIndex() const {return m_vertices;}
// ------------------------------------------------------------------------
/** Returns true if a given point lies in this polygon. */
bool pointInPoly(const Vec3& p) const;
const Vec3& operator[](int i) const ;
}; // class NavPoly
#endif

133
src/tracks/navmesh.cpp Normal file
View File

@ -0,0 +1,133 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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.
#include "tracks/navmesh.hpp"
#include "tracks/nav_poly.hpp"
#include <algorithm>
#include <S3DVertex.h>
#include <triangle3d.h>
#include "LinearMath/btTransform.h"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "utils/string_utils.hpp"
NavMesh *NavMesh::m_nav_mesh = NULL;
/** Constructor, loads the mesh information from a given set of polygons
* from a navmesh.xml file.
* \param filename Name of the file containing all polygons
*/
NavMesh::NavMesh(const std::string &filename)
{
m_n_verts=0;
m_n_polys=0;
XMLNode *xml = file_manager->createXMLTree(filename);
if(!xml || xml->getName()!="navmesh")
{
Log::error("NavMesh", "NavMesh '%s' not found. \n", filename.c_str());
return;
}
// Assigning m_nav_mesh here because constructing NavPoly requires m_nav_mesh to be defined
m_nav_mesh = this;
for(unsigned int i=0; i<xml->getNumNodes(); i++)
{
const XMLNode *xml_node = xml->getNode(i);
if(xml_node->getName()=="vertices")
{
for(unsigned int i=0; i<xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if(!(xml_node_node->getName()=="vertex"))
{
Log::error("NavMesh", "Unsupported type '%s' found in '%s' - ignored. \n",
xml_node_node->getName().c_str(),filename.c_str());
continue;
}
//Reading vertices
Vec3 p;
readVertex(xml_node_node, &p);
m_n_verts++;
m_verts.push_back(p);
}
}
if(xml_node->getName()=="faces")
{
for(unsigned int i=0; i<xml_node->getNumNodes(); i++)
{
const XMLNode *xml_node_node = xml_node->getNode(i);
if(xml_node_node->getName()!="face")
{
Log::error("NavMesh", "Unsupported type '%s' found in '%s' - ignored. \n",
xml_node_node->getName().c_str(),filename.c_str());
continue;
}
//Reading faces/polys
std::vector<int> polygonVertIndices;
std::vector<int> adjacentPolygonIndices;
xml_node_node->get("indices", &polygonVertIndices);
xml_node_node->get("adjacents", &adjacentPolygonIndices);
NavPoly *np = new NavPoly(polygonVertIndices, adjacentPolygonIndices);
m_polys.push_back(*np);
m_n_polys++;
}
}
if(xml_node->getName()=="MaxVertsPerPoly")
{
xml_node->get("nvp", &m_nvp);
}
//delete xml_node;
}
delete xml;
} // NavMesh
// ----------------------------------------------------------------------------
NavMesh::~NavMesh()
{
} // ~NavMesh
// ----------------------------------------------------------------------------
/** Reads the vertex information from an XMLNode */
void NavMesh::readVertex(const XMLNode *xml, Vec3* result) const
{
float x, y, z;
xml->get("x", &x);
xml->get("y", &y);
xml->get("z", &z);
Vec3 temp(x, y, z);
*result = temp;
} // readVertex

150
src/tracks/navmesh.hpp Normal file
View File

@ -0,0 +1,150 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009 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, B
#ifndef HEADER_NAVMESH_HPP
#define HEADER_NAVMESH_HPP
#include "tracks/nav_poly.hpp"
#include <vector>
#include <string>
#include <set>
#include "utils/vec3.hpp"
namespace irr
{
namespace video { struct S3DVertex; }
}
using namespace irr;
class btTransform;
class XMLNode;
/**
* \ingroup tracks
*
* \brief This class stores a set of navigatoin polygons. It uses a
* 'simplified singleton' design pattern: it has a static create function
* to create exactly one instance, a destroy function, and a get function
* (that does not have the side effect of the 'normal singleton' design
* pattern to create an instance). Besides saving on the if statement in get(),
* this is necessary since certain race modes might not have a navigaton
* mesh at all (e.g. race mode). So get() returns NULL in this case, and
* this is tested where necessary.
\ingroup tracks
*/
class NavMesh
{
private:
static NavMesh *m_nav_mesh;
/** The actual set of nav polys that constitute the nav mesh */
std::vector<NavPoly> m_polys;
/** The set of vertices that are part of this nav mesh*/
std::vector< Vec3 > m_verts;
/** Number of vertices */
unsigned int m_n_verts;
/** Number of polygons */
unsigned int m_n_polys;
/** Maximum vertices per polygon */
unsigned int m_nvp;
void readVertex(const XMLNode *xml, Vec3* result) const;
//void readFace(const XMLNode *xml, Vec3* result) const;
NavMesh(const std::string &filename);
~NavMesh();
public:
/** Creates a NavMesh instance. */
static void create(const std::string &filename)
{
assert(m_nav_mesh==NULL);
// m_nav_mesh assigned in the constructor because it needs to defined
// for NavPoly which is constructed in NavMesh()
new NavMesh(filename);
}
/** Cleans up the nav mesh. It is possible that this function is called
* even if no instance exists (e.g. in race). So it is not an
* error if there is no instance. */
static void destroy()
{
if(m_nav_mesh)
{
delete m_nav_mesh;
m_nav_mesh = NULL;
}
}
/** Returns the one instance of this object. It is possible that there
* is no instance created (e.g. in normal race, since it doesn't have
* a nav mesh), so we don't assert that an instance exist, and we
* also don't create one if it doesn't exists. */
static NavMesh *get() { return m_nav_mesh; }
/** Returns a const reference to a NavPoly */
const NavPoly& getNavPoly(int n) const
{ return m_polys[n]; }
/** Returns a const reference to a vertex(Vec3) */
const Vec3& getVertex(int n) const
{ return m_verts[n]; }
/** Returns a const reference to a vector containing all vertices */
const std::vector<Vec3>& getAllVertices() const
{ return m_verts; }
/** Returns the total number of polys */
unsigned int getNumberOfPolys() const
{ return m_n_polys; }
/** Returns the total number of vertices */
unsigned int getNumberOfVerts() const
{ return m_n_verts; }
/** Returns maximum vertices per polygon */
unsigned int getMaxVertsPerPoly() const
{ return m_nvp; }
/** Returns the center of a polygon */
const Vec3& getCenterOfPoly(int n) const
{return m_polys[n].getCenter();}
/** Returns a const referece to a vector containing the indices
* of polygons adjacent to a given polygon */
const std::vector<int>& getAdjacentPolys(int n) const
{return m_polys[n].getAdjacents();}
/** Returns a const reference to a vector containing the vertices
* of a given polygon. */
const std::vector<Vec3> getVertsOfPoly(int n)
{return m_polys[n].getVertices();}
};
#endif

View File

@ -54,6 +54,7 @@
#include "physics/triangle_mesh.hpp"
#include "race/race_manager.hpp"
#include "tracks/bezier_curve.hpp"
#include "tracks/battle_graph.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/model_definition_loader.hpp"
#include "tracks/track_manager.hpp"
@ -274,6 +275,7 @@ void Track::reset()
void Track::cleanup()
{
QuadGraph::destroy();
BattleGraph::destroy();
ItemManager::destroy();
VAOManager::kill();
@ -706,8 +708,14 @@ void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
}
}
} // loadQuadGraph
// -----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
/** Loads the polygon graph for battle, i.e. the definition of all polys, and the way
* they are connected to each other. Input file name is hardcoded for now
*/
void Track::loadBattleGraph()
{
BattleGraph::create(m_root+"navmesh.xml");
}// -----------------------------------------------------------------------------
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
{
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
@ -1587,9 +1595,10 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
// the information about the size of the texture to render the mini
// map to.
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
ItemManager::create();
// Set the default start positions. Node that later the default
// positions can still be overwritten.
float forwards_distance = 1.5f;
@ -1811,6 +1820,16 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
delete root;
// ItemManager assumes the existence of a QuadGraph, that is why the
// quad graph is loaded before ItemManager::create(). This is undesirable
// but requires signifcant code overhaul to fix. The new battle graph
// performs its own computatoins separate from ItemManager. But
// Battle Graph needs ItemManager to be created, and all items to be
// added to ItemManager. Loading battle graph here is therefore a workaround
// to the main problem.
if (m_is_arena && !m_is_soccer && !m_is_cutscene) loadBattleGraph();
if (UserConfigParams::m_track_debug &&
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
!m_is_cutscene)
@ -1818,6 +1837,12 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
QuadGraph::get()->createDebugMesh();
}
if (UserConfigParams::m_track_debug &&
race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
!m_is_cutscene)
BattleGraph::get()->createDebugMesh();
// Only print warning if not in battle mode, since battle tracks don't have
// any quads or check lines.
if (CheckManager::get()->getCheckStructureCount()==0 &&
@ -1851,6 +1876,8 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
}
irr_driver->unsetTextureErrorMessage();
} // loadTrackModel
//-----------------------------------------------------------------------------

View File

@ -43,6 +43,7 @@ class ModelDefinitionLoader;
#include "items/item.hpp"
#include "scriptengine/script_engine.hpp"
#include "tracks/quad_graph.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/aligned_array.hpp"
#include "utils/translation.hpp"
#include "utils/vec3.hpp"
@ -376,6 +377,7 @@ private:
void loadTrackInfo();
void loadQuadGraph(unsigned int mode_id, const bool reverse);
void loadBattleGraph();
void convertTrackToBullet(scene::ISceneNode *node);
bool loadMainTrack(const XMLNode &node);
void createWater(const XMLNode &node);