761 lines
30 KiB
C++
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
|