0ad/source/simulation/PathfindSparse.cpp
janwas 73683b6109 # SwEng
. the massive renaming undertaking: camelCase functions -> PascalCase.
. add some cppdoc.
. minor additional renaming improvements: e.g. GetIsClosed -> IsClosed
. in entity code, replace constructs like "pvec = new vector; return
pvec; use *pvec; delete pvec" with a simple stack variable passed as
output parameter (avoid unnecessary dynamic allocs)
. timer: simpler handling of raw ticks vs normal timer (less #if)

This was SVN commit r5017.
2007-05-02 12:07:08 +00:00

264 lines
7.5 KiB
C++

#include "precompiled.h"
#include "PathfindSparse.h"
#include "graphics/Terrain.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "Entity.h"
int SPF_RECURSION_DEPTH = 10;
SparsePathTree::SparsePathTree( const CVector2D& _from, const CVector2D& _to, HEntity _entity, CBoundingObject* _destinationCollisionObject, int _recursionDepth )
{
from = _from; to = _to;
recursionDepth = _recursionDepth;
entity = _entity; destinationCollisionObject = _destinationCollisionObject;
leftPre = NULL; leftPost = NULL;
rightPre = NULL; rightPost = NULL;
type = SPF_OPEN_UNVISITED;
leftImpossible = false; rightImpossible = false;
nextSubtree = 0;
}
SparsePathTree::~SparsePathTree()
{
if( leftPre ) delete( leftPre );
if( leftPost ) delete( leftPost );
if( rightPre ) delete( rightPre );
if( rightPost ) delete( rightPost );
}
bool SparsePathTree::slice()
{
CTerrain *pTerrain = g_Game->GetWorld()->GetTerrain();
if( type == SPF_OPEN_UNVISITED )
{
if( !recursionDepth )
{
// Too deep.
type = SPF_IMPOSSIBLE;
return( true );
}
rayIntersectionResults r;
CVector2D forward = to - from;
float len = forward.Length();
if( len == 0.0f )
{
// Too weird. (Heavy traffic, obstacles in positions leading to this degenerate state.
type = SPF_IMPOSSIBLE;
return( true );
}
forward /= len;
CVector2D v_right = CVector2D( forward.y, -forward.x );
if( !GetRayIntersection( from, forward, v_right, len, entity->m_bounds->m_radius * 1.1f, destinationCollisionObject, &r ) )
{
type = SPF_CLOSED_DIRECT;
return( true );
}
float turningRadius = ( entity->m_bounds->m_radius + r.boundingObject->m_radius ) * 1.1f;
if( entity->m_turningRadius > turningRadius ) turningRadius = entity->m_turningRadius;
// Too close, an impossible turn
if( r.distance < turningRadius )
{
// Too close to make a proper turn; try dodging immediately a long way to the left or right.
left = from - v_right * r.boundingObject->m_radius * 2.5f;
right = from + v_right * r.boundingObject->m_radius * 2.5f;
}
else if( r.distance > ( len - turningRadius ) )
{
// Again, too close to avoid it properly. Try approaching the goal from the left or right.
left = to - v_right * r.boundingObject->m_radius * 2.5f;
right = to + v_right * r.boundingObject->m_radius * 2.5f;
}
else
{
// Dodge to the left or right of the obstacle.
// A distance of offsetDistance is sufficient to guarantee we'll make the turn.
CVector2D delta = r.position - from;
float length = delta.Length();
float offsetDistance = ( turningRadius * length / sqrt( length * length - turningRadius * turningRadius ) );
left = r.position - v_right * offsetDistance;
right = r.position + v_right * offsetDistance;
}
favourLeft = false;
if( r.closestApproach < 0 )
favourLeft = true;
// First we path to the left...
leftPre = new SparsePathTree( from, left, entity, destinationCollisionObject, recursionDepth - 1 );
leftPost = new SparsePathTree( left, to, entity, destinationCollisionObject, recursionDepth - 1 );
// Then we path to the right...
rightPre = new SparsePathTree( from, right, entity, destinationCollisionObject, recursionDepth - 1 );
rightPost = new SparsePathTree( right, to, entity, destinationCollisionObject, recursionDepth - 1 );
// If anybody reaches this point and is thinking:
//
// "Let's Do The Time-Warp Agaaaain!"
//
// Let me know.
// Check that the subwaypoints are on the map...
if( !pTerrain->IsOnMap( left ) )
{
// Shut that path down
leftPre->type = SPF_IMPOSSIBLE;
leftPost->type = SPF_IMPOSSIBLE;
}
if( !pTerrain->IsOnMap( right ) )
{
// Shut that path down
rightPre->type = SPF_IMPOSSIBLE;
rightPost->type = SPF_IMPOSSIBLE;
}
if( ( leftPre->type == SPF_IMPOSSIBLE ) && ( rightPre->type == SPF_IMPOSSIBLE ) )
{
// It's unlikely, but I suppose it /might/ happen
type = SPF_IMPOSSIBLE;
return( true );
}
type = SPF_OPEN_PROCESSING;
return( true );
}
else /* type == SPF_OPEN_PROCESSING */
{
bool done = false;
while( !done )
{
if( subtrees[nextSubtree]->type & SPF_OPEN )
if( subtrees[nextSubtree]->slice() )
done = true;
nextSubtree++;
nextSubtree %= 4;
}
if( ( leftPre->type == SPF_IMPOSSIBLE ) || ( leftPost->type == SPF_IMPOSSIBLE ) )
leftImpossible = true;
if( ( rightPre->type == SPF_IMPOSSIBLE ) || ( rightPost->type == SPF_IMPOSSIBLE ) )
rightImpossible = true;
if( leftImpossible && rightImpossible )
{
type = SPF_IMPOSSIBLE;
return( done );
}
if( ( ( leftPre->type & SPF_SOLVED ) && ( leftPost->type & SPF_SOLVED ) ) ||
( ( rightPre->type & SPF_SOLVED ) && ( rightPost->type & SPF_SOLVED ) ) )
{
type = SPF_CLOSED_WAYPOINTED;
return( done );
}
return( done );
}
}
void SparsePathTree::pushResults( std::vector<CVector2D>& nodelist )
{
debug_assert( type & SPF_SOLVED );
if( type == SPF_CLOSED_DIRECT )
{
nodelist.push_back( to );
}
else /* type == SPF_CLOSED_WAYPOINTED */
{
leftImpossible = !( ( leftPre->type & SPF_SOLVED ) && ( leftPost->type & SPF_SOLVED ) );
rightImpossible = !( ( rightPre->type & SPF_SOLVED ) && ( rightPost->type & SPF_SOLVED ) );
if( !leftImpossible && ( favourLeft || rightImpossible ) )
{
leftPost->pushResults( nodelist );
leftPre->pushResults( nodelist );
}
else
{
debug_assert( !rightImpossible );
rightPost->pushResults( nodelist );
rightPre->pushResults( nodelist );
}
}
}
void NodePostProcess( HEntity entity, std::vector<CVector2D>& nodelist )
{
std::vector<CVector2D>::iterator it;
CVector2D next = nodelist.front();
CEntityOrder node;
node.m_type = CEntityOrder::ORDER_PATH_END_MARKER;
entity->m_orderQueue.push_front( node );
node.m_type = CEntityOrder::ORDER_GOTO_SMOOTHED;
node.m_target_location = next;
entity->m_orderQueue.push_front( node );
for( it = nodelist.begin() + 1; it != nodelist.end(); it++ )
{
if( ( it + 1 ) == nodelist.end() ) break;
CVector2D current = *it;
CVector2D previous = *( it + 1 );
CVector2D u = current - previous;
CVector2D v = next - current;
u = u.Normalize();
v = v.Normalize();
CVector2D ubar = u.beta();
CVector2D vbar = v.beta();
float alpha = entity->m_turningRadius * ( ubar - vbar ).Length() / ( u + v ).Length();
node.m_target_location = current - u * alpha;
entity->m_orderQueue.push_front( node );
next = current;
}
// If we try to apply turning constraints to getting onto this path, there's a reasonable
// risk the entity will deviate so far from the first path segment that the path becomes
// unwalkable for it.
entity->m_orderQueue.front().m_type = CEntityOrder::ORDER_GOTO_NOPATHING;
}
void PathSparse( HEntity entity, CVector2D destination )
{
std::vector<CVector2D> pathnodes;
CVector2D source( entity->m_position.X, entity->m_position.Z );
// Sanity check:
if( source.Length() < 0.01f ) return;
SparsePathTree sparseEngine( source, destination, entity, GetContainingObject( destination ), SPF_RECURSION_DEPTH );
while( sparseEngine.type & SparsePathTree::SPF_OPEN ) sparseEngine.slice();
// debug_assert( sparseEngine.type & SparsePathTree::SPF_SOLVED ); // Shouldn't be any impossible cases yet.
if( sparseEngine.type & SparsePathTree::SPF_SOLVED )
{
sparseEngine.pushResults( pathnodes );
pathnodes.push_back( source );
NodePostProcess( entity, pathnodes );
}
else
{
// Try a straight line. All we can do, really.
CEntityOrder direct;
direct.m_type = CEntityOrder::ORDER_GOTO_NOPATHING;
direct.m_target_location = destination;
entity->m_orderQueue.push_front( direct );
}
}