stk-code_catmod/src/tracks/drive_graph.cpp
2021-06-22 12:16:54 +02:00

761 lines
30 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2009-2015 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/drive_graph.hpp"
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "io/xml_node.hpp"
#include "main_loop.hpp"
#include "modes/world.hpp"
#include "race/race_manager.hpp"
#include "tracks/check_lap.hpp"
#include "tracks/check_line.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/drive_node.hpp"
#include "tracks/track.hpp"
#include "utils/string_utils.hpp"
// ----------------------------------------------------------------------------
/** Constructor, loads the graph information for a given set of quads
* from a graph file.
* \param quad_file_name Name of the file of all quads
* \param graph_file_name Name of the file describing the actual graph
*/
DriveGraph::DriveGraph(const std::string &quad_file_name,
const std::string &graph_file_name,
const bool reverse) : m_reverse(reverse)
{
m_lap_length = 0;
m_quad_filename = quad_file_name;
Graph::setGraph(this);
load(quad_file_name, graph_file_name);
} // DriveGraph
// ----------------------------------------------------------------------------
void DriveGraph::addSuccessor(unsigned int from, unsigned int to)
{
if(m_reverse)
getNode(to)->addSuccessor(from);
else
getNode(from)->addSuccessor(to);
} // addSuccessor
// ----------------------------------------------------------------------------
/** This function interprets a point specification as an attribute in the
xml quad file. It understands two different specifications:
p1="n:p" : get point p from square n (n, p integers)
p1="p1,p2,p3" : make a 3d point out of these 3 floating point values
*/
void DriveGraph::getPoint(const XMLNode *xml,
const std::string &attribute_name,
Vec3* result) const
{
std::string s;
xml->get(attribute_name, &s);
int pos=(int)s.find_first_of(":");
if(pos>0) // n:p specification
{
std::vector<std::string> l = StringUtils::split(s, ':');
int n=atoi(l[0].c_str());
int p=atoi(l[1].c_str());
*result=(*m_all_nodes[n])[p];
}
else
{
xml->get(attribute_name, result);
}
} // getPoint
// ----------------------------------------------------------------------------
/** Loads a drive graph from a file.
* \param filename Name of the quad file to load.
* \param filename Name of the graph file to load.
*/
void DriveGraph::load(const std::string &quad_file_name,
const std::string &filename)
{
XMLNode *quad = file_manager->createXMLTree(quad_file_name);
if (!quad || quad->getName() != "quads")
{
Log::error("DriveGraph : Quad xml '%s' not found.", filename.c_str());
delete quad;
return;
}
float min_height_testing = Graph::MIN_HEIGHT_TESTING;
float max_height_testing = Graph::MAX_HEIGHT_TESTING;
// Each quad is part of the graph exactly once now.
for (unsigned int i = 0; i < quad->getNumNodes(); i++)
{
main_loop->renderGUI(3331, i, quad->getNumNodes());
const XMLNode *xml_node = quad->getNode(i);
if (!(xml_node->getName() == "quad" || xml_node->getName() == "height-testing"))
{
Log::warn("DriveGraph: Unsupported node type '%s' found in '%s' - ignored.",
xml_node->getName().c_str(), filename.c_str());
continue;
}
if (xml_node->getName() == "height-testing")
{
xml_node->get("min", &min_height_testing);
xml_node->get("max", &max_height_testing);
continue;
}
// Note that it's not easy to do the reading of the parameters here
// in quad, since the specification in the xml can contain references
// to previous points. E.g.:
// <quad p0="40:3" p1="40:2" p2="25.396030 0.770338 64.796539" ...
Vec3 p0, p1, p2, p3;
getPoint(xml_node, "p0", &p0);
getPoint(xml_node, "p1", &p1);
getPoint(xml_node, "p2", &p2);
getPoint(xml_node, "p3", &p3);
bool invisible = false;
xml_node->get("invisible", &invisible);
bool ai_ignore = false;
xml_node->get("ai-ignore", &ai_ignore);
bool ignored = false;
std::string direction;
xml_node->get("direction", &direction);
if (direction == "forward" && RaceManager::get()->getReverseTrack())
{
ignored = true;
invisible = true;
ai_ignore = true;
}
else if (direction == "reverse" && !RaceManager::get()->getReverseTrack())
{
ignored = true;
invisible = true;
ai_ignore = true;
}
createQuad(p0, p1, p2, p3, (unsigned int)m_all_nodes.size(), invisible, ai_ignore,
false/*is_arena*/, ignored);
}
for (unsigned i = 0; i < m_all_nodes.size(); i++)
{
m_all_nodes[i]->setHeightTesting(min_height_testing,
max_height_testing);
}
delete quad;
const XMLNode *xml = file_manager->createXMLTree(filename);
if(!xml)
{
// No graph file exist, assume a default loop X -> X+1
// Set the default loop:
setDefaultSuccessors();
computeDirectionData();
if (m_all_nodes.size() > 0)
{
m_lap_length = getNode((int)m_all_nodes.size()-1)->getDistanceFromStart()
+ getNode((int)m_all_nodes.size()-1)->getDistanceToSuccessor(0);
}
else
{
Log::error("DriveGraph", "No node in driveline graph.");
m_lap_length = 10.0f;
}
return;
}
// The graph file exist, so read it in. The graph file must first contain
// the node definitions, before the edges can be set.
for(unsigned int node_index=0; node_index<xml->getNumNodes(); node_index++)
{
main_loop->renderGUI(3333, node_index, xml->getNumNodes());
const XMLNode *xml_node = xml->getNode(node_index);
// Load the definition of edges between the graph nodes:
// -----------------------------------------------------
if (xml_node->getName() == "node-list")
{
// Each quad is part of the graph exactly once now.
unsigned int to = 0;
xml_node->get("to-quad", &to);
assert(to + 1 == m_all_nodes.size());
continue;
}
else if(xml_node->getName()=="edge-loop")
{
// A closed loop:
unsigned int from, to;
xml_node->get("from", &from);
xml_node->get("to", &to);
for(unsigned int i=from; i<=to; i++)
{
assert(i!=to ? i+1 : from <m_all_nodes.size());
addSuccessor(i,(i!=to ? i+1 : from));
//~ m_all_nodes[i]->addSuccessor(i!=to ? i+1 : from);
}
}
else if(xml_node->getName()=="edge-line")
{
// A line:
unsigned int from, to;
xml_node->get("from", &from);
xml_node->get("to", &to);
for(unsigned int i=from; i<to; i++)
{
addSuccessor(i,i+1);
//~ m_all_nodes[i]->addSuccessor(i+1);
}
}
else if(xml_node->getName()=="edge")
{
// Adds a single edge to the graph:
unsigned int from, to;
xml_node->get("from", &from);
xml_node->get("to", &to);
assert(to<m_all_nodes.size());
addSuccessor(from,to);
//~ m_all_nodes[from]->addSuccessor(to);
} // edge
else
{
Log::error("DriveGraph", "Incorrect specification in '%s': '%s' ignored.",
filename.c_str(), xml_node->getName().c_str());
continue;
} // incorrect specification
}
delete xml;
setDefaultSuccessors();
computeDistanceFromStart(getStartNode(), 0.0f);
computeDirectionData();
// Define the track length as the maximum at the end of a quad
// (i.e. distance_from_start + length till successor 0).
m_lap_length = -1;
for(unsigned int i=0; i<m_all_nodes.size(); i++)
{
float l = getNode(i)->getDistanceFromStart()
+ getNode(i)->getDistanceToSuccessor(0);
if(l > m_lap_length)
m_lap_length = l;
}
loadBoundingBoxNodes();
} // load
// ----------------------------------------------------------------------------
/** Returns the index of the first graph node (i.e. the graph node which
* will trigger a new lap when a kart first enters it). This is always
* 0 for normal direction (this is guaranteed by the track exporter),
* but in reverse mode (where node 0 is actually the end of the track)
* this is 0's successor.
*/
unsigned int DriveGraph::getStartNode() const
{
return m_reverse ? getNode(0)->getSuccessor(0)
: 0;
} // getStartNode
// ----------------------------------------------------------------------------
/** Sets the checkline requirements for all nodes in the graph.
*/
void DriveGraph::computeChecklineRequirements()
{
computeChecklineRequirements(getNode(0),
Track::getCurrentTrack()->getCheckManager()->getLapLineIndex());
} // computeChecklineRequirements
// ----------------------------------------------------------------------------
/** Finds which checklines must be visited before driving on this quad
* (useful for rescue)
*/
void DriveGraph::computeChecklineRequirements(DriveNode* node,
int latest_checkline)
{
for (unsigned int n=0; n<node->getNumberOfSuccessors(); n++)
{
const int succ_id = node->getSuccessor(n);
// warp-around
if (succ_id == 0) break;
DriveNode* succ = getNode(succ_id);
int new_latest_checkline =
Track::getCurrentTrack()->getCheckManager()->getChecklineTriggering(node->getCenter(),
succ->getCenter() );
if(new_latest_checkline==-1)
new_latest_checkline = latest_checkline;
/*
printf("Quad %i : checkline %i\n", succ_id, new_latest_checkline);
printf("Quad %i :\n", succ_id);
for (std::set<int>::iterator it = these_checklines.begin();it != these_checklines.end(); it++)
{
printf(" Depends on checkline %i\n", *it);
}
*/
bool doRecursion = true;
if (new_latest_checkline != -1)
{
// If we are about to add a 'new_latest_checkline' that has already been added,
// we won't add new information and don't need to recurse.
// This will greatly speed up computeChecklineRequirements for tracks with a higher number
// of alternative drive lines and will also avoid adding the same value more than once.
const std::vector<int>& checkline_requirements = succ->getChecklineRequirements();
for (unsigned int i=0; i<checkline_requirements.size(); i++)
{
if (checkline_requirements[i] == new_latest_checkline)
{
doRecursion = false;
break;
}
}
if (doRecursion) // we haven't been here, so add it
succ->setChecklineRequirements(new_latest_checkline);
}
if (doRecursion)
computeChecklineRequirements(succ, new_latest_checkline);
}
} // computeChecklineRequirements
// ----------------------------------------------------------------------------
/** This function defines the "path-to-nodes" for each graph node that has
* more than one successor. The path-to-nodes indicates which successor to
* use to reach a certain node. This is e.g. used for the rubber ball to
* determine which path it is going to use to reach its target (this allows
* the ball to hit a target that is on a shortcut). The algorithm for the
* path computation favours the use of successor 0, i.e. it will if possible
* only use main driveline paths, not a shortcut (even though a shortcut
* could result in a faster way to the target) - but since shotcuts can
* potentially be hidden they should not be used (unless necessary).
* Only graph nodes with more than one successor have this data structure
* (since on other graph nodes only one path can be used anyway, this
* saves some memory).
*/
void DriveGraph::setupPaths()
{
for(unsigned int i=0; i<getNumNodes(); i++)
{
getNode(i)->setupPathsToNode();
}
} // setupPaths
// -----------------------------------------------------------------------------
/** This function sets a default successor for all graph nodes that currently
* don't have a successor defined. The default successor of node X is X+1.
*/
void DriveGraph::setDefaultSuccessors()
{
for(unsigned int i=0; i<m_all_nodes.size(); i++) {
if(getNode(i)->getNumberOfSuccessors()==0) {
addSuccessor(i,i+1>=m_all_nodes.size() ? 0 : i+1);
//~ getNode(i)->addSuccessor(i+1>=m_all_nodes.size() ? 0 : i+1);
} // if size==0
} // for i<m_allNodes.size()
} // setDefaultSuccessors
// -----------------------------------------------------------------------------
/** Sets all start positions depending on the drive graph. The number of
* entries needed is defined by the size of the start_transform (though all
* entries will be overwritten).
* E.g. the karts will be placed as:
* 1 \
* 2 +-- row with three karts, each kart is 'sidewards_distance'
* 3 / to the right of the previous kart, and
* 4 'forwards_distance' behind the previous kart.
* 5 The next row starts again with the kart being
* 6 'forwards_distance' behind the end of the previous row.
* etc.
* \param start_transforms A vector sized to the needed number of start
* positions. The values will all be overwritten with the
* default start positions.
* \param karts_per_row How many karts to place in each row.
* \param forwards_distance Distance in forward (Z) direction between
* each kart.
* \param sidewards_distance Distance in sidewards (X) direction between
* karts.
*/
void DriveGraph::setDefaultStartPositions(AlignedArray<btTransform>
*start_transforms,
unsigned int karts_per_row,
float forwards_distance,
float sidewards_distance,
float upwards_distance) const
{
// We start just before the start node (which will trigger lap
// counting when reached). The first predecessor is the one on
// the main driveline.
int current_node = getNode(getStartNode())->getPredecessor(0);
float distance_from_start = 0.75f+forwards_distance;
// Maximum distance to left (or right) of centre line
const float max_x_dist = 0.5f*(karts_per_row-0.5f)*sidewards_distance;
// X position relative to the centre line
float x_pos = -max_x_dist + sidewards_distance*0.5f;
unsigned int row_number = 0;
for(unsigned int i=0; i<(unsigned int)start_transforms->size(); i++)
{
if (current_node == -1)
{
(*start_transforms)[i].setOrigin(Vec3(0,0,0));
(*start_transforms)[i].setRotation(btQuaternion(btVector3(0, 1, 0),
0));
}
else
{
// First find on which segment we have to start
while(distance_from_start > getNode(current_node)->getNodeLength())
{
distance_from_start -= getNode(current_node)->getNodeLength();
// Only follow the main driveline, i.e. first predecessor
current_node = getNode(current_node)->getPredecessor(0);
}
const DriveNode* dn = getNode(current_node);
Vec3 center_line = dn->getLowerCenter() - dn->getUpperCenter();
center_line.normalize();
Vec3 horizontal_line = (*dn)[2] - (*dn)[3];
horizontal_line.normalize();
Vec3 start = dn->getUpperCenter()
+ center_line * distance_from_start
+ horizontal_line * x_pos;
// Add a certain epsilon to the height in case that the
// drivelines are beneath the track.
(*start_transforms)[i].setOrigin(start+Vec3(0,upwards_distance,0));
(*start_transforms)[i].setRotation(
btQuaternion(btVector3(0, 1, 0),
dn->getAngleToSuccessor(0)));
if(x_pos >= max_x_dist-sidewards_distance*0.5f)
{
x_pos = -max_x_dist;
// Every 2nd row will be pushed sideways by half the distance
// between karts, so that a kart can drive between the karts in
// the row ahead of it.
row_number ++;
if(row_number % 2 == 0)
x_pos += sidewards_distance*0.5f;
}
else
x_pos += sidewards_distance;
distance_from_start += forwards_distance;
}
} // for i<stk_config->m_max_karts
} // setStartPositions
// -----------------------------------------------------------------------------
/** Returns the list of successors or a node.
* \param node_number The number of the node.
* \param succ A vector of ints to which the successors are added.
* \param for_ai true if only quads accessible by the AI should be returned.
*/
void DriveGraph::getSuccessors(int node_number,
std::vector<unsigned int>& succ,
bool for_ai) const
{
const DriveNode *dn=getNode(node_number);
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
// If getSuccessor is called for the AI, only add
// quads that are meant for the AI to be used.
if(!for_ai || !dn->ignoreSuccessorForAI(i))
succ.push_back(dn->getSuccessor(i));
}
} // getSuccessors
// ----------------------------------------------------------------------------
/** Recursively determines the distance the beginning (lower end) of the quads
* have from the start of the track.
* \param node The node index for which to set the distance from start.
* \param new_distance The new distance for the specified graph node.
*/
void DriveGraph::computeDistanceFromStart(unsigned int node, float new_distance)
{
DriveNode *dn = getNode(node);
float current_distance = dn->getDistanceFromStart();
// If this node already has a distance defined, check if the new distance
// is longer, and if so adjust all following nodes. Without this the
// length of the track (as taken by the distance from start of the last
// node) could be smaller than some of the paths. This can result in
// incorrect results for the arrival time estimation of the AI karts.
// See trac #354 for details.
// Then there is no need to test/adjust any more nodes.
if(current_distance>=0)
{
if(current_distance<new_distance)
{
float delta = new_distance - current_distance;
updateDistancesForAllSuccessors(dn->getIndex(), delta, 0);
}
return;
}
// Otherwise this node has no distance defined yet. Set the new
// distance, and recursively update all following nodes.
dn->setDistanceFromStart(new_distance);
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
DriveNode *dn_next = getNode(dn->getSuccessor(i));
// The start node (only node with distance 0) is reached again,
// recursion can stop now
if(dn_next->getDistanceFromStart()==0)
continue;
computeDistanceFromStart(dn_next->getIndex(),
new_distance + dn->getDistanceToSuccessor(i));
} // for i
} // computeDistanceFromStart
// ----------------------------------------------------------------------------
/** Increases the distance from start for all nodes that are directly or
* indirectly a successor of the given node. This code is used when two
* branches merge together, but since the latest 'fork' indicates a longer
* distance from start.
* \param indx Index of the node for which to increase the distance.
* \param delta Amount by which to increase the distance.
* \param recursive_count Counts how often this function was called
* recursively in order to catch incorrect graphs that contain loops.
*/
void DriveGraph::updateDistancesForAllSuccessors(unsigned int indx, float delta,
unsigned int recursive_count)
{
if(recursive_count>getNumNodes())
{
Log::error("DriveGraph",
"DriveGraph contains a loop (without start node).");
Log::fatal("DriveGraph",
"Fix graph, check for directions of all shortcuts etc.");
}
recursive_count++;
DriveNode* dn = getNode(indx);
dn->setDistanceFromStart(dn->getDistanceFromStart()+delta);
for(unsigned int i=0; i<dn->getNumberOfSuccessors(); i++)
{
DriveNode* dn_next = getNode(dn->getSuccessor(i));
// Stop when we reach the start node, i.e. the only node with a
// distance of 0
if(dn_next->getDistanceFromStart()==0)
continue;
// Only increase the distance from start of a successor node, if
// this successor has a distance from start that is smaller then
// the increased amount.
if(dn->getDistanceFromStart()+dn->getDistanceToSuccessor(i) >
dn_next->getDistanceFromStart())
{
updateDistancesForAllSuccessors(dn->getSuccessor(i), delta,
recursive_count);
}
}
} // updateDistancesForAllSuccessors
//-----------------------------------------------------------------------------
/** Computes the direction (straight, left, right) of all graph nodes and the
* lastest graph node that is still turning in the given direction. For
* example, if a successor to this graph node is turning left, it will compute
* the last graph node that is still turning left. This data is used by the
* AI to estimate the turn radius.
* At this stage there is one restriction: if a node with more than one
* successor is ahead, only successor 0 is used. That might lead to somewhat
* incorrect results (i.e. the last successor is determined assuming that
* the kart is always using successor 0, while in reality it might follow
* a different successor, resulting in a different turn radius. It is not
* expected that this makes much difference for the AI (since it will update
* its data constantly, i.e. if it takes a different turn, it will be using
* the new data).
*/
void DriveGraph::computeDirectionData()
{
for(unsigned int i=0; i<m_all_nodes.size(); i++)
{
for(unsigned int succ_index=0;
succ_index<getNode(i)->getNumberOfSuccessors();
succ_index++)
{
determineDirection(i, succ_index);
} // for next < getNumberOfSuccessor
} // for i < m_all_nodes.size()
} // computeDirectionData
//-----------------------------------------------------------------------------
/** Adjust the given angle to be in [-PI, PI].
*/
float DriveGraph::normalizeAngle(float f)
{
if(f>M_PI) f -= 2*M_PI;
else if(f<-M_PI) f += 2*M_PI;
return f;
} // normalizeAngle
//-----------------------------------------------------------------------------
/** Determines the direction of the drive graph when driving to the specified
* successor. It also determines the last graph node that is still following
* the given direction. The computed data is saved in the corresponding
* graph node.
* It compares the lines connecting the center point of node n with n+1 and
* the lines connecting n+1 and n+2 (where n is the current node, and +1 etc.
* specifying the successor). Then it keeps on testing the line from n+2 to
* n+3, n+3 to n+4, ... as long as the turn direction is the same. The last
* value that still has the same direction is then set as the last node
* with the same direction in the specified graph node.
* \param current Index of the graph node with which to start ('n' in the
* description above).
* \param succ_index The successor to be followed from the current node.
* If there should be any other branches later, successor
* 0 will always be tetsed.
*/
void DriveGraph::determineDirection(unsigned int current,
unsigned int succ_index)
{
// The maximum angle which is still considered to be straight
const float max_straight_angle=0.1f;
// Compute the angle from n (=current) to n+1 (=next)
float angle_current = getAngleToNext(current, succ_index);
unsigned int next = getNode(current)->getSuccessor(succ_index);
float angle_next = getAngleToNext(next, 0);
float rel_angle = normalizeAngle(angle_next-angle_current);
// Small angles are considered to be straight
if(fabsf(rel_angle)<max_straight_angle)
rel_angle = 0;
next = getNode(next)->getSuccessor(0); // next is now n+2
// If the direction is still the same during a lap the last node
// in the same direction is the previous node;
int max_step = (int)m_all_nodes.size()-1;
while(max_step-- != 0)
{
// Now compute the angle from n+1 (new current) to n+2 (new next)
angle_current = angle_next;
angle_next = getAngleToNext(next, 0);
float new_rel_angle = normalizeAngle(angle_next - angle_current);
if(fabsf(new_rel_angle)<max_straight_angle)
new_rel_angle = 0;
// We have reached a different direction if we go from a non-straight
// section to a straight one, from a straight section to a non-
// straight section, or from a left to a right turn (or vice versa)
if( (rel_angle != 0 && new_rel_angle == 0 ) ||
(rel_angle == 0 && new_rel_angle != 0 ) ||
(rel_angle * new_rel_angle < 0 ) )
break;
rel_angle = new_rel_angle;
next = getNode(next)->getSuccessor(0);
} // while(1)
DriveNode::DirectionType dir =
rel_angle==0 ? DriveNode::DIR_STRAIGHT
: (rel_angle>0) ? DriveNode::DIR_RIGHT
: DriveNode::DIR_LEFT;
getNode(current)->setDirectionData(succ_index, dir, next);
} // determineDirection
//-----------------------------------------------------------------------------
/** This function takes absolute coordinates (coordinates in OpenGL
* space) and transforms them into coordinates based on the track. The y-axis
* of the returned vector is how much of the track the point has gone
* through, the x-axis is on which side of the road it is (relative to a line
* connecting the two center points of a quad). The Y axis is not changed.
* \param dst Returns the results in the X and Z coordinates.
* \param xyz The position of the kart.
* \param sector The graph node the position is on.
*/
void DriveGraph::spatialToTrack(Vec3 *dst, const Vec3& xyz,
const int sector) const
{
if(sector == UNKNOWN_SECTOR )
{
Log::warn("Drive Graph", "UNKNOWN_SECTOR in spatialToTrack().");
return;
}
getNode(sector)->getDistances(xyz, dst);
} // spatialToTrack
//-----------------------------------------------------------------------------
float DriveGraph::getDistanceToNext(int n, int j) const
{
return getNode(n)->getDistanceToSuccessor(j);
} // getDistanceToNext
//-----------------------------------------------------------------------------
float DriveGraph::getAngleToNext(int n, int j) const
{
return getNode(n)->getAngleToSuccessor(j);
} // getAngleToNext
//-----------------------------------------------------------------------------
int DriveGraph::getNumberOfSuccessors(int n) const
{
return getNode(n)->getNumberOfSuccessors();
} // getNumberOfSuccessors
//-----------------------------------------------------------------------------
float DriveGraph::getDistanceFromStart(int j) const
{
return getNode(j)->getDistanceFromStart();
} // getDistanceFromStart
// -----------------------------------------------------------------------------
void DriveGraph::differentNodeColor(int n, video::SColor* c) const
{
if (UserConfigParams::m_track_debug)
{
if (getNode(n)->is3DQuad())
*c = video::SColor(255, 0, 255, 0);
else
*c = video::SColor(255, 255, 255, 0);
}
} // differentNodeColor
// -----------------------------------------------------------------------------
DriveNode* DriveGraph::getNode(unsigned int j) const
{
assert(j < m_all_nodes.size());
DriveNode* n = dynamic_cast<DriveNode*>(m_all_nodes[j]);
assert(n != NULL);
return n;
} // getNode
// -----------------------------------------------------------------------------
bool DriveGraph::hasLapLine() const
{
if (Track::getCurrentTrack()->isCTF() &&
RaceManager::get()->getMinorMode() ==
RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
return false;
return true;
} // hasLapLine