1390 lines
42 KiB
C++
1390 lines
42 KiB
C++
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
// $Logfile:: /EF2/Code/DLLs/game/FollowPath.cpp $
|
||
|
// $Revision:: 47 $
|
||
|
// $Author:: Singlis $
|
||
|
// $Date:: 9/26/03 2:36p $
|
||
|
//
|
||
|
// Copyright (C) 1998 by Ritual Entertainment, Inc.
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// This source may not be distributed and/or modified without
|
||
|
// expressly written permission by Ritual Entertainment, Inc.
|
||
|
//
|
||
|
//
|
||
|
//
|
||
|
// DESCRIPTION:
|
||
|
// Provides base functionality for path following with avoidance
|
||
|
//
|
||
|
|
||
|
#include "_pch_cpp.h"
|
||
|
#include "actor.h"
|
||
|
#include "FollowPath.h"
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: DrawBox
|
||
|
// Class: none
|
||
|
//
|
||
|
// Description: draws a box in the xy plane
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Vector center - the point about which the box is centered
|
||
|
// float width - the width of the box
|
||
|
// float r, g, b - the color values for the box
|
||
|
// float alpha - the amount of alpha applied to the box
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
|
||
|
void DrawBox(const Vector ¢er, const float width, const float r, const float g, const float b, const float alpha )
|
||
|
{
|
||
|
float squareSize=width/2.0f;
|
||
|
Vector topLeftPoint = center + Vector(-squareSize, -squareSize, 0);
|
||
|
Vector topRightPoint = center + Vector(+squareSize, -squareSize, 0);
|
||
|
Vector bottomRightPoint = center + Vector(+squareSize, +squareSize, 0);
|
||
|
Vector bottomLeftPoint = center + Vector(-squareSize, +squareSize, 0);
|
||
|
G_DebugLine( topLeftPoint, topRightPoint, r, g, b, alpha );
|
||
|
G_DebugLine( topRightPoint, bottomRightPoint, r, g, b, alpha );
|
||
|
G_DebugLine( bottomRightPoint, bottomLeftPoint, r, g, b, alpha );
|
||
|
G_DebugLine( bottomLeftPoint, topLeftPoint, r, g, b, alpha );
|
||
|
}
|
||
|
|
||
|
//------------------------- CLASS ------------------------------
|
||
|
//
|
||
|
// Name: FollowNode
|
||
|
// Base Class: Class
|
||
|
//
|
||
|
// Description: FollowNodes are a lightweight class meant to
|
||
|
// store translation information for the FollowNodePath class
|
||
|
//
|
||
|
// Method of Use: FollowNodes should only be created by
|
||
|
// FollowNodePaths but other classes my need access a node's
|
||
|
// translation.
|
||
|
//
|
||
|
//--------------------------------------------------------------
|
||
|
|
||
|
CLASS_DECLARATION( Class, FollowNode, NULL )
|
||
|
{
|
||
|
{ NULL, NULL }
|
||
|
};
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: FollowNode
|
||
|
// Class: FollowNode
|
||
|
//
|
||
|
// Description: default constructor
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
FollowNode::FollowNode ( void ):
|
||
|
_position( 0, 0, 0 ),
|
||
|
_isTemporary( false ),
|
||
|
_isJumpNode( false ),
|
||
|
_jumpAngle( 45.0f )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: FollowNode
|
||
|
// Class: FollowNode
|
||
|
//
|
||
|
// Description: constructor
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Vector position - translation of the FollowNode
|
||
|
// bool isTemporary - flag identifying temporary nodes
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
FollowNode::FollowNode ( const Vector &position, const bool isTemporary, const bool isJumpNode, const float jumpAngle ):
|
||
|
_position( position ),
|
||
|
_isTemporary( isTemporary ),
|
||
|
_isJumpNode( isJumpNode ),
|
||
|
_jumpAngle( jumpAngle )
|
||
|
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//------------------------- CLASS ------------------------------
|
||
|
//
|
||
|
// Name: FollowNodePath
|
||
|
// Base Class: Class
|
||
|
//
|
||
|
// Description: FollowNodePaths are simple paths that support the
|
||
|
// concept of temporary path nodes (allowing temporary insertion
|
||
|
// nodes into the path).
|
||
|
//
|
||
|
// Method of Use: FollowNodePaths should primarily be used by
|
||
|
// FollowPath classes, although other classes may need access to
|
||
|
// most of the members of the class
|
||
|
//
|
||
|
//--------------------------------------------------------------
|
||
|
CLASS_DECLARATION( Class, FollowNodePath, NULL )
|
||
|
{
|
||
|
{ NULL, NULL }
|
||
|
};
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: FollowNodePath
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: default constructor
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
FollowNodePath::FollowNodePath():
|
||
|
_nodes(),
|
||
|
_currentNode(NULL)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: RemoveNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: removes node from path while maintaining
|
||
|
// the correct current node
|
||
|
//
|
||
|
// Parameters: FollowNodePtr node - pointer to the node to
|
||
|
// be removed
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowNodePath::RemoveNode(FollowNodePtr node)
|
||
|
{
|
||
|
if (node == GetCurrentNode())
|
||
|
{
|
||
|
FollowNodePtr newCurrentNode=GetNextNode(GetCurrentNode());
|
||
|
if (newCurrentNode == NULL)
|
||
|
{
|
||
|
newCurrentNode=GetPreviousNode(GetCurrentNode());
|
||
|
}
|
||
|
SetCurrentNode(newCurrentNode);
|
||
|
}
|
||
|
_nodes.RemoveObject(node);
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: InsertNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: inserts a node into the path while maintaining
|
||
|
// the correct current node
|
||
|
//
|
||
|
// Parameters:
|
||
|
// FollowNodePtr node - pointer to the node to
|
||
|
// be inserted
|
||
|
// int index - the index in the list where the
|
||
|
// node is to be inserted
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowNodePath::InsertNode(FollowNodePtr node, const int index)
|
||
|
{
|
||
|
int currentNodeIndex=GetNodeIndex(GetCurrentNode());
|
||
|
_nodes.InsertObjectAt(index, node);
|
||
|
if (currentNodeIndex >= index)
|
||
|
{
|
||
|
SetCurrentNode(GetNodeAt(currentNodeIndex+1));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: GetPreviousNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: returns a pointer to the node preceding the
|
||
|
// one passed to the method
|
||
|
//
|
||
|
// Parameters:
|
||
|
// FollowNodePtr node - node following the node
|
||
|
// to be returned
|
||
|
//
|
||
|
// Returns: the node preceding 'node'
|
||
|
//----------------------------------------------------------------
|
||
|
FollowNodePtr FollowNodePath::GetPreviousNode(const FollowNodePtr node ) const
|
||
|
{
|
||
|
int nodeIndex=GetNodeIndex(node);
|
||
|
if (nodeIndex > 1)
|
||
|
{
|
||
|
return GetNodeAt(nodeIndex - 1);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: GetNextNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: returns a pointer to the node following the
|
||
|
// one passed to the method
|
||
|
//
|
||
|
// Parameters:
|
||
|
// FollowNodePtr node - node preceding the node
|
||
|
// to be returned
|
||
|
//
|
||
|
// Returns: the node following 'node'
|
||
|
//----------------------------------------------------------------
|
||
|
FollowNodePtr FollowNodePath::GetNextNode(const FollowNodePtr node ) const
|
||
|
{
|
||
|
int nodeIndex=GetNodeIndex(node);
|
||
|
if (nodeIndex < NumNodes())
|
||
|
{
|
||
|
return GetNodeAt(nodeIndex + 1);
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: Evaluate
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: ensures that the path maintains the correct
|
||
|
// current node as the actor follows the path
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// Vector goalPosition - the place that the path
|
||
|
// is taking the Actor
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue used to return success
|
||
|
// or reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowNodePath::Evaluate( Actor &self, const Vector &goalPosition )
|
||
|
{
|
||
|
Steering::ReturnValue returnValue = Steering::EVALUATING;
|
||
|
if (
|
||
|
( NumNodes() < 1 ) &&
|
||
|
( SetPath( self, self.origin, goalPosition ) != PATH_CREATION_SUCCESS )
|
||
|
)
|
||
|
{
|
||
|
return Steering::FAILED_NO_PATH;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
returnValue = AdvanceCurrentNode( self );
|
||
|
|
||
|
if (RetreatCurrentNode( self, goalPosition ) == Steering::FAILED_NO_PATH)
|
||
|
{
|
||
|
returnValue = Steering::FAILED_NO_PATH;
|
||
|
}
|
||
|
|
||
|
if ( g_showactorpath->integer )
|
||
|
{
|
||
|
Draw();
|
||
|
}
|
||
|
}
|
||
|
return returnValue;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: SetPath
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: builds a new path for an Actor to follow
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// Vector from - the place that the path starts
|
||
|
// Vector to - the place that the path leads to
|
||
|
//
|
||
|
// Returns: bool that is false if no path is created
|
||
|
//----------------------------------------------------------------
|
||
|
unsigned int const FollowNodePath::SetPath(Actor &self, const Vector &from, const Vector &to)
|
||
|
{
|
||
|
// Find the end node
|
||
|
PathNode *goalNode = thePathManager.NearestNode( to, &self );
|
||
|
|
||
|
if ( !goalNode )
|
||
|
{
|
||
|
return PATH_CREATION_FAILED_END_NODE_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
// Find the start node
|
||
|
PathNode *node = thePathManager.NearestNode( from, &self );
|
||
|
if ( !node || ( goalNode == node ) )
|
||
|
{
|
||
|
return PATH_CREATION_FAILED_START_NODE_NOT_FOUND;
|
||
|
}
|
||
|
|
||
|
// Find the best _path
|
||
|
StandardMovePath find;
|
||
|
find.heuristic.setSize( self.size );
|
||
|
find.heuristic.entnum = self.entnum;
|
||
|
find.heuristic.can_jump = self.CanJump() != 0;
|
||
|
|
||
|
Clear();
|
||
|
|
||
|
Path *newpath = find.FindPath( node, goalNode );
|
||
|
if ( newpath != NULL )
|
||
|
{
|
||
|
BuildFromPathNodes( newpath, self );
|
||
|
delete newpath;
|
||
|
newpath = NULL;
|
||
|
if ( self.GetActorFlag( ACTOR_FLAG_STRICTLY_FOLLOW_PATHS ) )
|
||
|
{
|
||
|
SetCurrentNode( FirstNode() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( !FindNewCurrentNode( self ) )
|
||
|
{
|
||
|
SetCurrentNode( FirstNode() );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetCurrentNode( NULL );
|
||
|
return PATH_CREATION_FAILED_NODES_NOT_CONNECTED;
|
||
|
}
|
||
|
|
||
|
assert ( NumNodes() > 0 );
|
||
|
//AddNode( new FollowNode( to, false ) );
|
||
|
return PATH_CREATION_SUCCESS;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: Clear
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: removes all FollowNodes from the path and
|
||
|
// deletes them
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowNodePath::Clear()
|
||
|
{
|
||
|
for (int i=NumNodes(); i>0; i--)
|
||
|
{
|
||
|
FollowNodePtr node=GetNodeAt(i);
|
||
|
RemoveNode( node );
|
||
|
delete node;
|
||
|
}
|
||
|
_currentNode=NULL;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: Draw
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: draws all nodes, all connections and the goal
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowNodePath::Draw( void ) const
|
||
|
{
|
||
|
if (NumNodes() == 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float squareSize=8.0f;
|
||
|
Vector verticalOffset(0,0,16);
|
||
|
DrawBox(GetNodeAt(1)->GetPosition() + verticalOffset, squareSize , 0, 1, 0, 1);
|
||
|
for (int i=NumNodes(); i>1; i--)
|
||
|
{
|
||
|
if ( GetNodeAt(i)->GetIsTemporary() )
|
||
|
{
|
||
|
DrawBox(GetNodeAt(i)->GetPosition() + verticalOffset, squareSize , 1, 0, 1, 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DrawBox(GetNodeAt(i)->GetPosition() + verticalOffset, squareSize , 0, 1, 0, 1);
|
||
|
}
|
||
|
|
||
|
if (i%2)
|
||
|
{
|
||
|
G_DebugLine( GetNodeAt(i)->GetPosition() + verticalOffset, GetNodeAt(i-1)->GetPosition() + verticalOffset, 0.0f, 1.0f, 0.0f, 1.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
G_DebugLine( GetNodeAt(i)->GetPosition() + verticalOffset, GetNodeAt(i-1)->GetPosition() + verticalOffset, 0.0f, 0.5f, 0.0f, 1.0f );
|
||
|
}
|
||
|
}
|
||
|
if (_currentNode!=NULL)
|
||
|
{
|
||
|
DrawBox(_currentNode->GetPosition() + verticalOffset, squareSize * 2, 1, 1, 1, 1);
|
||
|
}
|
||
|
DrawBox(LastNode()->GetPosition() + verticalOffset, squareSize * 4, 1, 0, 0, 1);
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: BuildFromPathNodes
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: builds a FollowNode path from a PathNode path
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// Vector from - the place that the path starts
|
||
|
// Vector to - the place that the path leads to
|
||
|
//
|
||
|
// Returns: bool that is false if no path is created
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowNodePath::BuildFromPathNodes(Path *path, const Actor &self)
|
||
|
{
|
||
|
assert(path->NumNodes() > 0);
|
||
|
|
||
|
int sourceNodeNumber=1;
|
||
|
int sourceNumNodes=path->NumNodes();
|
||
|
|
||
|
|
||
|
AddNode (new FollowNode(path->GetNode(sourceNodeNumber++)->origin));
|
||
|
PathNodePtr currentPathNode = NULL;
|
||
|
PathNodePtr nextPathNode = NULL;
|
||
|
while(sourceNodeNumber < sourceNumNodes)
|
||
|
{
|
||
|
bool isJumpNode = false;
|
||
|
float jumpAngle = 0.0f;
|
||
|
|
||
|
currentPathNode = path->GetNode(sourceNodeNumber);
|
||
|
|
||
|
if ( sourceNodeNumber < path->NumNodes() && (currentPathNode->nodeflags & AI_JUMP) && currentPathNode->target.length() )
|
||
|
{
|
||
|
nextPathNode = path->GetNode(sourceNodeNumber + 1);
|
||
|
if (currentPathNode->target == nextPathNode->targetname)
|
||
|
{
|
||
|
isJumpNode = true;
|
||
|
jumpAngle = currentPathNode->jumpAngle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AddNode ( new FollowNode( currentPathNode->origin, false, isJumpNode, jumpAngle ) );
|
||
|
sourceNodeNumber++;
|
||
|
}
|
||
|
AddNode (new FollowNode( path->GetNode(sourceNumNodes)->origin));
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: FindNewCurrentNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: sets the current node to the node furthest
|
||
|
// down the path that the Actor can see
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: bool that is true if a new current node is set
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowNodePath::FindNewCurrentNode(const Actor &self)
|
||
|
{
|
||
|
int mask;
|
||
|
int currentNodeIndex = GetNodeIndex( GetCurrentNode() );
|
||
|
for (
|
||
|
int currentTestNodeNumber = NumNodes();
|
||
|
currentTestNodeNumber > currentNodeIndex;
|
||
|
currentTestNodeNumber--
|
||
|
)
|
||
|
{
|
||
|
FollowNodePtr testnode = GetNodeAt(currentTestNodeNumber);
|
||
|
if (currentTestNodeNumber == 1)
|
||
|
{
|
||
|
SetCurrentNode(testnode);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
mask = self.edict->clipmask & ~CONTENTS_BODY;
|
||
|
|
||
|
trace_t trace = self.Trace( self.origin, testnode->GetPosition(), mask, "FollowPath" );
|
||
|
|
||
|
if ( !trace.startsolid && ( trace.fraction == 1.0f ) && self.movementSubsystem->CanWalkTo( testnode->GetPosition() ) )
|
||
|
{
|
||
|
SetCurrentNode(testnode);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: AdvanceCurrentNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: sets the current node to the next node if the
|
||
|
// Actor gets close to the current node
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowNodePath::AdvanceCurrentNode( Actor const &self )
|
||
|
{
|
||
|
if ( GetCurrentNode() != NULL )
|
||
|
{
|
||
|
// Advance current node if possible
|
||
|
FollowNodePtr nextNode = GetNextNode( GetCurrentNode() );
|
||
|
|
||
|
// check if the distance remaining is less than the distance we'll travel
|
||
|
float radius = max( self.total_delta.lengthXY(), 16.0f );
|
||
|
if ( Vector::DistanceXY( self.origin, GetCurrentNode()->GetPosition() ) <= radius )
|
||
|
{
|
||
|
if ( nextNode != NULL )
|
||
|
{
|
||
|
SetCurrentNode ( nextNode );
|
||
|
return Steering::EVALUATING_REACHED_NODE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Clear();
|
||
|
return Steering::SUCCESS;
|
||
|
}
|
||
|
}
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
return Steering::FAILED_NO_PATH;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: RetreatCurrentNode
|
||
|
// Class: FollowNodePath
|
||
|
//
|
||
|
// Description: search backwards from the current node searching
|
||
|
// for a node that is not blocked by the world
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowNodePath::RetreatCurrentNode(Actor &self, const Vector &goalPosition)
|
||
|
{
|
||
|
int mask;
|
||
|
|
||
|
if ( GetCurrentNode() == NULL )
|
||
|
{
|
||
|
return Steering::FAILED_NO_PATH;
|
||
|
}
|
||
|
|
||
|
if ( GetCurrentNodeIndex() == 1 )
|
||
|
{
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
|
||
|
// Build the correct mask (the actor's normal mask without body)
|
||
|
|
||
|
mask = self.edict->clipmask & ~CONTENTS_BODY;
|
||
|
|
||
|
if ( !GetPreviousNode(GetCurrentNode() )->GetIsJumpNode() )
|
||
|
{
|
||
|
//Vector myPosition( self.origin );
|
||
|
trace_t traceToCurrentNode = self.Trace( self.origin, GetCurrentNode()->GetPosition(), mask, "FollowPath" );
|
||
|
|
||
|
if ( traceToCurrentNode.startsolid || ( traceToCurrentNode.fraction < 1.0f ) )
|
||
|
{
|
||
|
gi.WDPrintf( "Couldn't complete path, building new one for %s (%d)\n" , self.TargetName(), self.entnum );
|
||
|
|
||
|
if ( SetPath( self, self.origin, goalPosition ) != PATH_CREATION_SUCCESS )
|
||
|
{
|
||
|
return Steering::FAILED_NO_PATH;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
|
||
|
|
||
|
//------------------------- CLASS ------------------------------
|
||
|
//
|
||
|
// Name: FollowPath
|
||
|
// Base Class: Steering
|
||
|
//
|
||
|
// Description: FollowPath is the base class for and Steering
|
||
|
// classes that need to navigate any significant distance through
|
||
|
// the level.
|
||
|
//
|
||
|
// Method of Use: Never instantiate an object of type FollowPath.
|
||
|
// If the TikiEngine architecture allowed, this cless would be an
|
||
|
// interface class. If you need FollowPath behavior either use an
|
||
|
// existing FollowPath subclass or derive a new class from a
|
||
|
// FollowPath class.
|
||
|
//
|
||
|
//--------------------------------------------------------------
|
||
|
CLASS_DECLARATION( Steering, FollowPath, NULL )
|
||
|
{
|
||
|
{ NULL, NULL }
|
||
|
};
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: FollowPath
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: default constructor
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
FollowPath::FollowPath():
|
||
|
_avoidanceDistance(96.0f),
|
||
|
_jumping( false )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: Begin
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: Initializes variables necessary to Evaluate
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::Begin(Actor &self)
|
||
|
{
|
||
|
_desiredHeading = self.movementSubsystem->getMoveDir().toAngles();
|
||
|
_jumping = false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: SetSteeringForceAndDesiredHeading
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: Sets absolute direction that Actor should turn to
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// EulerAngles steeringForce - deltaRotation that
|
||
|
// Actor should turn
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::SetSteeringForceAndDesiredHeading( Actor &self, const Vector &steeringForce )
|
||
|
{
|
||
|
_desiredHeading = self.movementSubsystem->getMoveDir().toAngles() + steeringForce;
|
||
|
_desiredHeading.EulerNormalize360();
|
||
|
SetSteeringForce( steeringForce );
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: DoneTurning
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: test used to determine if the Actor is facing
|
||
|
// the desired heading
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: bool - true if the Actor is facing the desired
|
||
|
// direction
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::DoneTurning( Actor &self ) const
|
||
|
{
|
||
|
Vector deltaHeading( _desiredHeading - self.movementSubsystem->getMoveDir().toAngles() );
|
||
|
deltaHeading.EulerNormalize();
|
||
|
return fabs( deltaHeading[YAW] ) < 1.0f ;
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: GotoCurrentNode
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: steers the Actor towards the current FollowNode
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowPath::GotoCurrentNode( Actor &self )
|
||
|
{
|
||
|
float traceLength = min(Vector::DistanceXY( self.origin, GetCurrentNode()->GetPosition() ), GetAvoidanceDistance() );
|
||
|
Vector traceEnd = self.origin + ( self.movementSubsystem->getMoveDir() * traceLength );
|
||
|
trace_t horizontalTrace;
|
||
|
trace_t verticalTrace;
|
||
|
stepmoveresult_t result = self.movementSubsystem->IsMoveValid( horizontalTrace, verticalTrace, self.origin, traceEnd );
|
||
|
if ( result == STEPMOVE_STUCK )
|
||
|
{
|
||
|
return Steering::FAILED;
|
||
|
}
|
||
|
if ( result == STEPMOVE_BLOCKED_BY_ENTITY )
|
||
|
{
|
||
|
return AvoidObstacle( self, horizontalTrace );
|
||
|
}
|
||
|
|
||
|
SteerToCurrentNode( self );
|
||
|
return Steering::EVALUATING;
|
||
|
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: GotoGoal
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: steers the Actor towards the final goal
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowPath::GotoGoal( Actor &self )
|
||
|
{
|
||
|
float traceLength = min(Vector::DistanceXY( self.origin, GetGoalPosition() ), GetAvoidanceDistance() );
|
||
|
Vector traceEnd = self.origin + ( self.movementSubsystem->getMoveDir() * traceLength );
|
||
|
trace_t horizontalTrace;
|
||
|
trace_t verticalTrace;
|
||
|
stepmoveresult_t result = self.movementSubsystem->IsMoveValid( horizontalTrace, verticalTrace, self.origin, traceEnd );
|
||
|
if ( result == STEPMOVE_STUCK )
|
||
|
{
|
||
|
return Steering::FAILED;
|
||
|
}
|
||
|
if ( result == STEPMOVE_BLOCKED_BY_ENTITY )
|
||
|
{
|
||
|
return AvoidObstacle( self, horizontalTrace, true );
|
||
|
}
|
||
|
|
||
|
SetSteeringForceAndDesiredHeading
|
||
|
(
|
||
|
self,
|
||
|
self.movementSubsystem->SteerTowardsPoint
|
||
|
(
|
||
|
GetGoalPosition(),
|
||
|
vec_zero,
|
||
|
self.movementSubsystem->getMoveDir(),
|
||
|
self.movementSubsystem->getMoveSpeed()
|
||
|
)
|
||
|
);
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: Evaluate
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: attempts to move the Actor to the goal position
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowPath::Evaluate( Actor &self )
|
||
|
{
|
||
|
ResetForces();
|
||
|
|
||
|
|
||
|
if ( _jumping )
|
||
|
{
|
||
|
if ( _jump.Evaluate( self ) == Steering::SUCCESS)
|
||
|
{
|
||
|
_jump.End( self );
|
||
|
_jumping = false;
|
||
|
self.SetAnim( _oldAnim );
|
||
|
}
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (AtDestination( self ))
|
||
|
{
|
||
|
FreePath();
|
||
|
|
||
|
//if ( g_showactorpath->integer )
|
||
|
// gi.WDPrintf("Success %d\n" , self.entnum );
|
||
|
|
||
|
return Steering::SUCCESS;
|
||
|
}
|
||
|
|
||
|
|
||
|
Steering::ReturnValue returnValue = Steering::EVALUATING;
|
||
|
|
||
|
trace_t traceToGoal;
|
||
|
trace_t verticalTrace;
|
||
|
stepmoveresult_t moveResult = self.movementSubsystem->IsMoveValid( traceToGoal, verticalTrace, self.origin, GetGoalPosition() );
|
||
|
|
||
|
if (
|
||
|
(
|
||
|
!self.GetActorFlag(ACTOR_FLAG_STRICTLY_FOLLOW_PATHS) &&
|
||
|
(( moveResult == STEPMOVE_OK) ||
|
||
|
( traceToGoal.entityNum != ENTITYNUM_WORLD && traceToGoal.entityNum != ENTITYNUM_NONE && ClearTraceToGoal( self, traceToGoal, GetGoalRadius() ) )
|
||
|
) && self.movementSubsystem->CanWalkTowardsPoint( GetGoalPosition() ) )
|
||
|
)
|
||
|
{
|
||
|
FreePath();
|
||
|
SteerToGoal( self );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( DoneTurning( self ) )
|
||
|
{
|
||
|
returnValue = GetPath().Evaluate( self, GetGoalPosition() );
|
||
|
|
||
|
int currentNodeIndex = GetPath().GetCurrentNodeIndex();
|
||
|
if ( currentNodeIndex > 1 )
|
||
|
{
|
||
|
FollowNodePtr lastNode = GetPath().GetNodeAt( currentNodeIndex - 1 );
|
||
|
const Vector currentNodePosition = GetPath().GetCurrentNode()->GetPosition();
|
||
|
const float distanceFromActorToCurrentNode = Vector::DistanceXY( self.origin, currentNodePosition );
|
||
|
const float distanceBetweenNodes = Vector::DistanceXY( lastNode->GetPosition(), currentNodePosition );
|
||
|
if ( lastNode->GetIsJumpNode() && ( distanceFromActorToCurrentNode > 0.5f * distanceBetweenNodes ) )
|
||
|
{
|
||
|
_jumping = true;
|
||
|
_oldAnim = self.animname;
|
||
|
_jump.SetGoal( GetCurrentNode()->GetPosition() );
|
||
|
_jump.SetLaunchAngle( lastNode->GetJumpAngle() );
|
||
|
_jump.Begin( self );
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
}
|
||
|
if ( returnValue == Steering::EVALUATING )
|
||
|
{
|
||
|
returnValue = GotoCurrentNode( self );
|
||
|
}
|
||
|
|
||
|
else if ( returnValue == Steering::FAILED_NO_PATH && moveResult != STEPMOVE_BLOCKED_BY_WORLD )
|
||
|
{
|
||
|
returnValue = GotoGoal( self );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Vector newSteeringForce( _desiredHeading - self.movementSubsystem->getMoveDir().toAngles() );
|
||
|
newSteeringForce.EulerNormalize();
|
||
|
SetSteeringForce( newSteeringForce );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( returnValue == Steering::EVALUATING )
|
||
|
{
|
||
|
self.movementSubsystem->Accelerate( GetSteeringForce() );
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return returnValue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: ShowInfo
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: prints out useful debug info for the class
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::ShowInfo( Actor &self )
|
||
|
{
|
||
|
Steering::ShowInfo( self );
|
||
|
|
||
|
gi.Printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n",
|
||
|
_path.FirstNode()->GetPosition().x, _path.FirstNode()->GetPosition().y, _path.FirstNode()->GetPosition().z,
|
||
|
_path.LastNode()->GetPosition().x, _path.LastNode()->GetPosition().y, _path.LastNode()->GetPosition().z );
|
||
|
|
||
|
if ( GetCurrentNode() )
|
||
|
{
|
||
|
gi.Printf( "currentNode: ( %f, %f, %f )\n",
|
||
|
GetCurrentNode()->GetPosition().x, GetCurrentNode()->GetPosition().y, GetCurrentNode()->GetPosition().z );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
gi.Printf( "currentNode: NULL\n" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: DeleteTemporaryPathNodes
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: deletes all temporary FollowNodes in the path
|
||
|
// and removes them from the path
|
||
|
//
|
||
|
// Parameters: None
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::DeleteTemporaryPathNodes(void)
|
||
|
{
|
||
|
int numNodes=GetPath().NumNodes();
|
||
|
int i;
|
||
|
for (i=numNodes; i>0; i--)
|
||
|
{
|
||
|
FollowNodePtr nodeToRemove=GetPath().GetNodeAt(i);
|
||
|
if (nodeToRemove->GetIsTemporary())
|
||
|
{
|
||
|
GetPath().RemoveNode(nodeToRemove);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: ClearTraceToGoal
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: test to determine if Actor can move directly
|
||
|
// to the goal
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the Actor trying to get to the goal
|
||
|
// trace_t trace - trace that travels from the
|
||
|
// Actor to the goal
|
||
|
//
|
||
|
// Returns: bool that is true if the trace reaches the goal
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::ClearTraceToGoal( Actor &self, const trace_t &traceToGoal, const float radius ) const
|
||
|
{
|
||
|
// Totally clear path
|
||
|
bool traceToGoalIsClear = traceToGoal.fraction == 1.0f;
|
||
|
|
||
|
// Path is blocked but obstracle is inside goal radius
|
||
|
float distanceToEdgeOfGoal = Vector::DistanceXY( self.origin, GetGoalPosition() ) - radius;
|
||
|
float lengthOfTrace = Vector::DistanceXY( self.origin, Vector( traceToGoal.endpos ) );
|
||
|
bool traceGotInsideGoalRadius = ( distanceToEdgeOfGoal < lengthOfTrace );
|
||
|
|
||
|
return ( !traceToGoal.startsolid && ( traceToGoalIsClear || traceGotInsideGoalRadius) );
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: BuildAvoidancePath
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: attempts to build a temporary path around one
|
||
|
// side of the obstacle
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// bool passOnTheLeft - should the path be created
|
||
|
// on the left side if the obstacle
|
||
|
// Vector obstaclePosition - center point of the
|
||
|
// obstacle
|
||
|
// float avoidanceRadius - distance used to avoid
|
||
|
// obstacle
|
||
|
//
|
||
|
// Returns: bool that is true if a path is successfully
|
||
|
// created
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::BuildAvoidancePath(Actor &self, const bool passOnTheLeft, const Vector &obstaclePosition, const float avoidanceRadius, const bool pursueGoal )
|
||
|
{
|
||
|
DeleteTemporaryPathNodes();
|
||
|
Vector myPosition(self.origin);
|
||
|
float distanceToGoal=Vector::DistanceXY(myPosition, GetGoalPosition());
|
||
|
Vector parallelDirection(self.movementSubsystem->getMoveDir());
|
||
|
if ( distanceToGoal < avoidanceRadius * 2.0f || pursueGoal )
|
||
|
{
|
||
|
parallelDirection = GetGoalPosition() - myPosition;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int currentNodeIndex = GetPath().GetNodeIndex(GetPath().GetCurrentNode());
|
||
|
if (currentNodeIndex < GetPath().NumNodes())
|
||
|
{
|
||
|
parallelDirection = GetPath().GetNodeAt(currentNodeIndex + 1)->GetPosition() - GetPath().GetCurrentNode()->GetPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
parallelDirection.normalize();
|
||
|
|
||
|
Vector perpendicularDirection;
|
||
|
perpendicularDirection.CrossProduct(parallelDirection, Vector(0,0,1));
|
||
|
if (passOnTheLeft)
|
||
|
{
|
||
|
perpendicularDirection *= -1.0f;
|
||
|
}
|
||
|
|
||
|
Vector firstPathNodeLocation(obstaclePosition + (avoidanceRadius * (perpendicularDirection - parallelDirection)));
|
||
|
trace_t testTraceForFirstPoint;
|
||
|
trace_t verticalTrace;
|
||
|
stepmoveresult_t firstMoveResult = self.movementSubsystem->IsMoveValid( testTraceForFirstPoint, verticalTrace, self.origin, firstPathNodeLocation );
|
||
|
|
||
|
if ( ( firstMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForFirstPoint, avoidanceRadius/2.0f ) ) )
|
||
|
{
|
||
|
Vector secondPathNodeLocation(obstaclePosition + (avoidanceRadius * (perpendicularDirection + parallelDirection)));
|
||
|
trace_t testTraceForSecondPoint;
|
||
|
stepmoveresult_t secondMoveResult = self.movementSubsystem->IsMoveValid( testTraceForSecondPoint, verticalTrace, firstPathNodeLocation, secondPathNodeLocation );
|
||
|
|
||
|
if ( ( secondMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForSecondPoint, avoidanceRadius/2.0f ) ) )
|
||
|
{
|
||
|
Vector thirdPathNodeLocation(obstaclePosition + (2.0f * avoidanceRadius * parallelDirection));
|
||
|
trace_t testTraceForThirdPoint;
|
||
|
stepmoveresult_t thirdMoveResult = self.movementSubsystem->IsMoveValid( testTraceForThirdPoint, verticalTrace, secondPathNodeLocation, thirdPathNodeLocation );
|
||
|
|
||
|
if ( ( thirdMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForThirdPoint, avoidanceRadius/2.0f ) ) )
|
||
|
{
|
||
|
int insertionPoint=GetPath().GetNodeIndex(GetPath().GetCurrentNode());
|
||
|
if (insertionPoint > 0)
|
||
|
{
|
||
|
FollowNodePtr nextNode=GetPath().GetNodeAt(insertionPoint);
|
||
|
while (nextNode != NULL && (Vector::DistanceXY(nextNode->GetPosition(), myPosition) < Vector::DistanceXY(myPosition, thirdPathNodeLocation)))
|
||
|
{
|
||
|
insertionPoint++;
|
||
|
if (insertionPoint <= GetPath().NumNodes())
|
||
|
{
|
||
|
nextNode=GetPath().GetNodeAt(insertionPoint);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nextNode=NULL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
insertionPoint=1;
|
||
|
}
|
||
|
if ( Vector::DistanceXY(myPosition, firstPathNodeLocation) < distanceToGoal)
|
||
|
{
|
||
|
if (Vector::DistanceXY(myPosition, secondPathNodeLocation) < distanceToGoal)
|
||
|
{
|
||
|
if (Vector::DistanceXY(myPosition, thirdPathNodeLocation) < distanceToGoal)
|
||
|
{
|
||
|
InsertPathNode(new FollowNode(thirdPathNodeLocation, true), insertionPoint);
|
||
|
}
|
||
|
InsertPathNode(new FollowNode(secondPathNodeLocation, true), insertionPoint);
|
||
|
}
|
||
|
InsertPathNode(new FollowNode(firstPathNodeLocation, true), insertionPoint);
|
||
|
SetSteeringForceAndDesiredHeading( self,
|
||
|
self.movementSubsystem->SteerTowardsPoint (
|
||
|
firstPathNodeLocation, // Target Position
|
||
|
vec_zero, // Target Acceleration
|
||
|
self.movementSubsystem->getMoveDir(), // Actor Move Direction
|
||
|
self.movementSubsystem->getMoveSpeed()) // Actor Move Speed
|
||
|
);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: AvoidObstacle
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: attempts to build a temporary path around one
|
||
|
// side of the obstacle
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// trace_t trace - the trace that hits the obstacle
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
//
|
||
|
// Given that there is an obstacle in the actor's way, the actor will attempt
|
||
|
// trace a route around the obstacle and then insert new temporary path nodes
|
||
|
// into the path to allow the actor to navigate around the obstacle without
|
||
|
// having to do larges amounts of complex steering every frame.
|
||
|
//
|
||
|
// The plan is to pick a point (#1) that will get the actor away from the obstacle
|
||
|
// and will also allow the actor to pass the obstacle. Point 2 is passed the
|
||
|
// obstacle in the sense that it is further down a path that is parallel to the
|
||
|
// direction from the actor to the obstacle. The third point is chosen to safely
|
||
|
// get the actor back on the path.
|
||
|
//
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
//
|
||
|
// O-> X 3---------->
|
||
|
// \ /
|
||
|
// 1---2
|
||
|
//
|
||
|
//
|
||
|
// Actor O->
|
||
|
// Obstacle X
|
||
|
// First New Path Node 1
|
||
|
// Second New Path Node 2
|
||
|
// Third New Path Node 3
|
||
|
//
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowPath::AvoidObstacle(Actor &self, const trace_t &trace, const bool pursueGoal )
|
||
|
{
|
||
|
Entity *hitEntity = G_GetEntity( trace.entityNum );
|
||
|
assert( hitEntity->entnum != ENTITYNUM_WORLD );
|
||
|
if ( hitEntity == NULL )
|
||
|
{
|
||
|
return Steering::FAILED;
|
||
|
}
|
||
|
|
||
|
Vector obstaclePosition( hitEntity->origin );
|
||
|
float avoidanceRadius( hitEntity->mins.lengthXY() + self.mins.lengthXY() );
|
||
|
|
||
|
bool passOnLeft = false;
|
||
|
switch( self._steeringDirectionPreference )
|
||
|
{
|
||
|
case STEER_RIGHT_ALWAYS:
|
||
|
passOnLeft = false;
|
||
|
break;
|
||
|
case STEER_LEFT_ALWAYS:
|
||
|
passOnLeft = true;
|
||
|
break;
|
||
|
case STEER_RANDOMLY:
|
||
|
{
|
||
|
int random = rand() % 2;
|
||
|
if ( random )
|
||
|
{
|
||
|
passOnLeft = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
passOnLeft = true;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case STEER_BEST:
|
||
|
// To be implemented
|
||
|
// Intended to allow obstable avoidance to make a guess as to which way will get to goal better
|
||
|
passOnLeft = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
bool foundPath = BuildAvoidancePath(self, passOnLeft, obstaclePosition, avoidanceRadius, pursueGoal );
|
||
|
|
||
|
if (!foundPath)
|
||
|
{
|
||
|
foundPath = BuildAvoidancePath(self, !passOnLeft, obstaclePosition, avoidanceRadius, pursueGoal );
|
||
|
}
|
||
|
if (foundPath)
|
||
|
{
|
||
|
return Steering::EVALUATING;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return Steering::FAILED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: TraceBlockedByEntity
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: test that determines if there is an entity that
|
||
|
// we will collide with anywhere along the trace
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
// trace_t trace - the trace that hits the obstacle
|
||
|
//
|
||
|
// Returns: bool that is true if there is a blocking Entity
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::TraceBlockedByEntity(Actor &self, const trace_t & trace ) const
|
||
|
{
|
||
|
if ( !trace.startsolid && (trace.fraction == 1.0f || trace.entityNum == ENTITYNUM_WORLD))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Entity *obstacleEntity = G_GetEntity( trace.entityNum );
|
||
|
assert( obstacleEntity != NULL );
|
||
|
|
||
|
if (obstacleEntity->velocity.lengthXY() < Vector::Epsilon())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Vector relativeVelocity = self.velocity - obstacleEntity->velocity;
|
||
|
|
||
|
float rateOfClosure = self.movementSubsystem->getMoveDir() * relativeVelocity;
|
||
|
|
||
|
// This means that the two entities are moving towards each other
|
||
|
if ( rateOfClosure >= 0.0f )
|
||
|
{
|
||
|
/* Code to check if the obstacle will move out of the way quickly enough
|
||
|
Vector myPosition(self.origin);
|
||
|
float myRadius = self.maxs.length();
|
||
|
|
||
|
Vector obstaclePosition = trace.endpos;
|
||
|
float obstacleRadius = obstacleEntity->maxs.length();
|
||
|
|
||
|
Vector obstacleDirection = obstaclePosition - myPosition;
|
||
|
float obstacleDistance = obstacleDirection.length();
|
||
|
obstacleDirection /= obstacleDistance; // normalize
|
||
|
|
||
|
Vector perpendicularDirection;
|
||
|
perpendicularDirection.CrossProduct(obstacleDirection, Vector(0,0,1));
|
||
|
|
||
|
float timeUntilCollision = obstacleDistance / self.total_delta.length(); // Adequate approximation
|
||
|
|
||
|
Vector obstacleVelocity(obstacleEntity->velocity);
|
||
|
float parallelSpeed=Vector::Dot(obstacleVelocity, obstacleDirection);
|
||
|
float perpendicularSpeed=Vector::Dot(obstacleVelocity, perpendicularDirection);
|
||
|
|
||
|
float parallelMove = parallelSpeed * timeUntilCollision;
|
||
|
float perpendicularMove = perpendicularSpeed * timeUntilCollision;
|
||
|
|
||
|
// If this is true then the obstacle entity will not get out of the way fast
|
||
|
// enoughm so we need to avoid them
|
||
|
if (perpendicularMove < parallelMove + myRadius + obstacleRadius)
|
||
|
*/
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: CheckBlocked
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: test that determines if the Actor is blocked
|
||
|
// by a door
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: bool that is true if the Actor is blocked
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::CheckBlocked( Actor &self )
|
||
|
{
|
||
|
trace_t trace = self.Trace( GetAvoidanceDistance() , "FollowPath" );
|
||
|
|
||
|
if ( trace.ent )
|
||
|
{
|
||
|
Entity *door = trace.ent->entity;
|
||
|
if ( door && door->isSubclassOf( Door ) )
|
||
|
{
|
||
|
Event *ev = new Event( EV_Use );
|
||
|
ev->AddEntity( &self );
|
||
|
door->PostEvent( ev, 0 );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: ReturnBlockingObject
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: returns a code based on the type of Entity
|
||
|
// that is blocking the Actor
|
||
|
//
|
||
|
// Parameters:
|
||
|
// trace_t trace - the trace that hits the obstacle
|
||
|
//
|
||
|
// Returns: Steering::ReturnValue returns reason for failure
|
||
|
//----------------------------------------------------------------
|
||
|
const Steering::ReturnValue FollowPath::ReturnBlockingObject(const trace_t &trace) const
|
||
|
{
|
||
|
if (trace.entityNum == ENTITYNUM_WORLD)
|
||
|
{
|
||
|
return Steering::FAILED_BLOCKED_BY_WORLD;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Entity *traceEnt = G_GetEntity( trace.entityNum );
|
||
|
if ( traceEnt && traceEnt->isSubclassOf( Actor) )
|
||
|
{
|
||
|
Actor *traceActor = static_cast<Actor *>(traceEnt);
|
||
|
switch (traceActor->actortype)
|
||
|
{
|
||
|
case IS_ENEMY:
|
||
|
return Steering::FAILED_BLOCKED_BY_ENEMY;
|
||
|
break;
|
||
|
case IS_CIVILIAN:
|
||
|
return Steering::FAILED_BLOCKED_BY_CIVILIAN;
|
||
|
break;
|
||
|
case IS_FRIEND:
|
||
|
return Steering::FAILED_BLOCKED_BY_FRIEND;
|
||
|
break;
|
||
|
case IS_TEAMMATE:
|
||
|
return Steering::FAILED_BLOCKED_BY_TEAMMATE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return Steering::ERROR;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: AtDestination
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: test that determines if the Actor is close to
|
||
|
// the goal
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: bool that is true if the Actor is at the goal
|
||
|
//----------------------------------------------------------------
|
||
|
const bool FollowPath::AtDestination(Actor &self) const
|
||
|
{
|
||
|
//float dist = Vector::DistanceXY(GetGoalPosition(), self.origin);
|
||
|
//gi.WDPrintf( "Distance: %f , %d\n" , dist , self.entnum );
|
||
|
|
||
|
|
||
|
//Check if we are in range
|
||
|
if ((Vector::DistanceXY(GetGoalPosition(), self.origin)) <= GetRadius())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: SteerToCurrentNode
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: steers the Actor towards the current FollowNode
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::SteerToCurrentNode(Actor &self )
|
||
|
{
|
||
|
assert ( GetCurrentNode() != NULL );
|
||
|
|
||
|
SetSteeringForceAndDesiredHeading
|
||
|
(
|
||
|
self,
|
||
|
self.movementSubsystem->SteerTowardsPoint
|
||
|
(
|
||
|
GetCurrentNode()->GetPosition(),
|
||
|
vec_zero,
|
||
|
self.movementSubsystem->getMoveDir(),
|
||
|
self.movementSubsystem->getMoveSpeed()
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------
|
||
|
// Name: SteerToGoal
|
||
|
// Class: FollowPath
|
||
|
//
|
||
|
// Description: steers the Actor towards the goal
|
||
|
//
|
||
|
// Parameters:
|
||
|
// Actor self - the actor following the path
|
||
|
//
|
||
|
// Returns: None
|
||
|
//----------------------------------------------------------------
|
||
|
void FollowPath::SteerToGoal( Actor &self )
|
||
|
{
|
||
|
Vector newSteeringForce(self.movementSubsystem->SteerTowardsPoint(
|
||
|
GetGoalPosition(),
|
||
|
vec_zero,
|
||
|
self.movementSubsystem->getMoveDir(),
|
||
|
self.movementSubsystem->getMoveSpeed()
|
||
|
) );
|
||
|
|
||
|
SetSteeringForceAndDesiredHeading( self, newSteeringForce );
|
||
|
}
|