|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "ndebugoverlay.h"
#include "ai_pathfinder.h"
#include "ai_basenpc.h"
#include "ai_node.h"
#include "ai_network.h"
#include "ai_waypoint.h"
#include "ai_link.h"
#include "ai_routedist.h"
#include "ai_moveprobe.h"
#include "ai_dynamiclink.h"
#include "ai_localnavigator.h"
#include "ai_hint.h"
#include "bitstring.h"
//@todo: bad dependency!
#include "ai_navigator.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#if defined( OSX )
#pragma GCC diagnostic ignored "-Wenum-compare" // comparison of two values with different enumeration types (Capability_t and RouteBuildFlags_e)
#endif
#define NUM_NPC_DEBUG_OVERLAYS 50
const float MAX_LOCAL_NAV_DIST_GROUND[2] = { (50*12), (25*12) }; const float MAX_LOCAL_NAV_DIST_FLY[2] = { (750*12), (750*12) };
//-----------------------------------------------------------------------------
// CAI_Pathfinder
//
BEGIN_SIMPLE_DATADESC( CAI_Pathfinder )
// m_TriDebugOverlay
// m_bIgnoreStaleLinks
DEFINE_FIELD( m_flLastStaleLinkCheckTime, FIELD_TIME ), // m_pNetwork
END_DATADESC()
//-----------------------------------------------------------------------------
// Compute move type bits to nav type
//-----------------------------------------------------------------------------
Navigation_t MoveBitsToNavType( int fBits ) { switch (fBits) { case bits_CAP_MOVE_GROUND: return NAV_GROUND;
case bits_CAP_MOVE_FLY: return NAV_FLY;
case bits_CAP_MOVE_CLIMB: return NAV_CLIMB;
case bits_CAP_MOVE_JUMP: return NAV_JUMP;
case bits_CAP_MOVE_CRAWL: return NAV_CRAWL;
default: // This will only happen if more than one bit is set
return NAV_NONE; } }
int NavTypeToMoveBits( Navigation_t nNavType ) { switch (nNavType) { case NAV_GROUND: return bits_CAP_MOVE_GROUND;
case NAV_FLY: return bits_CAP_MOVE_FLY;
case NAV_CLIMB: return bits_CAP_MOVE_CLIMB;
case NAV_JUMP: return bits_CAP_MOVE_JUMP;
case NAV_CRAWL: return bits_CAP_MOVE_CRAWL;
default: return 0; } }
//-----------------------------------------------------------------------------
void CAI_Pathfinder::Init( CAI_Network *pNetwork ) { Assert( pNetwork ); m_pNetwork = pNetwork; } //-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CAI_Pathfinder::UseStrongOptimizations() { if ( !AIStrongOpt() ) { return false; }
#ifdef HL2_DLL
if( GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL ) { return false; } #endif//HL2_DLL
return true; }
//-----------------------------------------------------------------------------
// Computes the link type
//-----------------------------------------------------------------------------
Navigation_t CAI_Pathfinder::ComputeWaypointType( bool *pWantsPreciseMovement, CAI_Node **ppNodes, int parentID, int destID ) { Navigation_t navType = NAV_NONE; *pWantsPreciseMovement = false;
CAI_Node *pNode = ppNodes[parentID]; for (int link=0; link < pNode->NumLinks();link++) { CAI_Link *pLink = pNode->GetLinkByIndex(link); if ( pLink->DestNodeID(parentID) != destID ) continue;
// BRJ 10/1/02
// FIXME: pNPC->CapabilitiesGet() is actually the mechanism by which fliers
// filter out the bitfields in the waypoint type (most importantly, bits_MOVE_CAP_GROUND)
// that would cause the waypoint distance to be computed in a 2D, as opposed to 3D fashion
// This is a super-scary weak link if you ask me.
int linkMoveTypeBits = pLink->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( linkMoveTypeBits & CapabilitiesGet()); if ( !moveTypeBits && linkMoveTypeBits == bits_CAP_MOVE_JUMP ) { Assert( pNode->GetHint() && pNode->GetHint()->HintType() == HINT_JUMP_OVERRIDE ); ppNodes[destID]->Lock(0.3); moveTypeBits = linkMoveTypeBits; } *pWantsPreciseMovement = ( pLink->m_LinkInfo & bits_LINK_PRECISE_MOVEMENT ) != 0;
Navigation_t linkType = NAV_NONE; if ( IsPowerOfTwo( moveTypeBits & bits_CAP_MOVE_GROUP ) ) { linkType = MoveBitsToNavType( moveTypeBits ); } else { // NOTE: Hack for nodes which say they are jump-capable + crawl-capable
if ( navType != NAV_NONE ) { // This will only trigger if the links disagree about their nav type
Assert( NavTypeToMoveBits( navType ) & moveTypeBits ); linkType = navType; } else { // This logic only works assuming crawl nodes are the only
// type of node that can overlap with other nodes
Assert( moveTypeBits & bits_CAP_MOVE_CRAWL ); moveTypeBits = ( CapabilitiesGet() & bits_CAP_MOVE_CRAWL ) ? ( moveTypeBits & (~bits_CAP_MOVE_GROUP ) | bits_CAP_MOVE_CRAWL ) : ( moveTypeBits & (~bits_CAP_MOVE_CRAWL) ); linkType = MoveBitsToNavType( moveTypeBits ); } }
// This will only trigger if the links disagree about their nav type
Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType; break; }
// @TODO (toml 10-15-02): one would not expect to come out of the above logic
// with NAV_NONE. However, if a graph is newly built, it can contain malformed
// links that are referred to by the destination node, not the source node.
// This has to be fixed
if ( navType == NAV_NONE ) { pNode = ppNodes[destID]; for (int link=0; link < pNode->NumLinks();link++) { if (pNode->GetLinkByIndex(link)->DestNodeID(parentID) == destID) { int npcMoveBits = CapabilitiesGet(); int nodeMoveBits = pNode->GetLinkByIndex(link)->m_iAcceptedMoveTypes[GetHullType()]; int moveTypeBits = ( npcMoveBits & nodeMoveBits ); Navigation_t linkType = MoveBitsToNavType( moveTypeBits );
Assert( (navType == NAV_NONE) || (navType == linkType) ); navType = linkType;
DevMsg( "Note: Strange link found between nodes in AI node graph\n" ); break; } } }
AssertMsg( navType != NAV_NONE, "Pathfinder appears to have output a path with consecutive nodes thate are not actually connected\n" );
return navType; }
//-----------------------------------------------------------------------------
// Purpose: Given an array of parentID's and endID, contruct a linked
// list of waypoints through those parents
//-----------------------------------------------------------------------------
AI_Waypoint_t* CAI_Pathfinder::MakeRouteFromParents( int *parentArray, int endID ) { AI_Waypoint_t *pOldWaypoint = NULL; AI_Waypoint_t *pNewWaypoint = NULL; int currentID = endID;
CAI_Node **pAInode = GetNetwork()->AccessNodes();
int nNextWaypointFlags = 0; while (currentID != NO_NODE) { // Try to link it to the previous waypoint
int prevID = parentArray[currentID];
int destID; if (prevID != NO_NODE) { destID = prevID; } else { // If we have no previous node, then use the next node
if ( !pOldWaypoint ) return NULL; destID = pOldWaypoint->iNodeID; }
bool bWantsPreciseMovement; Navigation_t waypointType = ComputeWaypointType( &bWantsPreciseMovement, pAInode, currentID, destID );
// BRJ 10/1/02
// FIXME: It appears potentially possible for us to compute waypoints
// here which the NPC is not capable of traversing (because
// pNPC->CapabilitiesGet() in ComputeWaypointType() above filters it out).
// It's also possible if none of the lines have an appropriate DestNodeID.
// Um, shouldn't such a waypoint not be allowed?!?!?
Assert( waypointType != NAV_NONE ); int nWaypointFlags = bits_WP_TO_NODE | nNextWaypointFlags; if ( bWantsPreciseMovement ) { nWaypointFlags |= bits_WP_DONT_SIMPLIFY | bits_WP_PRECISE_MOVEMENT; nNextWaypointFlags = bits_WP_DONT_SIMPLIFY | bits_WP_PRECISE_MOVEMENT; } else { nNextWaypointFlags = 0; }
pNewWaypoint = new AI_Waypoint_t( pAInode[currentID]->GetPosition( GetHullType() ), pAInode[currentID]->GetYaw(), waypointType, nWaypointFlags, currentID );
// Link it up...
pNewWaypoint->SetNext( pOldWaypoint ); pOldWaypoint = pNewWaypoint;
currentID = prevID; }
return pOldWaypoint; }
//------------------------------------------------------------------------------
// Purpose : Test if stale link is no longer stale
//------------------------------------------------------------------------------
bool CAI_Pathfinder::IsLinkStillStale(int moveType, CAI_Link *nodeLink) { if ( m_bIgnoreStaleLinks ) return false;
if ( !(nodeLink->m_LinkInfo & bits_LINK_STALE_SUGGESTED ) ) return false;
if ( gpGlobals->curtime < nodeLink->m_timeStaleExpires ) return true;
// NPC should only check one stale link per think
if (gpGlobals->curtime == m_flLastStaleLinkCheckTime) { return true; } else { m_flLastStaleLinkCheckTime = gpGlobals->curtime; } // Test movement, if suceeds, clear the stale bit
if (CheckStaleRoute(GetNetwork()->GetNode(nodeLink->m_iSrcID)->GetPosition(GetHullType()), GetNetwork()->GetNode(nodeLink->m_iDestID)->GetPosition(GetHullType()), moveType)) { nodeLink->m_LinkInfo &= ~bits_LINK_STALE_SUGGESTED; return false; }
nodeLink->m_timeStaleExpires = gpGlobals->curtime + 1.0;
return true; }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_Pathfinder::NearestNodeToNPC() { return GetNetwork()->NearestNodeToPoint( GetOuter(), GetAbsOrigin() ); }
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CAI_Pathfinder::NearestNodeToPoint( const Vector &vecOrigin ) { return GetNetwork()->NearestNodeToPoint( GetOuter(), vecOrigin ); }
//-----------------------------------------------------------------------------
// Purpose: Build a path between two nodes
//-----------------------------------------------------------------------------
static float s_pDangerDistFactor[3] = { 2048.0f, 4096.0f, 8192.0f }; AI_Waypoint_t *CAI_Pathfinder::FindBestPath(int startID, int endID) { AI_PROFILE_SCOPE( CAI_Pathfinder_FindBestPath ); if ( !GetNetwork()->NumNodes() ) return NULL;
#ifdef AI_PERF_MON
m_nPerfStatPB++; #endif
int nNodes = GetNetwork()->NumNodes(); CAI_Node **pAInode = GetNetwork()->AccessNodes();
CVarBitVec openBS(nNodes); CVarBitVec closeBS(nNodes);
// ------------- INITIALIZE ------------------------
float* nodeG = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeH = (float *)stackalloc( nNodes * sizeof(float) ); float* nodeF = (float *)stackalloc( nNodes * sizeof(float) ); int* nodeP = (int *)stackalloc( nNodes * sizeof(int) ); // Node parent
for (int node=0;node<nNodes;node++) { nodeG[node] = FLT_MAX; nodeP[node] = -1; }
nodeG[startID] = 0;
nodeH[startID] = 0.1*(pAInode[startID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); // Don't want to over estimate
nodeF[startID] = nodeG[startID] + nodeH[startID];
openBS.Set(startID); closeBS.Set( startID );
// --------------- FIND BEST PATH ------------------
while (!openBS.IsAllClear()) { int smallestID = CAI_Network::FindBSSmallest(&openBS,nodeF,nNodes); openBS.Clear(smallestID);
CAI_Node *pSmallestNode = pAInode[smallestID]; if (GetOuter()->IsUnusableNode(smallestID, pSmallestNode->GetHint())) continue;
if (smallestID == endID) { AI_Waypoint_t* route = MakeRouteFromParents(&nodeP[0], endID); return route; }
// Check this if the node is immediately in the path after the startNode
// that it isn't blocked
for (int link=0; link < pSmallestNode->NumLinks();link++) { CAI_Link *nodeLink = pSmallestNode->GetLinkByIndex(link); if (!IsLinkUsable(nodeLink,smallestID)) continue;
// FIXME: the cost function should take into account Node costs (danger, flanking, etc).
int moveType = nodeLink->m_iAcceptedMoveTypes[GetHullType()] & CapabilitiesGet(); int testID = nodeLink->DestNodeID(smallestID);
Vector r1 = pSmallestNode->GetPosition(GetHullType()); Vector r2 = pAInode[testID]->GetPosition(GetHullType());
float dist = GetOuter()->GetNavigator()->MovementCost( moveType, r1, r2 ); // MovementCost takes ref parameters!!
if ( dist == FLT_MAX ) continue;
if ( nodeLink->m_LinkInfo & bits_PREFER_AVOID ) { dist += 512.0f; }
if ( nodeLink->m_nDangerCount > 0 ) { if ( nodeLink->m_nDangerCount > 3 ) continue; dist += s_pDangerDistFactor[ nodeLink->m_nDangerCount - 1 ]; }
float new_g = nodeG[smallestID] + dist;
if ( !closeBS.IsBitSet(testID) || (new_g < nodeG[testID]) ) { nodeP[testID] = smallestID; nodeG[testID] = new_g; nodeH[testID] = (pAInode[testID]->GetPosition(GetHullType())-pAInode[endID]->GetPosition(GetHullType())).Length(); nodeF[testID] = nodeG[testID] + nodeH[testID];
closeBS.Set( testID ); openBS.Set( testID ); } } }
return NULL; }
//-----------------------------------------------------------------------------
// Purpose: Find a short random path of at least pathLength distance. If
// vDirection is given random path will expand in the given direction,
// and then attempt to go generally straight
//-----------------------------------------------------------------------------
AI_Waypoint_t* CAI_Pathfinder::FindShortRandomPath(int startID, float minPathLength, const Vector &directionIn) { int pNeighbor[AI_MAX_NODE_LINKS]; int pStaleNeighbor[AI_MAX_NODE_LINKS]; int numNeighbors = 1; // The start node
int numStaleNeighbors = 0; int neighborID = NO_NODE;
int nNodes = GetNetwork()->NumNodes(); CAI_Node **pAInode = GetNetwork()->AccessNodes();
if ( !nNodes ) return NULL; MARK_TASK_EXPENSIVE();
int *nodeParent = (int *)stackalloc( sizeof(int) * nNodes ); CVarBitVec closeBS(nNodes); Vector vDirection = directionIn;
// ------------------------------------------
// Bail immediately if node has no neighbors
// ------------------------------------------
if (pAInode[startID]->NumLinks() == 0) { return NULL; }
// ------------- INITIALIZE ------------------------
nodeParent[startID] = NO_NODE; pNeighbor[0] = startID;
// --------------- FIND PATH ---------------------------------------------------------------
// Quit when path is long enough, and I've run out of neighbors unless I'm on a climb node
// in which case I'm not allowed to stop
// -----------------------------------------------------------------------------------------
float pathLength = 0; int nSearchCount = 0; while ( (pathLength < minPathLength) || (neighborID != NO_NODE && pAInode[neighborID]->GetType() == NODE_CLIMB)) { nSearchCount++;
// If no neighbors try circling back to last node
if (neighborID != NO_NODE && numNeighbors == 0 && numStaleNeighbors == 0 ) { // If we dead ended on a climb node we've failed as we
// aren't allowed to stop on a climb node
if (pAInode[neighborID]->GetType() == NODE_CLIMB) { // If no neighbors exist we've failed.
return NULL; } // Otherwise accept this path to a dead end
else { AI_Waypoint_t* route = MakeRouteFromParents(&nodeParent[0], neighborID); return route; } }
// ----------------------
// Pick a neighbor
// ----------------------
int lastID = neighborID;
// If vDirection is non-zero attempt to expand close to current direction
if (vDirection != vec3_origin) { float bestDot = -1; Vector vLastPos;
if (lastID == NO_NODE) { vLastPos = GetLocalOrigin(); } else { vLastPos = pAInode[lastID]->GetOrigin(); }
// If no neighbors, try using a stale one
if (numNeighbors == 0) { neighborID = pStaleNeighbor[random->RandomInt(0,numStaleNeighbors-1)]; } else { for (int i=0;i<numNeighbors;i++) { Vector nodeDir = vLastPos - pAInode[pNeighbor[i]]->GetOrigin(); VectorNormalize(nodeDir); float fDotPr = DotProduct(vDirection,nodeDir); if (fDotPr > bestDot) { bestDot = fDotPr; neighborID = pNeighbor[i]; } } }
if (neighborID != NO_NODE) { vDirection = vLastPos - pAInode[neighborID]->GetOrigin(); VectorNormalize(vDirection); }
} // Pick random neighbor
else if (numNeighbors != 0) { neighborID = pNeighbor[random->RandomInt(0,numNeighbors-1)]; } // If no neighbors, try using a stale one
else { neighborID = pStaleNeighbor[random->RandomInt(0,numStaleNeighbors-1)]; }
// BUGBUG: This routine is totally hosed!
if ( neighborID < 0 ) return NULL;
// Set previous nodes parent
nodeParent[neighborID] = lastID; closeBS.Set(neighborID);
// Add the new length
if (lastID != NO_NODE) { pathLength += (pAInode[lastID]->GetOrigin() - pAInode[neighborID]->GetOrigin()).Length(); }
// If path is long enough or we've hit a maximum number of search nodes,
// we're done unless we've ended on a climb node
if ((pathLength >= minPathLength || nSearchCount > 20) && pAInode[neighborID]->GetType() != NODE_CLIMB) { return MakeRouteFromParents(&nodeParent[0], neighborID); }
// Clear neighbors
numNeighbors = 0; numStaleNeighbors = 0;
// Now add in new neighbors, pick links in different order ever time
pAInode[neighborID]->ShuffleLinks(); for (int link=0; link < pAInode[neighborID]->NumLinks();link++) { if ( numStaleNeighbors == ARRAYSIZE(pStaleNeighbor) ) { AssertMsg( 0, "Array overflow" ); return NULL; } if ( numNeighbors == ARRAYSIZE(pStaleNeighbor) ) { AssertMsg( 0, "Array overflow" ); return NULL; }
CAI_Link* nodeLink = pAInode[neighborID]->GetShuffeledLink(link); int testID = nodeLink->DestNodeID(neighborID);
// --------------------------------------------------------------------------
// Don't loop
// --------------------------------------------------------------------------
if (closeBS.IsBitSet(testID)) { continue; }
// --------------------------------------------------------------------------
// Don't go back to the node I just visited
// --------------------------------------------------------------------------
if (testID == lastID) { continue; }
// --------------------------------------------------------------------------
// Make sure link is valid
// --------------------------------------------------------------------------
if (!IsLinkUsable(nodeLink,neighborID)) { continue; }
// --------------------------------------------------------------------------
// If its a stale node add to stale list
// --------------------------------------------------------------------------
if (pAInode[testID]->IsLocked()) { pStaleNeighbor[numStaleNeighbors]=testID; numStaleNeighbors++; }
// --------------------------------------
// Add to list of non-stale neighbors
// --------------------------------------
else { pNeighbor[numNeighbors]=testID; numNeighbors++; } } } // Failed to get a path of full length, but return what we have
return MakeRouteFromParents(&nodeParent[0], neighborID); }
//------------------------------------------------------------------------------
// Purpose : Returns true is link us usable by the given NPC from the
// startID node.
//------------------------------------------------------------------------------
bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) { // --------------------------------------------------------------------------
// Skip if link turned off
// --------------------------------------------------------------------------
if (pLink->m_LinkInfo & ( bits_LINK_OFF | bits_LINK_ASW_BASHABLE ) ) { CAI_DynamicLink *pDynamicLink = pLink->m_pDynamicLink;
if ( !pDynamicLink || pDynamicLink->m_strAllowUse == NULL_STRING ) return false;
const char *pszAllowUse = STRING( pDynamicLink->m_strAllowUse ); if ( pDynamicLink->m_bInvertAllow ) { // Exlude only the specified entity name or classname
if ( GetOuter()->NameMatches(pszAllowUse) || GetOuter()->ClassMatches( pszAllowUse ) ) return false; } else { // Exclude everything but the allowed entity name or classname
if ( !GetOuter()->NameMatches( pszAllowUse) && !GetOuter()->ClassMatches( pszAllowUse ) ) return false; } }
// --------------------------------------------------------------------------
// Get the destination nodeID
// --------------------------------------------------------------------------
int endID = pLink->DestNodeID(startID);
// --------------------------------------------------------------------------
// Make sure I have the ability to do the type of movement specified by the link
// --------------------------------------------------------------------------
int linkMoveTypes = pLink->m_iAcceptedMoveTypes[GetHullType()]; int moveType = ( linkMoveTypes & CapabilitiesGet() );
CAI_Node *pStartNode,*pEndNode;
pStartNode = GetNetwork()->GetNode(startID); pEndNode = GetNetwork()->GetNode(endID);
if ( (linkMoveTypes & bits_CAP_MOVE_JUMP) && !moveType ) { CAI_Hint *pStartHint = pStartNode->GetHint(); CAI_Hint *pEndHint = pEndNode->GetHint(); if ( pStartHint && pEndHint ) { if ( pStartHint->HintType() == HINT_JUMP_OVERRIDE && pEndHint->HintType() == HINT_JUMP_OVERRIDE && ( ( ( pStartHint->GetSpawnFlags() | pEndHint->GetSpawnFlags() ) & SF_ALLOW_JUMP_UP ) || pStartHint->GetAbsOrigin().z > pEndHint->GetAbsOrigin().z ) ) { if ( !pStartNode->IsLocked() ) { if ( pStartHint->GetTargetNode() == -1 || pStartHint->GetTargetNode() == endID ) moveType = bits_CAP_MOVE_JUMP; } } } }
if (!moveType) { return false; }
// --------------------------------------------------------------------------
// Check if NPC has a reason not to use the desintion node
// --------------------------------------------------------------------------
if (GetOuter()->IsUnusableNode(endID, pEndNode->GetHint())) { return false; }
// --------------------------------------------------------------------------
// If a jump make sure the jump is within NPC's legal parameters for jumping
// --------------------------------------------------------------------------
if (moveType == bits_CAP_MOVE_JUMP) { if (!GetOuter()->IsJumpLegal(pStartNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()), pEndNode->GetPosition(GetHullType()))) { return false; } }
// --------------------------------------------------------------------------
// If an NPC suggested that this link is stale and I haven't checked it yet
// I should make sure the link is still valid before proceeding
// --------------------------------------------------------------------------
if (pLink->m_LinkInfo & bits_LINK_STALE_SUGGESTED) { if (IsLinkStillStale(moveType, pLink)) { return false; } } return true; }
//-----------------------------------------------------------------------------
static int NPCBuildFlags( CAI_BaseNPC *pNPC, const Vector &vecOrigin ) { // If vecOrigin the the npc's position and npc is climbing only climb nodes allowed
if (pNPC->GetLocalOrigin() == vecOrigin && pNPC->GetNavType() == NAV_CLIMB) { return bits_BUILD_CLIMB; } else if (pNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY) { return bits_BUILD_FLY | bits_BUILD_GIVEWAY; } else if (pNPC->CapabilitiesGet() & bits_CAP_MOVE_GROUND) { int buildFlags = bits_BUILD_GROUND | bits_BUILD_GIVEWAY; if (pNPC->CapabilitiesGet() & bits_CAP_MOVE_JUMP) { buildFlags |= bits_BUILD_JUMP; } if (pNPC->CapabilitiesGet() & bits_CAP_MOVE_CRAWL) { buildFlags |= bits_BUILD_CRAWL; }
return buildFlags; } return 0; }
//-----------------------------------------------------------------------------
// Creates a node waypoint
//-----------------------------------------------------------------------------
AI_Waypoint_t* CAI_Pathfinder::CreateNodeWaypoint( Hull_t hullType, int nodeID, int nodeFlags ) { CAI_Node *pNode = GetNetwork()->GetNode(nodeID);
Navigation_t navType; switch(pNode->GetType()) { case NODE_CLIMB: navType = NAV_CLIMB; break;
case NODE_AIR: navType = NAV_FLY; break;
default: navType = NAV_GROUND; break; }
return new AI_Waypoint_t( pNode->GetPosition(hullType), pNode->GetYaw(), navType, ( bits_WP_TO_NODE | nodeFlags) , nodeID ); }
//-----------------------------------------------------------------------------
// Purpose: Returns a route to a node for the given npc with the given
// build flags
//-----------------------------------------------------------------------------
AI_Waypoint_t* CAI_Pathfinder::RouteToNode(const Vector &vecOrigin, int buildFlags, int nodeID, float goalTolerance) { AI_PROFILE_SCOPE( CAI_Pathfinder_RouteToNode ); buildFlags |= NPCBuildFlags( GetOuter(), vecOrigin ); buildFlags &= ~bits_BUILD_GET_CLOSE;
// Check if vecOrigin is already at the smallest node
// FIXME: an equals check is a bit sloppy, this should be a tolerance
const Vector &vecNodePosition = GetNetwork()->GetNode(nodeID)->GetPosition(GetHullType()); if (vecOrigin == vecNodePosition) { return CreateNodeWaypoint( GetHullType(), nodeID, bits_WP_TO_GOAL ); }
// Otherwise try to build a local route to the node
AI_Waypoint_t *pResult = BuildLocalRoute(vecOrigin, vecNodePosition, NULL, bits_WP_TO_NODE, nodeID, buildFlags, goalTolerance); if ( pResult ) pResult->iNodeID = nodeID; return pResult; }
//-----------------------------------------------------------------------------
// Purpose: Returns a route to a node for the given npc with the given
// build flags
//-----------------------------------------------------------------------------
AI_Waypoint_t* CAI_Pathfinder::RouteFromNode(const Vector &vecOrigin, int buildFlags, int nodeID, float goalTolerance) { AI_PROFILE_SCOPE( CAI_Pathfinder_RouteFromNode ); buildFlags |= NPCBuildFlags( GetOuter(), vecOrigin ); buildFlags |= bits_BUILD_GET_CLOSE;
// Check if vecOrigin is already at the smallest node
// FIXME: an equals check is a bit sloppy, this should be a tolerance
CAI_Node *pNode = GetNetwork()->GetNode(nodeID); const Vector &vecNodePosition = pNode->GetPosition(GetHullType());
if (vecOrigin == vecNodePosition) { return CreateNodeWaypoint( GetHullType(), nodeID, bits_WP_TO_GOAL ); }
// Otherwise try to build a local route from the node
AI_Waypoint_t* pResult = BuildLocalRoute( vecNodePosition, vecOrigin, NULL, bits_WP_TO_GOAL, NO_NODE, buildFlags, goalTolerance);
// Handle case of target hanging over edge near climb dismount
if ( !pResult && pNode->GetType() == NODE_CLIMB && ( vecOrigin - vecNodePosition ).Length2DSqr() < 32.0*32.0 && GetOuter()->GetMoveProbe()->CheckStandPosition(vecNodePosition, GetOuter()->GetAITraceMask_BrushOnly() ) ) { pResult = new AI_Waypoint_t( vecOrigin, 0, NAV_GROUND, bits_WP_TO_GOAL, nodeID ); }
return pResult; }
//-----------------------------------------------------------------------------
// Builds a simple route (no triangulation, no making way)
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildSimpleRoute( Navigation_t navType, const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int nodeTargetType, float flYaw ) { Assert( navType == NAV_JUMP || navType == NAV_CLIMB || navType == NAV_CRAWL ); // this is what this here function is for
// Only allowed to jump to ground nodes
if ((nodeID == NO_NODE) || (GetNetwork()->GetNode(nodeID)->GetType() == nodeTargetType) ) { AIMoveTrace_t moveTrace; GetOuter()->GetMoveProbe()->MoveLimit( navType, vStart, vEnd, GetOuter()->GetAITraceMask(), pTarget, &moveTrace );
// If I was able to make the move, or the vEnd is the
// goal and I'm within tolerance, just move to vEnd
if (!IsMoveBlocked(moveTrace)) { // It worked so return a route of length one to the endpoint
return new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID ); } } return NULL; }
//-----------------------------------------------------------------------------
// Builds a complex route (triangulation, making way)
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildComplexRoute( Navigation_t navType, const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw, float goalTolerance, float maxLocalNavDistance ) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildComplexRoute ); float flTotalDist = ComputePathDistance( navType, vStart, vEnd ); if ( flTotalDist < 0.0625 ) { return new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID ); } unsigned int collideFlags = (buildFlags & bits_BUILD_IGNORE_NPCS) ? GetOuter()->GetAITraceMask_BrushOnly() : GetOuter()->GetAITraceMask();
bool bCheckGround = (GetOuter()->CapabilitiesGet() & bits_CAP_SKIP_NAV_GROUND_CHECK) ? false : true;
if ( flTotalDist <= maxLocalNavDistance || ( buildFlags & bits_BUILD_UNLIMITED_DISTANCE ) ) { AIMoveTrace_t moveTrace;
AI_PROFILE_SCOPE_BEGIN( CAI_Pathfinder_BuildComplexRoute_Direct ); GetOuter()->GetMoveProbe()->MoveLimit( navType, vStart, vEnd, collideFlags, pTarget, (bCheckGround) ? 100 : 0, &moveTrace);
// If I was able to make the move...
if (!IsMoveBlocked(moveTrace)) { // It worked so return a route of length one to the endpoint
return new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID ); } // ...or the vEnd is thegoal and I'm within tolerance, just move to vEnd
if ( (buildFlags & bits_BUILD_GET_CLOSE) && (endFlags & bits_WP_TO_GOAL) && moveTrace.flDistObstructed <= goalTolerance ) { return new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID ); }
AI_PROFILE_SCOPE_END();
// -------------------------------------------------------------------
// Try to triangulate if requested
// -------------------------------------------------------------------
AI_PROFILE_SCOPE_BEGIN( CAI_Pathfinder_BuildComplexRoute_Triangulate ); if (buildFlags & bits_BUILD_TRIANG) { if ( !UseStrongOptimizations() || ( GetOuter()->GetState() == NPC_STATE_SCRIPT || GetOuter()->IsCurSchedule( SCHED_SCENE_GENERIC, false ) ) ) { float flTotalDist = ComputePathDistance( navType, vStart, vEnd );
AI_Waypoint_t *triangRoute = BuildTriangulationRoute(vStart, vEnd, pTarget, endFlags, nodeID, flYaw, flTotalDist - moveTrace.flDistObstructed, navType);
if (triangRoute) { return triangRoute; } } } AI_PROFILE_SCOPE_END();
// -------------------------------------------------------------------
// Try to giveway if requested
// -------------------------------------------------------------------
if (moveTrace.fStatus == AIMR_BLOCKED_NPC && (buildFlags & bits_BUILD_GIVEWAY)) { // If I can't get there even ignoring NPCs, don't bother to request a giveway
AIMoveTrace_t moveTrace2; GetOuter()->GetMoveProbe()->MoveLimit( navType, vStart, vEnd, GetOuter()->GetAITraceMask_BrushOnly(), pTarget, (bCheckGround) ? 100 : 0, &moveTrace2 );
if (!IsMoveBlocked(moveTrace2)) { // If I can clear the way return a route of length one to the target location
if ( CanGiveWay(vStart, vEnd, moveTrace.pObstruction) ) { return new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID ); } } } } return NULL; }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a crawl route between vStart
// and vEnd, ignoring entity pTarget
// Input :
// Output : Returns a route if successful or NULL if no local route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildCrawlRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw, float goalTolerance) { // Only allowed to jump to ground nodes
//return BuildSimpleRoute( NAV_CRAWL, vStart, vEnd, pTarget,
// endFlags, nodeID, NODE_GROUND, flYaw );
return BuildComplexRoute( NAV_CRAWL, vStart, vEnd, pTarget, endFlags, nodeID, buildFlags, flYaw, goalTolerance, MAX_LOCAL_NAV_DIST_GROUND[UseStrongOptimizations()] ); }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a jump route between vStart
// and vEnd, ignoring entity pTarget
// Input :
// Output : Returns a route if successful or NULL if no local route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildJumpRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw) { Vector vecDiff = vStart - vEnd;
if( fabs(vecDiff.z) <= 24.0f && vecDiff.Length2D() <= Square(600.0f) ) return NULL;
// Only allowed to jump to ground nodes
return BuildSimpleRoute( NAV_JUMP, vStart, vEnd, pTarget, endFlags, nodeID, NODE_GROUND, flYaw ); }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a climb route between vStart
// and vEnd, ignoring entity pTarget
// Input :
// Output : Returns a route if successful or NULL if no climb route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildClimbRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw) { // Only allowed to climb to climb nodes
return BuildSimpleRoute( NAV_CLIMB, vStart, vEnd, pTarget, endFlags, nodeID, NODE_CLIMB, flYaw ); }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a ground route between vStart
// and vEnd, ignoring entity pTarget the the given tolerance
// Input :
// Output : Returns a route if successful or NULL if no ground route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildGroundRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw, float goalTolerance) { return BuildComplexRoute( NAV_GROUND, vStart, vEnd, pTarget, endFlags, nodeID, buildFlags, flYaw, goalTolerance, MAX_LOCAL_NAV_DIST_GROUND[UseStrongOptimizations()] ); }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a fly route between vStart
// and vEnd, ignoring entity pTarget the the given tolerance
// Input :
// Output : Returns a route if successful or NULL if no ground route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildFlyRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float flYaw, float goalTolerance) { return BuildComplexRoute( NAV_FLY, vStart, vEnd, pTarget, endFlags, nodeID, buildFlags, flYaw, goalTolerance, MAX_LOCAL_NAV_DIST_FLY[UseStrongOptimizations()] ); }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a route between vStart and vEnd, requesting the
// pNPCBlocker to get out of the way
// Input :
// Output : Returns a route if successful or NULL if giveway failed
//-----------------------------------------------------------------------------
bool CAI_Pathfinder::CanGiveWay( const Vector& vStart, const Vector& vEnd, CBaseEntity *pBlocker) { // FIXME: make this a CAI_BaseNPC member function
CAI_BaseNPC *pNPCBlocker = pBlocker->MyNPCPointer(); if (pNPCBlocker && pNPCBlocker->edict()) { Disposition_t eDispBlockerToMe = pNPCBlocker->IRelationType( GetOuter() ); if ( ( eDispBlockerToMe == D_LI ) || ( eDispBlockerToMe == D_NU ) ) { return true; } return false;
// FIXME: this is called in route creation, not navigation. It shouldn't actually make
// anyone get out of their way, just see if they'll honor the request.
// things like locked doors, enemies and such should refuse, all others shouldn't.
// things like breakables should know who is trying to break them, though a door hidden behind
// some boxes shouldn't be known to the AI even though a route should connect through them but
// be turned off.
/*
Vector moveDir = (vEnd - vStart).Normalize(); Vector blockerDir = (pNPCBlocker->GetLocalOrigin() - vStart); float blockerDist = DotProduct(moveDir,blockerDir); Vector blockPos = vStart + (moveDir*blockerDist);
if (pNPCBlocker->RequestGiveWay ( m_owner->GetLocalOrigin(), blockPos, moveDir, m_owner->m_eHull)) { return true; } */ } return false; }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a triangulation route between vStart
// and vEnd, ignoring entity pTarget the the given tolerance and
// triangulating around a blocking object at blockDist
// Input :
// Output : Returns a route if successful or NULL if no local route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildTriangulationRoute( const Vector &vStart, // from where
const Vector &vEnd, // to where
const CBaseEntity *pTarget, // an entity I can ignore
int endFlags, // add these WP flags to the last waypoint
int nodeID, // node id for the last waypoint
float flYaw, // ideal yaw for the last waypoint
float flDistToBlocker,// how far away is the obstruction from the start?
Navigation_t navType) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildTriangulationRoute ); Vector vApex; if (!Triangulate(navType, vStart, vEnd, flDistToBlocker, pTarget, &vApex )) return NULL;
//-----------------------------------------------------------------------------
// it worked, create a route
//-----------------------------------------------------------------------------
AI_Waypoint_t *pWayPoint2 = new AI_Waypoint_t( vEnd, flYaw, navType, endFlags, nodeID );
// FIXME: Compute a reasonable yaw here
AI_Waypoint_t *waypoint1 = new AI_Waypoint_t( vApex, 0, navType, bits_WP_TO_DETOUR, NO_NODE ); waypoint1->SetNext(pWayPoint2);
return waypoint1; }
//-----------------------------------------------------------------------------
// Purpose: Get the next node (with wrapping) around a circularly wound path
// Input : nLastNode - The starting node
// nDirection - Direction we're moving
// nNumNodes - Total nodes in the chain
//-----------------------------------------------------------------------------
inline int GetNextPoint( int nLastNode, int nDirection, int nNumNodes ) { int nNextNode = nLastNode + nDirection; if ( nNextNode > (nNumNodes-1) ) nNextNode = 0; else if ( nNextNode < 0 ) nNextNode = (nNumNodes-1);
return nNextNode; }
//-----------------------------------------------------------------------------
// Purpose: Attempt to wind a route through a series of node points in a specified direction.
// Input : *vecCorners - Points to test between
// nNumCorners - Number of points to test
// &vecStart - Starting position
// &vecEnd - Ending position
// Output : Route through the points
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildRouteThroughPoints( Vector *vecPoints, int nNumPoints, int nDirection, int nStartIndex, int nEndIndex, Navigation_t navType, CBaseEntity *pTarget ) { AIMoveTrace_t endTrace; endTrace.fStatus = AIMR_OK;
CAI_MoveProbe *pMoveProbe = GetOuter()->GetMoveProbe();
AI_Waypoint_t *pFirstRoute = NULL; AI_Waypoint_t *pHeadRoute = NULL;
int nCurIndex = nStartIndex; int nNextIndex;
// FIXME: Must be able to move to the first position (these needs some parameterization)
pMoveProbe->MoveLimit( navType, GetOuter()->GetAbsOrigin(), vecPoints[nStartIndex], GetOuter()->GetAITraceMask(), pTarget, &endTrace ); if ( IsMoveBlocked( endTrace ) ) { // NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecPoints[nStartIndex], 8.0f, 255, 0, 0, 0, true, 4.0f );
return NULL; }
// NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecPoints[nStartIndex], 8.0f, 0, 255, 0, 0, true, 4.0f );
int nRunAwayCount = 0; while ( nRunAwayCount++ < nNumPoints ) { // Advance our index in the specified direction
nNextIndex = GetNextPoint( nCurIndex, nDirection, nNumPoints );
// Try and build a local route between the current and next point
pMoveProbe->MoveLimit( navType, vecPoints[nCurIndex], vecPoints[nNextIndex], GetOuter()->GetAITraceMask(), pTarget, &endTrace ); if ( IsMoveBlocked( endTrace ) ) { // TODO: Triangulate here if we failed?
// We failed, so give up
if ( pHeadRoute ) { DeleteAll( pHeadRoute ); }
// NDebugOverlay::HorzArrow( vecPoints[nCurIndex], vecPoints[nNextIndex], 8.0f, 255, 0, 0, 0, true, 4.0f );
return NULL; }
// NDebugOverlay::HorzArrow( vecPoints[nCurIndex], vecPoints[nNextIndex], 8.0f, 0, 255, 0, 0, true, 4.0f );
if ( pHeadRoute == NULL ) { // Start a new route head
pFirstRoute = pHeadRoute = new AI_Waypoint_t( vecPoints[nCurIndex], 0.0f, navType, bits_WP_TO_DETOUR, NO_NODE ); } else { // Link a new waypoint into the path
AI_Waypoint_t *pNewNode = new AI_Waypoint_t( vecPoints[nCurIndex], 0.0f, navType, bits_WP_TO_DETOUR|bits_WP_DONT_SIMPLIFY, NO_NODE ); pHeadRoute->SetNext( pNewNode ); pHeadRoute = pNewNode; }
// See if we're done
if ( nNextIndex == nEndIndex ) { AI_Waypoint_t *pNewNode = new AI_Waypoint_t( vecPoints[nEndIndex], 0.0f, navType, bits_WP_TO_DETOUR, NO_NODE ); pHeadRoute->SetNext( pNewNode ); pHeadRoute = pNewNode; break; }
// Advance one node
nCurIndex = nNextIndex; }
return pFirstRoute; }
//-----------------------------------------------------------------------------
// Purpose: Find the closest point in a list of points, to a specified position
// Input : &vecPosition - Position to test against
// *vecPoints - List of vectors we'll check
// nNumPoints - Number of points in the list
// Output : Index to the closest point in the list
//-----------------------------------------------------------------------------
inline int ClosestPointToPosition( const Vector &vecPosition, Vector *vecPoints, int nNumPoints ) { int nBestNode = -1; float flBestDistSqr = FLT_MAX; float flDistSqr; for ( int i = 0; i < nNumPoints; i++ ) { flDistSqr = ( vecPoints[i] - vecPosition ).LengthSqr(); if ( flDistSqr < flBestDistSqr ) { flBestDistSqr = flDistSqr; nBestNode = i; } }
return nBestNode; }
//-----------------------------------------------------------------------------
// Purpose: Find which winding through a circular list is shortest in physical distance travelled
// Input : &vecStart - Where we started from
// nStartPoint - Starting index into the points
// nEndPoint - Ending index into the points
// nNumPoints - Number of points in the list
// *vecPoints - List of vectors making up a list of points
//-----------------------------------------------------------------------------
inline int ShortestDirectionThroughPoints( const Vector &vecStart, int nStartPoint, int nEndPoint, Vector *vecPoints, int nNumPoints ) { const int nClockwise = 1; const int nCounterClockwise = -1;
// Find the quickest direction around the object
int nCurPoint = nStartPoint; int nNextPoint = GetNextPoint( nStartPoint, 1, nNumPoints );
float flStartDistSqr = ( vecStart - vecPoints[nStartPoint] ).LengthSqr(); float flDistanceSqr = flStartDistSqr;
// Try going clockwise first
for ( int i = 0; i < nNumPoints; i++ ) { flDistanceSqr += ( vecPoints[nCurPoint] - vecPoints[nNextPoint] ).LengthSqr();
if ( nNextPoint == nEndPoint ) break;
nNextPoint = GetNextPoint( nNextPoint, 1, nNumPoints ); }
// Save this to test against
float flBestDistanceSqr = flDistanceSqr;
// Start from the beginning again
flDistanceSqr = flStartDistSqr;
nCurPoint = nStartPoint; nNextPoint = GetNextPoint( nStartPoint, -1, nNumPoints );
// Now go the other way and see if it's shorter to do so
for ( int i = 0; i < nNumPoints; i++ ) { flDistanceSqr += ( vecPoints[nCurPoint] - vecPoints[nNextPoint] ).LengthSqr();
// We've gone over our maximum so we can't be shorter
if ( flDistanceSqr > flBestDistanceSqr ) break;
// We hit the end, we're shorter
if ( nNextPoint == nEndPoint ) return nCounterClockwise;
nNextPoint = GetNextPoint( nNextPoint, -1, nNumPoints ); }
return nClockwise; }
//-----------------------------------------------------------------------------
// Purpose: Attempt to build an avoidance route around an object using its OBB
// Currently this function is meant for NPCs moving around a vehicle,
// and is very specialized as such
//
// Output : Returns a route if successful or NULL if no local route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildOBBAvoidanceRoute( const Vector &vStart, const Vector &vEnd, const CBaseEntity *pObstruction, // obstruction to avoid
const CBaseEntity *pTarget, // target to ignore
Navigation_t navType ) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildOBBAvoidanceRoute );
// If the point we're navigating to is within our OBB, then fail
// TODO: We could potentially also just try to get as near as possible
if ( pObstruction->CollisionProp()->IsPointInBounds( vEnd ) ) return NULL;
// Find out how much we'll need to inflate the collision bounds to let us move past
Vector vecSize = pObstruction->CollisionProp()->OBBSize(); float flWidth = GetOuter()->GetHullWidth() * 0.5f;
float flWidthPercX = ( flWidth / vecSize.x ); float flWidthPercY = ( flWidth / vecSize.y );
// Find the points around the object, bloating it by our hull width
// The ordering of these corners wind clockwise around the object, starting at the top left
Vector vecPoints[4]; pObstruction->CollisionProp()->NormalizedToWorldSpace( Vector( -flWidthPercX, 1+flWidthPercY, 0.25f ), &vecPoints[0] ); pObstruction->CollisionProp()->NormalizedToWorldSpace( Vector( 1+flWidthPercX, 1+flWidthPercY, 0.25f ), &vecPoints[1] ); pObstruction->CollisionProp()->NormalizedToWorldSpace( Vector( 1+flWidthPercX, -flWidthPercY, 0.25f ), &vecPoints[2] ); pObstruction->CollisionProp()->NormalizedToWorldSpace( Vector( -flWidthPercX, -flWidthPercY, 0.25f ), &vecPoints[3] );
// Find the two points nearest our goals
int nStartPoint = ClosestPointToPosition( vStart, vecPoints, ARRAYSIZE( vecPoints ) ); int nEndPoint = ClosestPointToPosition( vEnd, vecPoints, ARRAYSIZE( vecPoints ) );
// We won't be able to build a route if we're moving no distance between points
if ( nStartPoint == nEndPoint ) return NULL;
// Find the shortest path around this wound polygon (direction is how to step through array)
int nDirection = ShortestDirectionThroughPoints( vStart, nStartPoint, nEndPoint, vecPoints, ARRAYSIZE( vecPoints ) );
// Attempt to build a route in our direction
AI_Waypoint_t *pRoute = BuildRouteThroughPoints( vecPoints, ARRAYSIZE(vecPoints), nDirection, nStartPoint, nEndPoint, navType, (CBaseEntity *) pTarget ); if ( pRoute == NULL ) { // Failed that way, so try the opposite
pRoute = BuildRouteThroughPoints( vecPoints, ARRAYSIZE(vecPoints), (-nDirection), nStartPoint, nEndPoint, navType, (CBaseEntity *) pTarget ); if ( pRoute == NULL ) return NULL; }
return pRoute; }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a local route (not using nodes) between vStart
// and vEnd, ignoring entity pTarget the the given tolerance
// Input :
// Output : Returns a route if successful or NULL if no local route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildLocalRoute(const Vector &vStart, const Vector &vEnd, const CBaseEntity *pTarget, int endFlags, int nodeID, int buildFlags, float goalTolerance) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildLocalRoute );
// Get waypoint yaw
float flYaw; if (nodeID != NO_NODE) { flYaw = GetNetwork()->GetNode(nodeID)->GetYaw(); } else { flYaw = 0; }
// Try a ground route if requested
if (buildFlags & bits_BUILD_GROUND) { AI_Waypoint_t *groundRoute = BuildGroundRoute(vStart,vEnd,pTarget,endFlags,nodeID,buildFlags,flYaw,goalTolerance);
if (groundRoute) { return groundRoute; } }
// Try a fly route if requested
if ( buildFlags & bits_BUILD_FLY ) { AI_Waypoint_t *flyRoute = BuildFlyRoute(vStart,vEnd,pTarget,endFlags,nodeID,buildFlags,flYaw,goalTolerance);
if (flyRoute) { return flyRoute; } }
// Try a crawl route if NPC can crawl and requested
if ((buildFlags & bits_BUILD_CRAWL) && (CapabilitiesGet() & bits_CAP_MOVE_CRAWL)) { AI_Waypoint_t *crawlRoute = BuildCrawlRoute(vStart,vEnd,pTarget,endFlags,nodeID,buildFlags,flYaw,goalTolerance);
if (crawlRoute) { return crawlRoute; } }
// Try a jump route if NPC can jump and requested
if ((buildFlags & bits_BUILD_JUMP) && (CapabilitiesGet() & bits_CAP_MOVE_JUMP)) { AI_Waypoint_t *jumpRoute = BuildJumpRoute(vStart,vEnd,pTarget,endFlags,nodeID,buildFlags,flYaw);
if (jumpRoute) { return jumpRoute; } }
// Try a climb route if NPC can climb and requested
if ((buildFlags & bits_BUILD_CLIMB) && (CapabilitiesGet() & bits_CAP_MOVE_CLIMB)) { AI_Waypoint_t *climbRoute = BuildClimbRoute(vStart,vEnd,pTarget,endFlags,nodeID,buildFlags,flYaw); if (climbRoute) { return climbRoute; } }
// Everything failed so return a NULL route
return NULL; }
//-----------------------------------------------------------------------------
// Purpose: Builds a route to the given vecGoal using either local movement
// or nodes
//-----------------------------------------------------------------------------
ConVar ai_no_local_paths( "ai_no_local_paths", "0" );
AI_Waypoint_t *CAI_Pathfinder::BuildRoute( const Vector &vStart, const Vector &vEnd, CBaseEntity *pTarget, float goalTolerance, Navigation_t curNavType, int nBuildFlags ) { Assert( ( nBuildFlags & ( bits_BUILD_GROUND | bits_BUILD_JUMP | bits_BUILD_FLY | bits_BUILD_CLIMB | bits_BUILD_CRAWL | bits_BUILD_GIVEWAY | bits_BUILD_TRIANG | bits_BUILD_IGNORE_NPCS | bits_BUILD_COLLIDE_NPCS ) ) == 0 ); nBuildFlags &= ~( bits_BUILD_GROUND | bits_BUILD_JUMP | bits_BUILD_FLY | bits_BUILD_CLIMB | bits_BUILD_CRAWL | bits_BUILD_GIVEWAY | bits_BUILD_TRIANG | bits_BUILD_IGNORE_NPCS | bits_BUILD_COLLIDE_NPCS );
bool bTryLocal = !ai_no_local_paths.GetBool() && ( ( nBuildFlags & bits_BUILD_NO_LOCAL_NAV ) == 0 );
// Set up build flags
if (curNavType == NAV_CLIMB) { // if I'm climbing, then only allow routes that are also climb routes
nBuildFlags |= bits_BUILD_CLIMB; bTryLocal = false; } else if ( (CapabilitiesGet() & bits_CAP_MOVE_FLY) || (CapabilitiesGet() & bits_CAP_MOVE_SWIM) ) { nBuildFlags |= (bits_BUILD_FLY | bits_BUILD_GIVEWAY | bits_BUILD_TRIANG); } else if (CapabilitiesGet() & bits_CAP_MOVE_GROUND) { nBuildFlags |= (bits_BUILD_GROUND | bits_BUILD_GIVEWAY | bits_BUILD_TRIANG); if ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) { nBuildFlags |= bits_BUILD_JUMP; } if ( CapabilitiesGet() & bits_CAP_MOVE_CRAWL ) { nBuildFlags |= bits_BUILD_CRAWL; } }
AI_Waypoint_t *pResult = NULL;
// First try a local route
if ( bTryLocal && CanUseLocalNavigation() ) { int nLocalBuildFlags = nBuildFlags; if ( CapabilitiesGet() & bits_CAP_NO_LOCAL_NAV_CRAWL ) { nLocalBuildFlags &= ~bits_BUILD_CRAWL; } pResult = BuildLocalRoute(vStart, vEnd, pTarget, bits_WP_TO_GOAL, NO_NODE, nLocalBuildFlags, goalTolerance); }
// If the fails, try a node route
if ( !pResult ) { pResult = BuildNodeRoute( vStart, vEnd, nBuildFlags, goalTolerance ); }
m_bIgnoreStaleLinks = false;
return pResult; }
void CAI_Pathfinder::UnlockRouteNodes( AI_Waypoint_t *pPath ) { CAI_Node *pNode; while ( pPath ) { if ( pPath->iNodeID != NO_NODE && ( pNode = GetNetwork()->GetNode(pPath->iNodeID) ) != NULL && pNode->IsLocked() ) pNode->Unlock(); pPath = pPath->GetNext(); } }
//-----------------------------------------------------------------------------
// Purpose: Attempts to build a radial route around the given center position
// over a given arc size
//
// Input : vStartPos - where route should start from
// vCenterPos - the center of the arc
// vGoalPos - ultimate goal position
// flRadius - radius of the arc
// flArc - how long should the path be (in degrees)
// bClockwise - the direction we are heading
// Output : The route
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildRadialRoute( const Vector &vStartPos, const Vector &vCenterPos, const Vector &vGoalPos, float flRadius, float flArc, float flStepDist, bool bClockwise, float goalTolerance, bool bAirRoute /*= false*/ ) { MARK_TASK_EXPENSIVE();
// ------------------------------------------------------------------------------
// Make sure we have a minimum distance between nodes. For the given
// radius, calculate the angular step necessary for this distance.
// IMPORTANT: flStepDist must be large enough that given the
// NPC's movment speed that it can come to a stop
// ------------------------------------------------------------------------------
float flAngleStep = 2.0f * atan((0.5f*flStepDist)/flRadius);
// Flip direction if clockwise
if ( bClockwise ) { flArc *= -1; flAngleStep *= -1; }
// Calculate the start angle on the arc in world coordinates
Vector vStartDir = ( vStartPos - vCenterPos ); VectorNormalize( vStartDir );
// Get our control angles
float flStartAngle = DEG2RAD(UTIL_VecToYaw(vStartDir)); float flEndAngle = flStartAngle + DEG2RAD(flArc);
// Offset set our first node by one arc step so NPC doesn't run perpendicular to the arc when starting a different radius
flStartAngle += flAngleStep;
AI_Waypoint_t* pHeadRoute = NULL; // Pointer to the beginning of the route chains
AI_Waypoint_t* pNextRoute = NULL; // Next leg of the route
AI_Waypoint_t* pLastRoute = NULL; // The last route chain added to the head
Vector vLastPos = vStartPos; // Last position along the arc in worldspace
int fRouteBits = ( bAirRoute ) ? bits_BUILD_FLY : bits_BUILD_GROUND; // Whether this is an air route or not
float flCurAngle = flStartAngle; // Starting angle
Vector vNextPos; // Make sure that we've got somewhere to go. This generally means your trying to walk too small an arc.
Assert( ( bClockwise && flCurAngle > flEndAngle ) || ( !bClockwise && flCurAngle < flEndAngle ) );
// Start iterating through our arc
while( 1 ) { // See if we've ended our run
if ( ( bClockwise && flCurAngle <= flEndAngle ) || ( !bClockwise && flCurAngle >= flEndAngle ) ) break; // Get our next position along the arc
vNextPos = vCenterPos; vNextPos.x += flRadius * cos( flCurAngle ); vNextPos.y += flRadius * sin( flCurAngle );
// Build a route from the last position to the current one
pNextRoute = BuildLocalRoute( vLastPos, vNextPos, NULL, NULL, NO_NODE, fRouteBits, goalTolerance); // If we can't find a route, we failed
if ( pNextRoute == NULL ) return NULL;
// Don't simplify the route (otherwise we'll cut corners where we don't want to!
pNextRoute->ModifyFlags( bits_WP_DONT_SIMPLIFY, true ); if ( pHeadRoute ) { // Tack the routes together
AddWaypointLists( pHeadRoute, pNextRoute ); } else { // Otherwise we're now the previous route
pHeadRoute = pNextRoute; } // Move our position
vLastPos = vNextPos; pLastRoute = pNextRoute;
// Move our current angle
flCurAngle += flAngleStep; }
// NOTE: We could also simply build a local route with no curve, but it's unlikely that's what was intended by the caller
if ( pHeadRoute == NULL ) return NULL;
// Append a path to the final position
pLastRoute = BuildLocalRoute( vLastPos, vGoalPos, NULL, NULL, NO_NODE, bAirRoute ? bits_BUILD_FLY : bits_BUILD_GROUND, goalTolerance ); if ( pLastRoute == NULL ) return NULL;
// Allow us to simplify the last leg of the route
pLastRoute->ModifyFlags( bits_WP_DONT_SIMPLIFY, false ); pLastRoute->ModifyFlags( bits_WP_TO_GOAL, true );
// Add them together
AddWaypointLists( pHeadRoute, pLastRoute ); // Give back the complete route
return pHeadRoute; }
//-----------------------------------------------------------------------------
// Checks a stale navtype route
//-----------------------------------------------------------------------------
bool CAI_Pathfinder::CheckStaleNavTypeRoute( Navigation_t navType, const Vector &vStart, const Vector &vEnd ) { AIMoveTrace_t moveTrace; GetOuter()->GetMoveProbe()->MoveLimit( navType, vStart, vEnd, GetOuter()->GetAITraceMask(), NULL, 100, AIMLF_IGNORE_TRANSIENTS, &moveTrace);
// Is the direct route clear?
if (!IsMoveBlocked(moveTrace)) { return true; }
// Next try to triangulate
// FIXME: Since blocked dist is an unreliable number, this computation is bogus
Vector vecDelta; VectorSubtract( vEnd, vStart, vecDelta ); float flTotalDist = vecDelta.Length();
Vector vApex; if (Triangulate( navType, vStart, vEnd, flTotalDist - moveTrace.flDistObstructed, NULL, &vApex )) { return true; }
// Try a giveway request, if I can get there ignoring NPCs
if ( moveTrace.pObstruction && moveTrace.pObstruction->MyNPCPointer() ) { GetOuter()->GetMoveProbe()->MoveLimit( navType, vStart, vEnd, GetOuter()->GetAITraceMask_BrushOnly(), NULL, &moveTrace);
if (!IsMoveBlocked(moveTrace)) { return true; } }
return false; }
//-----------------------------------------------------------------------------
// Purpose: Checks if a local route (not using nodes) between vStart
// and vEnd exists using the moveType
// Input :
// Output : Returns a route if sucessful or NULL if no local route was possible
//-----------------------------------------------------------------------------
bool CAI_Pathfinder::CheckStaleRoute(const Vector &vStart, const Vector &vEnd, int moveTypes) { AI_PROFILE_SCOPE( CAI_Pathfinder_CheckStaleRoute );
// -------------------------------------------------------------------
// First try to go there directly
// -------------------------------------------------------------------
if (moveTypes & bits_CAP_MOVE_GROUND) { if (CheckStaleNavTypeRoute( NAV_GROUND, vStart, vEnd )) return true; }
// -------------------------------------------------------------------
// First try to go there directly
// -------------------------------------------------------------------
if (moveTypes & bits_CAP_MOVE_FLY) { if (CheckStaleNavTypeRoute( NAV_FLY, vStart, vEnd )) return true; }
// --------------------------------------------------------------
// Try to jump if we can jump to a node
// --------------------------------------------------------------
if (moveTypes & bits_CAP_MOVE_JUMP) { AIMoveTrace_t moveTrace; GetOuter()->GetMoveProbe()->MoveLimit( NAV_JUMP, vStart, vEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace); if (!IsMoveBlocked(moveTrace)) { return true; } else { // Can't tell jump up from jump down at this point
GetOuter()->GetMoveProbe()->MoveLimit( NAV_JUMP, vEnd, vStart, GetOuter()->GetAITraceMask(), NULL, &moveTrace); if (!IsMoveBlocked(moveTrace)) return true; } }
// --------------------------------------------------------------
// Try to climb if we can climb to a node
// --------------------------------------------------------------
if (moveTypes & bits_CAP_MOVE_CLIMB) { AIMoveTrace_t moveTrace; GetOuter()->GetMoveProbe()->MoveLimit( NAV_CLIMB, vStart, vEnd, GetOuter()->GetAITraceMask(), NULL, &moveTrace); if (!IsMoveBlocked(moveTrace)) { return true; } }
// Man do we suck! Couldn't get there by any route
return false; }
//-----------------------------------------------------------------------------
#define MAX_NODE_TRIES 4
#define MAX_TRIANGULATIONS 2
class CPathfindNearestNodeFilter : public INearestNodeFilter { public: CPathfindNearestNodeFilter( CAI_Pathfinder *pPathfinder, const Vector &vGoal, bool bToNode, int buildFlags, float goalTolerance, bool bAvoidObstacles ) : m_pPathfinder( pPathfinder ), m_nTries(0), m_vGoal( vGoal ), m_bToNode( bToNode ), m_goalTolerance( goalTolerance ), m_moveTypes( buildFlags & ( bits_BUILD_GROUND | bits_BUILD_FLY | bits_BUILD_JUMP | bits_BUILD_CLIMB | bits_BUILD_CRAWL ) ), m_bAvoidObstacles( bAvoidObstacles ), m_pRoute( NULL ) { COMPILE_TIME_ASSERT( bits_BUILD_GROUND == bits_CAP_MOVE_GROUND && bits_BUILD_FLY == bits_CAP_MOVE_FLY && bits_BUILD_JUMP == bits_CAP_MOVE_JUMP && bits_BUILD_CLIMB == bits_CAP_MOVE_CLIMB && bits_BUILD_CRAWL == bits_CAP_MOVE_CRAWL ); }
bool IsValid( CAI_Node *pNode ) { int nStaleLinks = 0; if ( !m_pPathfinder->m_bIgnoreStaleLinks ) { int hull = m_pPathfinder->GetOuter()->GetHullType(); for ( int i = 0; i < pNode->NumLinks(); i++ ) { CAI_Link *pLink = pNode->GetLinkByIndex( i ); if ( pLink->m_LinkInfo & ( bits_LINK_STALE_SUGGESTED | ( bits_LINK_OFF | bits_LINK_ASW_BASHABLE ) ) ) { nStaleLinks++; } else if ( ( pLink->m_iAcceptedMoveTypes[hull] & m_moveTypes ) == 0 ) { nStaleLinks++; } } }
if ( nStaleLinks && nStaleLinks == pNode->NumLinks() ) return false;
if ( m_bAvoidObstacles ) { if ( CAI_LocalNavigator::IsSegmentBlockedByGlobalObstacles( pNode->GetOrigin(), m_vGoal ) ) return false; }
int buildFlags = ( m_nTries < MAX_TRIANGULATIONS ) ? ( bits_BUILD_IGNORE_NPCS | bits_BUILD_TRIANG ) : bits_BUILD_IGNORE_NPCS;
if ( m_bToNode ) m_pRoute = m_pPathfinder->RouteToNode( m_vGoal, buildFlags, pNode->GetId(), m_goalTolerance ); else m_pRoute = m_pPathfinder->RouteFromNode( m_vGoal, buildFlags, pNode->GetId(), m_goalTolerance );
m_nTries++;
return ( m_pRoute != NULL ); }
bool ShouldContinue() { return ( !m_pRoute && m_nTries < MAX_NODE_TRIES ); }
CAI_Pathfinder *m_pPathfinder; int m_nTries; Vector m_vGoal; bool m_bToNode; float m_goalTolerance; int m_moveTypes; bool m_bAvoidObstacles; AI_Waypoint_t * m_pRoute; };
AI_Waypoint_t *CAI_Pathfinder::BuildNearestNodeRoute( const Vector &vGoal, bool bToNode, int buildFlags, float goalTolerance, int *pNearestNode ) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildNearestNodeRoute );
CPathfindNearestNodeFilter filter( this, vGoal, bToNode, buildFlags, goalTolerance, true ); *pNearestNode = GetNetwork()->NearestNodeToPoint( GetOuter(), vGoal, true, &filter );
return filter.m_pRoute; }
//-----------------------------------------------------------------------------
// Purpose: Attemps to build a node route between vStart and vEnd
// Input :
// Output : Returns a route if sucessful or NULL if no node route was possible
//-----------------------------------------------------------------------------
AI_Waypoint_t *CAI_Pathfinder::BuildNodeRoute(const Vector &vStart, const Vector &vEnd, int buildFlags, float goalTolerance) { AI_PROFILE_SCOPE( CAI_Pathfinder_BuildNodeRoute );
// ----------------------------------------------------------------------
// Make sure network has nodes
// ----------------------------------------------------------------------
if (GetNetwork()->NumNodes() == 0) return NULL;
// ----------------------------------------------------------------------
// Find the nearest source node
// ----------------------------------------------------------------------
int srcID; AI_Waypoint_t *srcRoute = BuildNearestNodeRoute( vStart, true, buildFlags, goalTolerance, &srcID ); if ( !srcRoute ) { DbgNavMsg1( GetOuter(), "Node pathfind failed, no route to source %d\n", srcID ); return NULL; }
// ----------------------------------------------------------------------
// Find the nearest destination node
// ----------------------------------------------------------------------
int destID; AI_Waypoint_t *destRoute = BuildNearestNodeRoute( vEnd, false, buildFlags, goalTolerance, &destID ); if ( !destRoute ) { DeleteAll( srcRoute ); DbgNavMsg1( GetOuter(), "Node pathfind failed, no route to dest %d\n", destID ); return NULL; }
// ----------------------------------------------------------------------
// If source and destination are the same, we can bypass finding a route
// ----------------------------------------------------------------------
if (destID == srcID) { AddWaypointLists(srcRoute,destRoute); DbgNavMsg( GetOuter(), "Node pathfind succeeded: dest == source\n"); return srcRoute; }
// If nodes are not connected by network graph, no route is possible
if (!GetNetwork()->IsConnected(srcID, destID)) return NULL;
AI_Waypoint_t *path = FindBestPath(srcID, destID);
if (!path) { DeleteAll(srcRoute); DeleteAll(destRoute); DbgNavMsg2( GetOuter(), "Node pathfind failed, no route between %d and %d\n", srcID, destID ); return NULL; }
// Now put all the pieces together to form our route
AddWaypointLists(srcRoute,path); AddWaypointLists(srcRoute,destRoute);
DbgNavMsg( GetOuter(), "Node pathfind succeeded\n"); return srcRoute; }
//-----------------------------------------------------------------------------
// Test the triangulation route...
//-----------------------------------------------------------------------------
#ifdef _WIN32
#pragma warning (disable:4701)
#endif
bool CAI_Pathfinder::TestTriangulationRoute( Navigation_t navType, const Vector& vecStart, const Vector &vecApex, const Vector &vecEnd, const CBaseEntity *pTargetEnt, AIMoveTrace_t *pStartTrace ) { AIMoveTrace_t endTrace; endTrace.fStatus = AIMR_OK; // just to make the debug overlay code easy
// Check the triangulation
CAI_MoveProbe *pMoveProbe = GetOuter()->GetMoveProbe();
bool bPathClear = false;
// See if we can get from the start point to the triangle apex
if ( pMoveProbe->MoveLimit(navType, vecStart, vecApex, GetOuter()->GetAITraceMask(), pTargetEnt, pStartTrace ) ) { // Ok, we got from the start to the triangle apex, now try
// the triangle apex to the end
if ( pMoveProbe->MoveLimit(navType, vecApex, vecEnd, GetOuter()->GetAITraceMask(), pTargetEnt, &endTrace ) ) { bPathClear = true; } }
// Debug mode: display the tested path...
if (GetOuter()->m_debugOverlays & OVERLAY_NPC_TRIANGULATE_BIT) m_TriDebugOverlay.AddTriOverlayLines( vecStart, vecApex, vecEnd, *pStartTrace, endTrace, bPathClear);
return bPathClear; }
#ifdef _WIN32
#pragma warning (default:4701)
#endif
//-----------------------------------------------------------------------------
// Purpose: tries to overcome local obstacles by triangulating a path around them.
// Input : flDist is is how far the obstruction that we are trying
// to triangulate around is from the npc
// Output :
//-----------------------------------------------------------------------------
// FIXME: this has no concept that the blocker may not be exactly along the vecStart, vecEnd vector.
// FIXME: it should take a position (and size?) to avoid
// FIXME: this does the position checks in the same order as GiveWay() so they tend to fight each other when both are active
#define MAX_TRIAGULATION_DIST (32*12)
bool CAI_Pathfinder::Triangulate( Navigation_t navType, const Vector &vecStart, const Vector &vecEndIn, float flDistToBlocker, const CBaseEntity *pTargetEnt, Vector *pApex ) { if ( GetOuter()->IsFlaggedEfficient() ) return false;
Assert( pApex );
AI_PROFILE_SCOPE( CAI_Pathfinder_Triangulate );
Vector vecForward, vecUp, vecPerpendicular; VectorSubtract( vecEndIn, vecStart, vecForward ); float flTotalDist = VectorNormalize( vecForward );
Vector vecEnd;
// If we're walking, then don't try to triangulate over large distances
if ( navType != NAV_FLY && flTotalDist > MAX_TRIAGULATION_DIST) { vecEnd = vecForward * MAX_TRIAGULATION_DIST; flTotalDist = MAX_TRIAGULATION_DIST; if ( !GetOuter()->GetMoveProbe()->MoveLimit(navType, vecEnd, vecEndIn, GetOuter()->GetAITraceMask(), pTargetEnt) ) { return false; }
} else vecEnd = vecEndIn;
// Compute a direction vector perpendicular to the desired motion direction
if ( 1.0f - fabs(vecForward.z) > 1e-3 ) { vecUp.Init( 0, 0, 1 ); CrossProduct( vecForward, vecUp, vecPerpendicular ); // Orthogonal to facing
} else { vecUp.Init( 0, 1, 0 ); vecPerpendicular.Init( 1, 0, 0 ); }
// Grab the size of the navigation bounding box
float sizeX = 0.5f * NAI_Hull::Length(GetHullType()); float sizeZ = 0.5f * NAI_Hull::Height(GetHullType());
// start checking right about where the object is, picking two equidistant
// starting points, one on the left, one on the right. As we progress
// through the loop, we'll push these away from the obstacle, hoping to
// find a way around on either side. m_vecSize.x is added to the ApexDist
// in order to help select an apex point that insures that the NPC is
// sufficiently past the obstacle before trying to turn back onto its original course.
if (GetOuter()->m_debugOverlays & OVERLAY_NPC_TRIANGULATE_BIT) { m_TriDebugOverlay.FadeTriOverlayLines(); }
float flApexDist = flDistToBlocker + sizeX; if (flApexDist > flTotalDist) { flApexDist = flTotalDist; }
// Compute triangulation apex points (NAV_FLY attempts vertical triangulation too)
Vector vecDelta[2]; Vector vecApex[4]; float pApexDist[4];
Vector vecCenter; int nNumToTest = 2; VectorMultiply( vecPerpendicular, sizeX, vecDelta[0] );
VectorMA( vecStart, flApexDist, vecForward, vecCenter ); VectorSubtract( vecCenter, vecDelta[0], vecApex[0] ); VectorAdd( vecCenter, vecDelta[0], vecApex[1] ); vecDelta[0] *= 2.0f; pApexDist[0] = pApexDist[1] = flApexDist;
if (navType == NAV_FLY) { VectorMultiply( vecUp, 3.0f * sizeZ, vecDelta[1] ); VectorSubtract( vecCenter, vecDelta[1], vecApex[2] ); VectorAdd( vecCenter, vecDelta[1], vecApex[3] ); pApexDist[2] = pApexDist[3] = flApexDist; nNumToTest = 4; }
AIMoveTrace_t moveTrace; for (int i = 0; i < 2; ++i ) { // NOTE: Do reverse order so fliers try to move over the top first
for (int j = nNumToTest; --j >= 0; ) { if (TestTriangulationRoute(navType, vecStart, vecApex[j], vecEnd, pTargetEnt, &moveTrace)) { *pApex = vecApex[j]; return true; }
// Here, the starting half of the triangle was blocked. Lets
// pull back the apex toward the start...
if (IsMoveBlocked(moveTrace)) { Vector vecStartToObstruction; VectorSubtract( moveTrace.vEndPosition, vecStart, vecStartToObstruction ); float flDistToObstruction = DotProduct( vecStartToObstruction, vecForward );
float flNewApexDist = pApexDist[j]; if (pApexDist[j] > flDistToObstruction) flNewApexDist = flDistToObstruction;
VectorMA( vecApex[j], flNewApexDist - pApexDist[j], vecForward, vecApex[j] ); pApexDist[j] = flNewApexDist; }
// NOTE: This has to occur *after* the code above because
// the above code uses vecApex for some distance computations
if (j & 0x1) vecApex[j] += vecDelta[j >> 1]; else vecApex[j] -= vecDelta[j >> 1]; } }
return false; }
//-----------------------------------------------------------------------------
// Purpose: Triangulation debugging
//-----------------------------------------------------------------------------
void CAI_Pathfinder::DrawDebugGeometryOverlays(int npcDebugOverlays) { m_TriDebugOverlay.Draw(npcDebugOverlays); }
void CAI_Pathfinder::CTriDebugOverlay::Draw(int npcDebugOverlays) { if (m_debugTriOverlayLine) { if ( npcDebugOverlays & OVERLAY_NPC_TRIANGULATE_BIT) { for (int i=0;i<NUM_NPC_DEBUG_OVERLAYS;i++) { if (m_debugTriOverlayLine[i]->draw) { NDebugOverlay::Line(m_debugTriOverlayLine[i]->origin, m_debugTriOverlayLine[i]->dest, m_debugTriOverlayLine[i]->r, m_debugTriOverlayLine[i]->g, m_debugTriOverlayLine[i]->b, m_debugTriOverlayLine[i]->noDepthTest, 0); } } } else { ClearTriOverlayLines(); } } }
void CAI_Pathfinder::CTriDebugOverlay::AddTriOverlayLines( const Vector &vecStart, const Vector &vecApex, const Vector &vecEnd, const AIMoveTrace_t &startTrace, const AIMoveTrace_t &endTrace, bool bPathClear ) { static unsigned char s_TriangulationColor[2][3] = { { 255, 0, 0 }, { 0, 255, 0 } };
unsigned char *c = s_TriangulationColor[bPathClear];
AddTriOverlayLine(vecStart, vecApex, c[0],c[1],c[2], false); AddTriOverlayLine(vecApex, vecEnd, c[0],c[1],c[2], false);
// If we've blocked, draw an X where we were blocked...
if (IsMoveBlocked(startTrace.fStatus)) { Vector pt1, pt2; pt1 = pt2 = startTrace.vEndPosition;
pt1.x -= 10; pt1.y -= 10; pt2.x += 10; pt2.y += 10; AddTriOverlayLine(pt1, pt2, c[0],c[1],c[2], false);
pt1.x += 20; pt2.x -= 20; AddTriOverlayLine(pt1, pt2, c[0],c[1],c[2], false); }
if (IsMoveBlocked(endTrace.fStatus)) { Vector pt1, pt2; pt1 = pt2 = endTrace.vEndPosition;
pt1.x -= 10; pt1.y -= 10; pt2.x += 10; pt2.y += 10; AddTriOverlayLine(pt1, pt2, c[0],c[1],c[2], false);
pt1.x += 20; pt2.x -= 20; AddTriOverlayLine(pt1, pt2, c[0],c[1],c[2], false); } }
void CAI_Pathfinder::CTriDebugOverlay::ClearTriOverlayLines(void) { if (m_debugTriOverlayLine) { for (int i=0;i<NUM_NPC_DEBUG_OVERLAYS;i++) { m_debugTriOverlayLine[i]->draw = false; } } } void CAI_Pathfinder::CTriDebugOverlay::FadeTriOverlayLines(void) { if (m_debugTriOverlayLine) { for (int i=0;i<NUM_NPC_DEBUG_OVERLAYS;i++) { m_debugTriOverlayLine[i]->r *= 0.5; m_debugTriOverlayLine[i]->g *= 0.5; m_debugTriOverlayLine[i]->b *= 0.5; } } } void CAI_Pathfinder::CTriDebugOverlay::AddTriOverlayLine(const Vector &origin, const Vector &dest, int r, int g, int b, bool noDepthTest) { if (!m_debugTriOverlayLine) { m_debugTriOverlayLine = new OverlayLine_t*[NUM_NPC_DEBUG_OVERLAYS]; for (int i=0;i<NUM_NPC_DEBUG_OVERLAYS;i++) { m_debugTriOverlayLine[i] = new OverlayLine_t; } } static int overCounter = 0;
if (overCounter >= NUM_NPC_DEBUG_OVERLAYS) { overCounter = 0; } m_debugTriOverlayLine[overCounter]->origin = origin; m_debugTriOverlayLine[overCounter]->dest = dest; m_debugTriOverlayLine[overCounter]->r = r; m_debugTriOverlayLine[overCounter]->g = g; m_debugTriOverlayLine[overCounter]->b = b; m_debugTriOverlayLine[overCounter]->noDepthTest = noDepthTest; m_debugTriOverlayLine[overCounter]->draw = true; overCounter++;
}
//-----------------------------------------------------------------------------
|