/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ #include "g_headers.h" #include #include "b_local.h" #include "g_navigator.h" #include "g_nav.h" extern int NAVNEW_ClearPathBetweenPoints(vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask); extern qboolean NAV_CheckNodeFailedForEnt( gentity_t *ent, int nodeNum ); extern qboolean G_EntIsUnlockedDoor( int entityNum ); extern qboolean G_EntIsDoor( int entityNum ); extern qboolean G_EntIsBreakable( int entityNum ); extern qboolean G_EntIsRemovableUsable( int entNum ); extern cvar_t *d_altRoutes; extern cvar_t *d_patched; static vec3_t wpMaxs = { 16, 16, 32 }; static vec3_t wpMins = { -16, -16, -24+STEPSIZE };//WTF: was 16??!!! static byte CHECKED_NO = 0; static byte CHECKED_FAILED = 1; static byte CHECKED_PASSED = 2; /* ------------------------- CEdge ------------------------- */ CEdge::CEdge( void ) { CEdge( -1, -1, -1 ); } CEdge::CEdge( int first, int second, int cost ) { m_first = first; m_second = second; m_cost = cost; } CEdge::~CEdge( void ) { } /* ------------------------- CNode ------------------------- */ CNode::CNode( void ) { m_numEdges = 0; m_radius = 0; m_ranks = NULL; } CNode::~CNode( void ) { m_edges.clear(); if ( m_ranks ) delete [] m_ranks; } /* ------------------------- Create ------------------------- */ CNode *CNode::Create( vec3_t position, int flags, int radius, int ID ) { CNode *node = new CNode; VectorCopy( position, node->m_position ); node->m_flags = flags; node->m_ID = ID; node->m_radius = radius; return node; } /* ------------------------- Create ------------------------- */ CNode *CNode::Create( void ) { return new CNode; } /* ------------------------- AddEdge ------------------------- */ void CNode::AddEdge( int ID, int cost, int flags ) { if ( m_numEdges ) {//already have at least 1 //see if it exists already edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( (*ei).ID == ID ) {//found it (*ei).cost = cost; (*ei).flags = flags; return; } } } edge_t edge; edge.ID = ID; edge.cost = cost; edge.flags = flags; STL_INSERT( m_edges, edge ); m_numEdges++; assert( m_numEdges < 9 );//8 is the max } /* ------------------------- GetEdge ------------------------- */ int CNode::GetEdgeNumToNode( int ID ) { int count = 0; edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( (*ei).ID == ID ) { return count; } count++; } return -1; } /* ------------------------- AddRank ------------------------- */ void CNode::AddRank( int ID, int rank ) { assert( m_ranks ); m_ranks[ ID ] = rank; } /* ------------------------- Draw ------------------------- */ void CNode::Draw( qboolean showRadius ) { CG_DrawNode( m_position, NODE_NORMAL ); if( showRadius ) { CG_DrawRadius( m_position, m_radius, NODE_NORMAL ); } } /* ------------------------- GetEdge ------------------------- */ int CNode::GetEdge( int edgeNum ) { if ( edgeNum > m_numEdges ) return -1; int count = 0; edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( count == edgeNum ) { return (*ei).ID; } count++; } return -1; } /* ------------------------- GetEdgeCost ------------------------- */ int CNode::GetEdgeCost( int edgeNum ) { if ( edgeNum > m_numEdges ) return Q3_INFINITE; // return -1; int count = 0; edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( count == edgeNum ) { return (*ei).cost; } count++; } return Q3_INFINITE; // return -1; } /* ------------------------- GetEdgeFlags ------------------------- */ unsigned char CNode::GetEdgeFlags( int edgeNum ) { if ( edgeNum > m_numEdges ) return 0; int count = 0; edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( count == edgeNum ) { return (*ei).flags; } count++; } return 0; } /* ------------------------- SetEdgeFlags ------------------------- */ void CNode::SetEdgeFlags( int edgeNum, int newFlags ) { if ( edgeNum > m_numEdges ) return; int count = 0; edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { if ( count == edgeNum ) { (*ei).flags = newFlags; return; } count++; } } /* ------------------------- InitRanks ------------------------- */ void CNode::InitRanks( int size ) { //Clear it if it's already allocated if ( m_ranks != NULL ) { delete [] m_ranks; m_ranks = NULL; } m_ranks = new int[size]; memset( m_ranks, -1, sizeof(int)*size ); } /* ------------------------- GetRank ------------------------- */ int CNode::GetRank( int ID ) { assert( m_ranks ); return m_ranks[ ID ]; } /* ------------------------- Save ------------------------- */ int CNode::Save( int numNodes, fileHandle_t file ) { int i; //Write out the header uint32_t header = NODE_HEADER_ID; gi.FS_Write( &header, sizeof( header ), file ); //Write out the basic information for ( i = 0; i < 3; i++ ) gi.FS_Write( &m_position[i], sizeof( float ), file ); gi.FS_Write( &m_flags, sizeof( m_flags ), file ); gi.FS_Write( &m_ID, sizeof( m_ID ), file ); gi.FS_Write( &m_radius, sizeof( m_radius ), file ); //Write out the edge information gi.FS_Write( &m_numEdges, sizeof( m_numEdges ), file ); edge_v::iterator ei; STL_ITERATE( ei, m_edges ) { gi.FS_Write( &(*ei), sizeof( edge_t ), file ); } //Write out the node ranks gi.FS_Write( &numNodes, sizeof( numNodes ), file ); for ( i = 0; i < numNodes; i++ ) { gi.FS_Write( &m_ranks[i], sizeof( int ), file ); } return true; } /* ------------------------- Load ------------------------- */ int CNode::Load( int numNodes, fileHandle_t file ) { uint32_t header; int i; gi.FS_Read( &header, sizeof(header), file ); //Validate the header if ( header != NODE_HEADER_ID ) return false; //Get the basic information for ( i = 0; i < 3; i++ ) gi.FS_Read( &m_position[i], sizeof( float ), file ); gi.FS_Read( &m_flags, sizeof( m_flags ), file ); gi.FS_Read( &m_ID, sizeof( m_ID ), file ); gi.FS_Read( &m_radius, sizeof( m_radius ), file ); //Get the edge information gi.FS_Read( &m_numEdges, sizeof( m_numEdges ), file ); for ( i = 0; i < m_numEdges; i++ ) { edge_t edge; gi.FS_Read( &edge, sizeof( edge_t ), file ); STL_INSERT( m_edges, edge ); } //Read the node ranks int numRanks; gi.FS_Read( &numRanks, sizeof( numRanks ), file ); //Allocate the memory InitRanks( numRanks ); for ( i = 0; i < numRanks; i++ ) { gi.FS_Read( &m_ranks[i], sizeof( int ), file ); } return true; } /* ------------------------- CNavigator ------------------------- */ CNavigator::CNavigator( void ) { } CNavigator::~CNavigator( void ) { } /* ------------------------- FlagAllNodes ------------------------- */ void CNavigator::FlagAllNodes( int newFlag ) { node_v::iterator ni; STL_ITERATE( ni, m_nodes ) { (*ni)->AddFlag( newFlag ); } } /* ------------------------- GetChar ------------------------- */ char CNavigator::GetChar( fileHandle_t file ) { char value; gi.FS_Read( &value, sizeof( value ), file ); return value; } /* ------------------------- GetInt ------------------------- */ int CNavigator::GetInt( fileHandle_t file ) { int value; gi.FS_Read( &value, sizeof( value ), file ); return value; } /* ------------------------- GetFloat ------------------------- */ float CNavigator::GetFloat( fileHandle_t file ) { float value; gi.FS_Read( &value, sizeof( value ), file ); return value; } /* ------------------------- GetLong ------------------------- */ uint32_t CNavigator::GetLong( fileHandle_t file ) { uint32_t value; gi.FS_Read( &value, sizeof( value ), file ); return value; } /* ------------------------- Init ------------------------- */ void CNavigator::Init( void ) { Free(); } /* ------------------------- Free ------------------------- */ void CNavigator::Free( void ) { node_v::iterator ni; STL_ITERATE( ni, m_nodes ) { delete (*ni); } m_nodes.clear(); } /* ------------------------- Load ------------------------- */ bool CNavigator::Load( const char *filename, int checksum ) { fileHandle_t file; //Attempt to load the file gi.FS_FOpenFile( va( "maps/%s.nav", filename ), &file, FS_READ ); //See if we succeeded if ( file == NULL_FILE ) return false; //Check the header id uint32_t navID = GetLong( file ); if ( navID != NAV_HEADER_ID ) { gi.FS_FCloseFile( file ); return false; } //Check the checksum to see if this file is out of date int check = GetInt( file ); if ( check != checksum ) { gi.FS_FCloseFile( file ); return false; } int numNodes = GetInt( file ); for ( int i = 0; i < numNodes; i++ ) { CNode *node = CNode::Create(); if ( node->Load( numNodes, file ) == false ) { gi.FS_FCloseFile( file ); return false; } STL_INSERT( m_nodes, node ); } //read in the failed edges gi.FS_Read( &failedEdges, sizeof( failedEdges ), file ); for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) { m_edgeLookupMap.insert(std::pair(failedEdges[j].startID, j)); } gi.FS_FCloseFile( file ); return true; } /* ------------------------- Save ------------------------- */ bool CNavigator::Save( const char *filename, int checksum ) { fileHandle_t file; //Attempt to load the file gi.FS_FOpenFile( va( "maps/%s.nav", filename ), &file, FS_WRITE ); if ( file == NULL_FILE ) return false; //Write out the header id uint32_t id = NAV_HEADER_ID; gi.FS_Write( &id, sizeof (id), file ); //Write out the checksum gi.FS_Write( &checksum, sizeof( checksum ), file ); int numNodes = m_nodes.size(); //Write out the number of nodes to follow gi.FS_Write( &numNodes, sizeof(numNodes), file ); //Write out all the nodes node_v::iterator ni; STL_ITERATE( ni, m_nodes ) { (*ni)->Save( numNodes, file ); } //write out failed edges gi.FS_Write( &failedEdges, sizeof( failedEdges ), file ); gi.FS_FCloseFile( file ); return true; } /* ------------------------- AddRawPoint ------------------------- */ int CNavigator::AddRawPoint( vec3_t point, int flags, int radius ) { CNode *node = CNode::Create( point, flags, radius, m_nodes.size() ); if ( node == NULL ) { Com_Error( ERR_DROP, "Error adding node!\n" ); return -1; } //TODO: Validate the position //TODO: Correct stuck waypoints STL_INSERT( m_nodes, node ); return node->GetID(); } /* ------------------------- GetEdgeCost ------------------------- */ int CNavigator::GetEdgeCost( CNode *first, CNode *second ) { trace_t trace; vec3_t start, end; vec3_t mins, maxs; //Setup the player size VectorSet( mins, -8, -8, -8 ); VectorSet( maxs, 8, 8, 8 ); //Setup the points first->GetPosition( start ); second->GetPosition( end ); gi.trace( &trace, start, mins, maxs, end, ENTITYNUM_NONE, MASK_SOLID, G2_NOCOLLIDE, 0 ); if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) return Q3_INFINITE; // return -1; //Connection successful, return the cost return Distance( start, end ); } void CNavigator::SetEdgeCost( int ID1, int ID2, int cost ) { if( (ID1 == -1) || (ID2 == -1) ) {//not valid nodes, must have come from the ClearAllFailedEdges initization-type calls return; } CNode *node1 = m_nodes[ID1]; CNode *node2 = m_nodes[ID2]; if ( cost == -1 ) {//they want us to calc it //FIXME: can we just remember this instead of recalcing every time? vec3_t pos1, pos2; node1->GetPosition( pos1 ); node2->GetPosition( pos2 ); cost = Distance( pos1, pos2 ); } //set it node1->AddEdge( ID2, cost ); node2->AddEdge( ID1, cost ); } /* ------------------------- AddNodeEdges ------------------------- */ void CNavigator::AddNodeEdges( CNode *node, int addDist, edge_l &edgeList, bool *checkedNodes ) { //Add all edge for ( int i = 0; i < node->GetNumEdges(); i++ ) { //Make sure we don't add an old edge twice if ( checkedNodes[ node->GetEdge( i ) ] == true ) continue; //Get the node CNode *nextNode = m_nodes[ node->GetEdge( i ) ]; //This node has now been checked checkedNodes[ nextNode->GetID() ] = true; //Add it to the list STL_INSERT( edgeList, CEdge( nextNode->GetID(), node->GetID(), addDist + ( node->GetEdgeCost( i ) ) ) ); } } /* ------------------------- CalculatePath ------------------------- */ void CNavigator::CalculatePath( CNode *node ) { int curRank = 0; int i; CPriorityQueue *pathList = new CPriorityQueue(); unsigned char *checked; //Init the completion table checked = new unsigned char[ m_nodes.size() ]; memset( checked, 0, m_nodes.size() ); //Mark this node as checked checked[ node->GetID() ] = true; node->AddRank( node->GetID(), curRank++ ); //Add all initial nodes for ( i = 0; i < node->GetNumEdges(); i++ ) { CNode *nextNode = m_nodes[ node->GetEdge(i) ]; assert(nextNode); checked[ nextNode->GetID() ] = true; pathList->Push( new CEdge( nextNode->GetID(), nextNode->GetID(), node->GetEdgeCost(i) ) ); } CEdge *test; //Now flood fill all the others while ( !pathList->Empty() ) { test = pathList->Pop(); CNode *testNode = m_nodes[ (*test).m_first ]; assert( testNode ); node->AddRank( testNode->GetID(), curRank++ ); //Add in all the new edges for ( i = 0; i < testNode->GetNumEdges(); i++ ) { CNode *addNode = m_nodes[ testNode->GetEdge(i) ]; assert( addNode ); if ( checked[ addNode->GetID() ] ) continue; int newDist = (*test).m_cost + testNode->GetEdgeCost(i); pathList->Push( new CEdge( addNode->GetID(), (*test).m_second, newDist ) ); checked[ addNode->GetID() ] = true; } delete test; } node->RemoveFlag( NF_RECALC ); delete pathList; delete [] checked; } /* ------------------------- CalculatePaths ------------------------- */ extern void CP_FindCombatPointWaypoints( void ); void CNavigator::CalculatePaths( bool recalc ) { #ifndef FINAL_BUILD int startTime = gi.Milliseconds(); #endif #if _HARD_CONNECT #else #endif int i; for ( i = 0; i < (int)m_nodes.size(); i++ ) { //Allocate the needed memory m_nodes[i]->InitRanks( m_nodes.size() ); } for ( i = 0; i < (int)m_nodes.size(); i++ ) { CalculatePath( m_nodes[i] ); } #ifndef FINAL_BUILD if ( pathsCalculated ) { gi.Printf( S_COLOR_CYAN"%s recalced paths in %d ms\n", (NPC!=NULL?NPC->targetname:"NULL"), gi.Milliseconds()-startTime ); } #endif if(!recalc) //Mike says doesn't need to happen on recalc { CP_FindCombatPointWaypoints(); } pathsCalculated = qtrue; } /* ------------------------- ShowNodes ------------------------- */ void CNavigator::ShowNodes( void ) { node_v::iterator ni; vec3_t position; qboolean showRadius; float dist, radius; STL_ITERATE( ni, m_nodes ) { (*ni)->GetPosition( position ); showRadius = qfalse; if( NAVDEBUG_showRadius ) { dist = DistanceSquared( g_entities[0].currentOrigin, position ); radius = (*ni)->GetRadius(); // if player within node radius or 256, draw radius (sometimes the radius is really small, so we check for 256 to catch everything) if( (dist <= radius*radius) || dist <= 65536 ) { showRadius = qtrue; } } else { dist = DistanceSquared( g_entities[0].currentOrigin, position ); } if ( dist < 1048576 ) { if ( gi.inPVS( g_entities[0].currentOrigin, position ) ) { (*ni)->Draw(showRadius); } } } } /* ------------------------- ShowEdges ------------------------- */ typedef std::map < int, bool > drawMap_m; void CNavigator::ShowEdges( void ) { node_v::iterator ni; vec3_t start, end; drawMap_m *drawMap; drawMap = new drawMap_m[ m_nodes.size() ]; STL_ITERATE( ni, m_nodes ) { (*ni)->GetPosition( start ); if ( DistanceSquared( g_entities[0].currentOrigin, start ) >= 1048576 ) { continue; } if ( !gi.inPVSIgnorePortals( g_entities[0].currentOrigin, start ) ) { continue; } //Attempt to draw each connection for ( int i = 0; i < (*ni)->GetNumEdges(); i++ ) { int id = (*ni)->GetEdge( i ); if ( id == -1 ) continue; //Already drawn? if ( drawMap[(*ni)->GetID()].find( id ) != drawMap[(*ni)->GetID()].end() ) continue; unsigned char flags = (*ni)->GetEdgeFlags( i ); CNode *node = m_nodes[id]; node->GetPosition( end ); //Set this as drawn drawMap[id][(*ni)->GetID()] = true; if ( DistanceSquared( g_entities[0].currentOrigin, end ) >= 1048576 ) { continue; } if ( !gi.inPVSIgnorePortals( g_entities[0].currentOrigin, end ) ) continue; if ( EdgeFailed( id, (*ni)->GetID() ) != -1 )//flags & EFLAG_FAILED ) CG_DrawEdge( start, end, EDGE_FAILED ); else if ( flags & EFLAG_BLOCKED ) CG_DrawEdge( start, end, EDGE_BLOCKED ); else CG_DrawEdge( start, end, EDGE_NORMAL ); } } delete [] drawMap; } int CNavigator::GetNodeRadius( int nodeID ) { if ( m_nodes.size() == 0 ) return 0; return m_nodes[nodeID]->GetRadius(); } void CNavigator::CheckBlockedEdges( void ) { CNode *start, *end; vec3_t p1, p2; int flags, first, second; trace_t trace; qboolean failed; int edgeNum; node_v::iterator ni; //Go through all edges and test the ones that were blocked STL_ITERATE( ni, m_nodes ) { //Attempt to draw each connection for ( edgeNum = 0; edgeNum < (*ni)->GetNumEdges(); edgeNum++ ) { flags = (*ni)->GetEdgeFlags( edgeNum ); if ( (flags&EFLAG_BLOCKED) ) { first = (*ni)->GetID(); second = (*ni)->GetEdge( edgeNum ); start = m_nodes[first]; end = m_nodes[second]; failed = qfalse; start->GetPosition( p1 ); end->GetPosition( p2 ); //FIXME: can't we just store the trace.entityNum from the HardConnect trace? So we don't have to do another trace here... gi.trace( &trace, p1, wpMins, wpMaxs, p2, ENTITYNUM_NONE, MASK_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, G2_NOCOLLIDE, 0 ); if ( trace.entityNum < ENTITYNUM_WORLD && (trace.fraction < 1.0f || trace.startsolid == qtrue || trace.allsolid == qtrue) ) {//could be assumed, since failed before if ( G_EntIsDoor( trace.entityNum ) ) {//door if ( !G_EntIsUnlockedDoor( trace.entityNum ) ) {//locked door failed = qtrue; } } else { if ( G_EntIsBreakable( trace.entityNum ) ) {//do same for breakable brushes/models/glass? failed = qtrue; } else if ( G_EntIsRemovableUsable( trace.entityNum ) ) { failed = qtrue; } else if ( trace.allsolid || trace.startsolid ) {//FIXME: the entitynum would be none here, so how do we know if this is stuck inside an ent or the world? } else {//FIXME: what about func_plats and scripted movers? } } } if ( failed ) { //could add the EFLAG_FAILED to the two edges, but we stopped doing that since it was pointless AddFailedEdge( ENTITYNUM_NONE, first, second ); } } } } } #if _HARD_CONNECT /* ------------------------- HardConnect ------------------------- */ void CNavigator::HardConnect( int first, int second ) { CNode *start, *end; start = m_nodes[first]; end = m_nodes[second]; vec3_t p1, p2; start->GetPosition( p1 ); end->GetPosition( p2 ); trace_t trace; int flags = EFLAG_NONE; gi.trace( &trace, p1, wpMins, wpMaxs, p2, ENTITYNUM_NONE, MASK_SOLID|CONTENTS_BOTCLIP|CONTENTS_MONSTERCLIP, G2_NOCOLLIDE, 0 ); int cost = Distance( p1, p2 ); if ( trace.fraction != 1.0f || trace.startsolid == qtrue || trace.allsolid == qtrue ) { flags |= EFLAG_BLOCKED; } start->AddEdge( second, cost, flags ); end->AddEdge( first, cost, flags ); } #endif /* ------------------------- TestNodePath ------------------------- */ int CNavigator::TestNodePath( gentity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ) { int clipmask = ent->clipmask; if ( !includeEnts ) { clipmask &= ~CONTENTS_BODY; } //Check the path if ( NAV_ClearPathToPoint( ent, ent->mins, ent->maxs, position, clipmask, okToHitEntNum ) == false ) return false; return true; } /* ------------------------- TestNodeLOS ------------------------- */ int CNavigator::TestNodeLOS( gentity_t *ent, vec3_t position ) { return NPC_ClearLOS( ent, position ); } /* ------------------------- TestBestFirst ------------------------- */ int CNavigator::TestBestFirst( gentity_t *ent, int lastID, int flags ) { //Must be a valid one to begin with if ( lastID == NODE_NONE ) return NODE_NONE; if ( lastID >= (int)m_nodes.size() ) return NODE_NONE; //Get the info vec3_t nodePos; CNode *node = m_nodes[ lastID ]; CNode *testNode; int numEdges = node->GetNumEdges(); float dist; node->GetPosition( nodePos ); //Setup our last node as our root, and search for a closer one according to its edges int bestNode = ( TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) ? lastID : NODE_NONE; float bestDist = ( bestNode == NODE_NONE ) ? Q3_INFINITE : DistanceSquared( ent->currentOrigin, nodePos ); //Test all these edges first for ( int i = 0; i < numEdges; i++ ) { //Get this node and its distance testNode = m_nodes[ node->GetEdge(i) ]; if ( NodeFailed( ent, testNode->GetID() ) ) { continue; } testNode->GetPosition( nodePos ); dist = DistanceSquared( ent->currentOrigin, nodePos ); //Test against current best if ( dist < bestDist ) { //See if this node is valid if ( CheckedNode(testNode->GetID(),ent->s.number) == CHECKED_PASSED || TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) { bestDist = dist; bestNode = testNode->GetID(); SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_PASSED); } else { SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_FAILED); } } } return bestNode; } /* ------------------------- CollectNearestNodes ------------------------- */ #define NODE_COLLECT_MAX 16 //Maximum # of nodes collected at any time #define NODE_COLLECT_RADIUS 512 //Default radius to search for nodes in #define NODE_COLLECT_RADIUS_SQR ( NODE_COLLECT_RADIUS * NODE_COLLECT_RADIUS ) int CNavigator::CollectNearestNodes( vec3_t origin, int radius, int maxCollect, nodeChain_l &nodeChain ) { node_v::iterator ni; float dist; vec3_t position; int collected = 0; bool added = false; //Get a distance rating for each node in the system STL_ITERATE( ni, m_nodes ) { //If we've got our quota, then stop looking //Get the distance to the node (*ni)->GetPosition( position ); dist = DistanceSquared( position, origin ); //Must be within our radius range if ( dist > (float) ( radius * radius ) ) continue; nodeList_t nChain; nodeChain_l::iterator nci; //Always add the first node if ( nodeChain.size() == 0 ) { nChain.nodeID = (*ni)->GetID(); nChain.distance = dist; nodeChain.insert( nodeChain.begin(), nChain ); continue; } added = false; //Compare it to what we already have STL_ITERATE( nci, nodeChain ) { //If we're less, than this entry, then insert before it if ( dist < (*nci).distance ) { nChain.nodeID = (*ni)->GetID(); nChain.distance = dist; nodeChain.insert( nci, nChain ); collected = nodeChain.size(); added = true; //If we've hit our collection limit, throw off the oldest one if ( (int)nodeChain.size() > maxCollect ) { nodeChain.pop_back(); } break; } } //Otherwise, always pad out the collection if possible so we don't miss anything if ( ( added == false ) && ( (int)nodeChain.size() < maxCollect ) ) { nChain.nodeID = (*ni)->GetID(); nChain.distance = dist; nodeChain.insert( nodeChain.end(), nChain ); } } return collected; } int CNavigator::GetBestPathBetweenEnts( gentity_t *ent, gentity_t *goal, int flags ) { //Must have nodes if ( m_nodes.size() == 0 ) return NODE_NONE; #define MAX_Z_DELTA 18 nodeChain_l nodeChain; nodeChain_l::iterator nci; nodeChain_l nodeChain2; nodeChain_l::iterator nci2; //Collect all nodes within a certain radius CollectNearestNodes( ent->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); CollectNearestNodes( goal->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain2 ); vec3_t position; vec3_t position2; int radius; int cost, pathCost, bestCost = Q3_INFINITE; CNode *node, *node2; int nodeNum, nodeNum2; int nextNode = NODE_NONE, bestNode = NODE_NONE; int nodeFlags = 0; // bool recalc = false; ent->waypoint = NODE_NONE; goal->waypoint = NODE_NONE; //Look through all nodes STL_ITERATE( nci, nodeChain ) { node = m_nodes[(*nci).nodeID]; nodeNum = (*nci).nodeID; node->GetPosition( position ); if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_FAILED ) {//already checked this node against ent and it failed continue; } if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_PASSED ) {//already checked this node against ent and it passed } else {//haven't checked this node against ent yet if ( NodeFailed( ent, nodeNum ) ) { SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); continue; } //okay, since we only have to do this once, let's check to see if this node is even usable (could help us short-circuit a whole loop of the dest nodes) radius = node->GetRadius(); //If we're not within the known clear radius of this node OR out of Z height range... if ( (signed)(*nci).distance >= (radius*radius) || ( fabs( position[2] - ent->currentOrigin[2] ) >= MAX_Z_DELTA ) ) { //We're not *within* this node, so check clear path, etc. //FIXME: any way to call G_FindClosestPointOnLineSegment and see if I can at least get to the waypoint's path if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) {//need a clear path or LOS if ( !gi.inPVS( ent->currentOrigin, position ) ) {//not even potentially clear SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); continue; } } //Do we need a clear path? if ( flags & NF_CLEAR_PATH ) { if ( TestNodePath( ent, goal->s.number, position, qtrue ) == false ) { SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); continue; } } }//otherwise, inside the node so it must be clear (?) SetCheckedNode( nodeNum, ent->s.number, CHECKED_PASSED ); } if ( d_altRoutes->integer ) { //calc the paths for this node if they're out of date nodeFlags = node->GetFlags(); if ( (nodeFlags&NF_RECALC) ) { //gi.Printf( S_COLOR_CYAN"%d recalcing paths from node %d\n", level.time, nodeNum ); CalculatePath( node ); } } STL_ITERATE( nci2, nodeChain2 ) { node2 = m_nodes[(*nci2).nodeID]; nodeNum2 = (*nci2).nodeID; if ( d_altRoutes->integer ) { //calc the paths for this node if they're out of date nodeFlags = node2->GetFlags(); if ( (nodeFlags&NF_RECALC) ) { //gi.Printf( S_COLOR_CYAN"%d recalcing paths from node %d\n", level.time, nodeNum2 ); CalculatePath( node2 ); } } node2->GetPosition( position2 ); //Okay, first get the entire path cost, including distance to first node from ents' positions cost = floor(Distance( ent->currentOrigin, position ) + Distance( goal->currentOrigin, position2 )); if ( d_altRoutes->integer ) { nextNode = GetBestNodeAltRoute( (*nci).nodeID, (*nci2).nodeID, &pathCost, bestNode ); cost += pathCost; } else { cost += GetPathCost( (*nci).nodeID, (*nci2).nodeID ); } if ( cost >= bestCost ) { continue; } //okay, this is the shortest path we've found yet, check clear path, etc. if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_FAILED ) {//already checked this node against goal and it failed continue; } if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_PASSED ) {//already checked this node against goal and it passed } else {//haven't checked this node against goal yet if ( NodeFailed( goal, nodeNum2 ) ) { SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); continue; } radius = node2->GetRadius(); //If we're not within the known clear radius of this node OR out of Z height range... if ( (signed)(*nci2).distance >= (radius*radius) || ( fabs( position2[2] - goal->currentOrigin[2] ) >= MAX_Z_DELTA ) ) { //We're not *within* this node, so check clear path, etc. if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) {//need a clear path or LOS if ( !gi.inPVS( goal->currentOrigin, position2 ) ) {//not even potentially clear SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); continue; } } //Do we need a clear path? if ( flags & NF_CLEAR_PATH ) { if ( TestNodePath( goal, ent->s.number, position2, qfalse ) == false )//qtrue? { SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); continue; } } }//otherwise, inside the node so it must be clear (?) SetCheckedNode( nodeNum2, goal->s.number, CHECKED_PASSED ); } bestCost = cost; bestNode = nextNode; ent->waypoint = (*nci).nodeID; goal->waypoint = (*nci2).nodeID; } } if ( !d_altRoutes->integer ) {//bestNode would not have been set by GetBestNodeAltRoute above, so get it here if ( ent->waypoint != NODE_NONE && goal->waypoint != NODE_NONE ) {//have 2 valid waypoints which means a valid path bestNode = GetBestNodeAltRoute( ent->waypoint, goal->waypoint, &bestCost, NODE_NONE ); } } return bestNode; } /* ------------------------- GetNearestWaypoint ------------------------- */ int CNavigator::GetNearestNode( gentity_t *ent, int lastID, int flags, int targetID ) { int bestNode = NODE_NONE; //Must have nodes if ( m_nodes.size() == 0 ) return NODE_NONE; if ( targetID == NODE_NONE ) { //Try and find an early match using our last node bestNode = TestBestFirst( ent, lastID, flags ); if ( bestNode != NODE_NONE ) return bestNode; }//else can't rely on testing last, we want best to targetID ///////////////////////////////////////////////// #define MAX_Z_DELTA 18 ///////////////////////////////////////////////// nodeChain_l nodeChain; nodeChain_l::iterator nci; //Collect all nodes within a certain radius CollectNearestNodes( ent->currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); vec3_t position; int radius; int dist, bestDist = Q3_INFINITE; CNode *node; //Look through all nodes STL_ITERATE( nci, nodeChain ) { node = m_nodes[(*nci).nodeID]; node->GetPosition( position ); radius = node->GetRadius(); if ( NodeFailed( ent, (*nci).nodeID ) ) { continue; } //Are we within the known clear radius of this node? if ( (signed)(*nci).distance < (radius*radius) ) { //Do a z-difference sanity check if ( fabs( position[2] - ent->currentOrigin[2] ) < MAX_Z_DELTA ) { //Found one return (*nci).nodeID; } } //We're not *within* this node, so... if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) { continue; } else if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) { continue; } else { //Do we need a clear path? if ( flags & NF_CLEAR_PATH ) { if ( TestNodePath( ent, ENTITYNUM_NONE, position, qfalse ) == false )//qtrue? { SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_FAILED); continue; } } //Do we need a clear line of sight? /* if ( flags & NF_CLEAR_LOS ) { if ( TestNodeLOS( ent, position ) == false ) { nodeChecked[(*nci).nodeID][ent->s.number] = CHECKED_FAILED; continue; } } */ SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_PASSED); } if ( targetID != WAYPOINT_NONE ) {//we want to find the one with the shortest route here dist = GetPathCost( (*nci).nodeID, targetID ); if ( dist < bestDist ) { bestDist = dist; bestNode = (*nci).nodeID; } } else {//first one we find is fine bestNode = (*nci).nodeID; break; } } //Found one, we're done return bestNode; } /* ------------------------- ShowPath ------------------------- */ void CNavigator::ShowPath( int start, int end ) { //Validate the start position if ( ( start < 0 ) || ( start >= (int)m_nodes.size() ) ) return; //Validate the end position if ( ( end < 0 ) || ( end >= (int)m_nodes.size() ) ) return; CNode *startNode = m_nodes[ start ]; CNode *endNode = m_nodes[ end ]; CNode *moveNode = startNode; CNode *testNode = NULL; int bestNode; vec3_t startPos, endPos; int runAway = 0; //Draw out our path while ( moveNode != endNode ) { bestNode = GetBestNode( moveNode->GetID(), end ); //Some nodes may be fragmented if ( bestNode == -1 ) { Com_Printf("No connection possible between node %d and %d\n", start, end ); return; } //This is our next node on the path testNode = m_nodes[ bestNode ]; //Get their origins moveNode->GetPosition( startPos ); testNode->GetPosition( endPos ); //Draw the edge CG_DrawEdge( startPos, endPos, EDGE_PATH ); //Take a new best node moveNode = testNode; if ( runAway++ > 64 ) { Com_Printf("Potential Run-away path!\n"); return; } } } static std::map CheckedNodes; void CNavigator::ClearCheckedNodes( void ) { CheckedNodes.clear(); } byte CNavigator::CheckedNode(int wayPoint,int ent) { assert(wayPoint>=0&&wayPoint=0&&ent::iterator f=CheckedNodes.find(wayPoint*MAX_GENTITIES+ent); if (f!=CheckedNodes.end()) { return (*f).second; } return CHECKED_NO; } void CNavigator::SetCheckedNode(int wayPoint,int ent,byte value) { assert(wayPoint>=0&&wayPoint=0&&entfailedWaypointCheckTime && ent->failedWaypointCheckTime < level.time ) { int failed = 0; //do this only once every 1 second for ( j = 0; j < MAX_FAILED_NODES; j++ ) { if ( ent->failedWaypoints[j] != 0 ) { failed++; //-1 because 0 is a valid node but also the default, so we add one when we add one m_nodes[ent->failedWaypoints[j]-1]->GetPosition( nodePos ); if ( !NAV_ClearPathToPoint( ent, ent->mins, ent->maxs, nodePos, (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP), ENTITYNUM_NONE ) ) {//no path clear of architecture, so clear this since we can't check against entities ent->failedWaypoints[j] = 0; failed--; } //have clear architectural path, now check against ents only else if ( NAV_ClearPathToPoint( ent, ent->mins, ent->maxs, nodePos, CONTENTS_BODY, ENTITYNUM_NONE ) ) {//clear of ents, too, so all clear, clear this one out ent->failedWaypoints[j] = 0; failed--; } } } if ( !failed ) { ent->failedWaypointCheckTime = 0; } else { ent->failedWaypointCheckTime = level.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); } } } void CNavigator::AddFailedNode( gentity_t *ent, int nodeID ) { int j; for ( j = 0; j < MAX_FAILED_NODES; j++ ) { if ( ent->failedWaypoints[j] == 0 ) { ent->failedWaypoints[j] = nodeID+1;//+1 because 0 is the default value and that's a valid node, so we take the +1 out when we check the node above if ( !ent->failedWaypointCheckTime ) { ent->failedWaypointCheckTime = level.time + CHECK_FAILED_EDGE_INTITIAL; } return; } if ( ent->failedWaypoints[j] == nodeID+1 ) {//already have this one marked as failed return; } } if ( j == MAX_FAILED_NODES )//check not needed, but... {//ran out of failed nodes, get rid of first one, shift rest up for ( j = 0; j < MAX_FAILED_NODES-1; j++ ) { ent->failedWaypoints[j] = ent->failedWaypoints[j+1]; } } ent->failedWaypoints[MAX_FAILED_NODES-1] = nodeID+1; if ( !ent->failedWaypointCheckTime ) { ent->failedWaypointCheckTime = level.time + CHECK_FAILED_EDGE_INTITIAL; } } qboolean CNavigator::NodeFailed( gentity_t *ent, int nodeID ) { for ( int j = 0; j < MAX_FAILED_NODES; j++ ) { if ( (ent->failedWaypoints[j]-1) == nodeID ) { return qtrue; } } return qfalse; } qboolean CNavigator::NodesAreNeighbors( int startID, int endID ) {//See if these 2 are neighbors if ( startID == endID ) { return qfalse; } CNode *start = m_nodes[startID]; int nextID = -1; //NOTE: we only check start because we assume all connections are 2-way for ( int i = 0; i < start->GetNumEdges(); i++ ) { nextID = start->GetEdge(i); if ( nextID == endID ) { return qtrue; } } //not neighbors return qfalse; } void CNavigator::ClearFailedEdge( failedEdge_t *failedEdge ) { if ( !failedEdge ) { return; } //clear the edge failed flags /* CNode *node = m_nodes[failedEdge->startID]; int edgeNum = node->GetEdgeNumToNode( failedEdge->endID ); int flags; if ( edgeNum != -1 ) { flags = node->GetEdgeFlags( edgeNum )&~EFLAG_FAILED; node->SetEdgeFlags( edgeNum, flags ); } node = m_nodes[failedEdge->endID]; edgeNum = node->GetEdgeNumToNode( failedEdge->startID ); if ( edgeNum != -1 ) { flags = node->GetEdgeFlags( edgeNum )&~EFLAG_FAILED; node->SetEdgeFlags( edgeNum, flags ); } */ //clear failedEdge info SetEdgeCost( failedEdge->startID, failedEdge->endID, -1 ); failedEdge->startID = failedEdge->endID = WAYPOINT_NONE; failedEdge->entID = ENTITYNUM_NONE; failedEdge->checkTime = 0; } void CNavigator::ClearAllFailedEdges( void ) { memset( &failedEdges, WAYPOINT_NONE, sizeof( failedEdges ) ); for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) { ClearFailedEdge( &failedEdges[j] ); } } int CNavigator::EdgeFailed( int startID, int endID ) { //OPTIMIZED WAY (bjg 01/02) //find in lookup map std::pair findValue; findValue = m_edgeLookupMap.equal_range(startID); while ( findValue.first != findValue.second ) { if( failedEdges[findValue.first->second].endID == endID) { return findValue.first->second; } ++findValue.first; } findValue = m_edgeLookupMap.equal_range(endID); while ( findValue.first != findValue.second ) { if( failedEdges[findValue.first->second].endID == startID) { return findValue.first->second; } ++findValue.first; } return -1; //Old way (linear search) /* for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) { if ( failedEdges[j].startID == startID ) { if ( failedEdges[j].endID == endID ) { return j; } } else if ( failedEdges[j].startID == endID ) { if ( failedEdges[j].endID == startID ) { return j; } } } return -1; */ } void CNavigator::AddFailedEdge( int entID, int startID, int endID ) { int j;//, nextID; //Must have nodes if ( m_nodes.size() == 0 ) return; if ( d_patched->integer ) {//use patch-style navigation if ( startID == endID ) {//not an edge! return; } } //Validate the ent number if ( ( entID < 0 ) || ( entID > ENTITYNUM_NONE ) ) { #ifndef FINAL_BUILD gi.Printf( S_COLOR_RED"NAV ERROR: envalid ent %d\n", entID ); assert(0&&"invalid entID"); #endif return; } //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) { #ifndef FINAL_BUILD gi.Printf( S_COLOR_RED"NAV ERROR: tried to fail invalid waypoint %d\n", startID ); assert(0&&"invalid failed edge"); #endif return; } //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) { #ifndef FINAL_BUILD gi.Printf( S_COLOR_RED"NAV ERROR: tried to fail invalid waypoint %d\n", endID ); assert(0&&"invalid failed edge"); #endif return; } //First see if we already have this one if ( (j = EdgeFailed( startID, endID )) != -1 ) { //just remember this guy instead failedEdges[j].entID = entID; return; } //Okay, new one, find an empty slot for ( j = 0; j < MAX_FAILED_EDGES; j++ ) { if ( failedEdges[j].startID == WAYPOINT_NONE ) { failedEdges[j].startID = startID; failedEdges[j].endID = endID; //Check one second from now to see if it's clear failedEdges[j].checkTime = level.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); m_edgeLookupMap.insert(std::pair(startID, j)); /* //DISABLED this for now, makes people stand around too long when // collision avoidance just wasn't at it's best but path is clear CNode *start = m_nodes[startID]; CNode *end = m_nodes[endID]; for ( int i = 0; i < start->GetNumEdges(); i++ ) { nextID = start->GetEdge(i); if ( EdgeFailed( startID, nextID ) != -1 ) { //This edge blocked, check next continue; } if ( nextID == endID || end->GetRank( nextID ) >= 0 ) {//neighbor of or route to end //There's an alternate route, so don't check this one for 10 seconds failedEdges[j].checkTime = level.time + CHECK_FAILED_EDGE_INTITIAL; break; } } */ //Remember who needed it failedEdges[j].entID = entID; //set the edge failed flags /* CNode *node = m_nodes[startID]; int edgeNum = node->GetEdgeNumToNode( endID ); int flags; if ( edgeNum != -1 ) { flags = node->GetEdgeFlags( edgeNum )|EFLAG_FAILED; node->SetEdgeFlags( edgeNum, flags ); } node = m_nodes[endID]; edgeNum = node->GetEdgeNumToNode( startID ); if ( edgeNum != -1 ) { flags = node->GetEdgeFlags( edgeNum )|EFLAG_FAILED; node->SetEdgeFlags( edgeNum, flags ); } */ //stuff the index to this one in our lookup map //now recalc all the paths! if ( pathsCalculated ) { //reconnect the nodes and mark every node's flag NF_RECALC //gi.Printf( S_COLOR_CYAN"%d marking all nodes for recalc\n", level.time ); SetEdgeCost( startID, endID, Q3_INFINITE ); FlagAllNodes( NF_RECALC ); } return; } } #ifndef FINAL_BUILD gi.Printf( S_COLOR_RED"NAV ERROR: too many blocked waypoint connections (%d)!!!\n", j ); #endif } qboolean CNavigator::CheckFailedEdge( failedEdge_t *failedEdge ) { if ( !failedEdge ) { return qfalse; } //Every 1 second, see if our failed edges are clear if ( failedEdge->checkTime < level.time ) { if ( failedEdge->startID != WAYPOINT_NONE ) { vec3_t start, end, mins, maxs; int ignore, clipmask; gentity_t *ent = (failedEdge->entIDentID]:NULL; int hitEntNum; if ( !ent || !ent->inuse || !ent->client || ent->health <= 0 ) { VectorSet( mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2+STEPSIZE ); VectorSet( maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 ); ignore = ENTITYNUM_NONE; clipmask = MASK_NPCSOLID; } else { VectorCopy( ent->mins, mins ); mins[2] += STEPSIZE; VectorCopy( ent->maxs, maxs ); ignore = failedEdge->entID; clipmask = ent->clipmask; } if ( maxs[2] < mins[2] ) {//don't invert bounding box maxs[2] = mins[2]; } m_nodes[failedEdge->startID]->GetPosition( start ); m_nodes[failedEdge->endID]->GetPosition( end ); //See if it's NAV_ClearPath... #if 0 hitEntNum = NAVNEW_ClearPathBetweenPoints( start, end, mins, maxs, ignore, clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );//NOTE: should we really always include monsterclip (physically blocks NPCs) and botclip (do not enter)? #else trace_t trace; //Test if they're even conceivably close to one another if ( !gi.inPVSIgnorePortals( start, end ) ) { return qfalse; } gi.trace( &trace, start, mins, maxs, end, ignore, clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, G2_NOCOLLIDE, 0 );//NOTE: should we really always include monsterclip (physically blocks NPCs) and botclip (do not enter)? if( trace.startsolid == qtrue || trace.allsolid == qtrue ) { return qfalse; } hitEntNum = trace.entityNum; #endif //if we did hit something, see if it's just an auto-door and allow it if ( hitEntNum != ENTITYNUM_NONE && G_EntIsUnlockedDoor( hitEntNum ) ) { hitEntNum = ENTITYNUM_NONE; } else if ( hitEntNum == failedEdge->entID ) {//don't hit the person who initially marked the edge failed hitEntNum = ENTITYNUM_NONE; } if ( hitEntNum == ENTITYNUM_NONE ) { //If so, clear it ClearFailedEdge( failedEdge ); return qtrue; } else { //Check again in one second failedEdge->checkTime = level.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); } } } return qfalse; } void CNavigator::CheckAllFailedEdges( void ) { failedEdge_t *failedEdge; qboolean clearedAny = qfalse; //Must have nodes if ( m_nodes.size() == 0 ) return; for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) { failedEdge = &failedEdges[j]; clearedAny = CheckFailedEdge( failedEdge )?qtrue:clearedAny; } if ( clearedAny ) {//need to recalc the paths if ( pathsCalculated ) { //reconnect the nodes and mark every node's flag NF_RECALC //gi.Printf( S_COLOR_CYAN"%d marking all nodes for recalc\n", level.time ); FlagAllNodes( NF_RECALC ); } } } qboolean CNavigator::RouteBlocked( int startID, int testEdgeID, int endID, int rejectRank ) { int nextID, edgeID, lastID, bestNextID = NODE_NONE; int bestRank = rejectRank; int testRank; qboolean allEdgesFailed; CNode *end; CNode *next; if ( EdgeFailed( startID, testEdgeID ) != -1 ) { return qtrue; } if ( testEdgeID == endID ) {//Neighbors, checked out, all clear return qfalse; } //Okay, first edge is clear, now check rest of route! end = m_nodes[ endID ]; nextID = testEdgeID; lastID = startID; while( 1 ) { next = m_nodes[ nextID ]; allEdgesFailed = qtrue; for ( int i = 0; i < next->GetNumEdges(); i++ ) { edgeID = next->GetEdge(i); if ( edgeID == lastID ) {//Don't backtrack continue; } if ( edgeID == startID ) {//Don't loop around continue; } if ( EdgeFailed( nextID, edgeID ) != -1 ) { //This edge blocked, check next continue; } if ( edgeID == endID ) {//We got there all clear! return qfalse; } //Still going... testRank = end->GetRank( edgeID ); if ( testRank < 0 ) {//No route this way continue; } //Is the rank good enough? if ( testRank < bestRank ) { bestNextID = edgeID; bestRank = testRank; allEdgesFailed = qfalse; } } if ( allEdgesFailed ) { //This route has no clear way of getting to end return qtrue; } else { lastID = nextID; nextID = bestNextID; } } } /* ------------------------- GetBestNodeAltRoute ------------------------- */ int CNavigator::GetBestNodeAltRoute( int startID, int endID, int *pathCost, int rejectID ) { //Must have nodes if ( m_nodes.size() == 0 ) return WAYPOINT_NONE; //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) return WAYPOINT_NONE; //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) return WAYPOINT_NONE; //Is it the same node? if ( startID == endID ) { if ( !d_altRoutes->integer || EdgeFailed( startID, endID ) == -1 ) { return startID; } else { return WAYPOINT_NONE; } } CNode *start = m_nodes[ startID ]; int bestNode = -1; int bestRank = Q3_INFINITE; int testRank, rejectRank = Q3_INFINITE; int bestCost = Q3_INFINITE; *pathCost = 0; //Find the minimum rank of the edge(s) we want to reject as paths if ( rejectID != WAYPOINT_NONE ) { for ( int i = 0; i < start->GetNumEdges(); i++ ) { if ( start->GetEdge(i) == rejectID ) { rejectRank = GetPathCost( startID, endID );//end->GetRank( start->GetEdge(i) ); break; } } } for ( int i = 0; i < start->GetNumEdges(); i++ ) { int edgeID = start->GetEdge(i); testRank = GetPathCost( edgeID, endID );//end->GetRank( edgeID ); //Make sure it's not worse than our reject rank if ( testRank >= rejectRank ) continue; //Found one if ( edgeID == endID ) { if ( !d_altRoutes->integer || !RouteBlocked( startID, edgeID, endID, rejectRank ) ) { *pathCost += start->GetEdgeCost( i ); return edgeID; } else {//this is blocked, can't consider it continue; } } //No possible connection if ( testRank == NODE_NONE ) { *pathCost = Q3_INFINITE; return NODE_NONE; } //Found a better one if ( testRank < bestRank ) { //FIXME: make sure all the edges down from startID through edgeID to endID // does NOT include a failedEdge... if ( !d_altRoutes->integer || !RouteBlocked( startID, edgeID, endID, rejectRank ) ) { bestNode = edgeID; bestRank = testRank; bestCost = start->GetEdgeCost(i)+testRank; } } } *pathCost = bestCost; return bestNode; } /* ------------------------- GetBestNodeAltRoute overloaded so you don't have to pass a pathCost int pointer in ------------------------- */ int CNavigator::GetBestNodeAltRoute( int startID, int endID, int rejectID ) { int junk; return GetBestNodeAltRoute( startID, endID, &junk, rejectID ); } /* ------------------------- GetBestNode ------------------------- */ int CNavigator::GetBestNode( int startID, int endID, int rejectID ) { //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) return WAYPOINT_NONE; //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) return WAYPOINT_NONE; if ( startID == endID ) return startID; CNode *start = m_nodes[ startID ]; CNode *end = m_nodes[ endID ]; int bestNode = -1; int bestRank = Q3_INFINITE; int testRank, rejectRank = 0; if ( rejectID != WAYPOINT_NONE ) { for ( int i = 0; i < start->GetNumEdges(); i++ ) { if ( start->GetEdge(i) == rejectID ) { rejectRank = end->GetRank( start->GetEdge(i) ); break; } } } for ( int i = 0; i < start->GetNumEdges(); i++ ) { int edgeID = start->GetEdge(i); //Found one if ( edgeID == endID ) return edgeID; testRank = end->GetRank( edgeID ); //Found one if ( testRank <= rejectRank ) continue; //No possible connection if ( testRank == NODE_NONE ) return NODE_NONE; //Found a better one if ( testRank < bestRank ) { bestNode = edgeID; bestRank = testRank; } } return bestNode; } /* ------------------------- GetNodePosition ------------------------- */ int CNavigator::GetNodePosition( int nodeID, vec3_t out ) { //Validate the number if ( ( nodeID < 0 ) || ( nodeID >= (int)m_nodes.size() ) ) return false; CNode *node = m_nodes[ nodeID ]; node->GetPosition( out ); return true; } /* ------------------------- GetNodeNumEdges ------------------------- */ int CNavigator::GetNodeNumEdges( int nodeID ) { if ( ( nodeID < 0 ) || ( nodeID >= (int)m_nodes.size() ) ) return -1; CNode *node = m_nodes[ nodeID ]; assert( node ); return node->GetNumEdges(); } /* ------------------------- GetNodeEdge ------------------------- */ int CNavigator::GetNodeEdge( int nodeID, int edge ) { if ( ( nodeID < 0 ) || ( nodeID >= (int)m_nodes.size() ) ) return -1; CNode *node = m_nodes[ nodeID ]; assert( node ); return node->GetEdge( edge ); } /* ------------------------- Connected ------------------------- */ bool CNavigator::Connected( int startID, int endID ) { //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) return false; //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) return false; if ( startID == endID ) return true; CNode *start = m_nodes[ startID ]; CNode *end = m_nodes[ endID ]; for ( int i = 0; i < start->GetNumEdges(); i++ ) { int edgeID = start->GetEdge(i); //Found one if ( edgeID == endID ) return true; if ( ( end->GetRank( edgeID ) ) != NODE_NONE ) return true; } return false; } /* ------------------------- GetPathCost ------------------------- */ unsigned int CNavigator::GetPathCost( int startID, int endID ) { //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) return Q3_INFINITE; // return 0; //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) return Q3_INFINITE; // return 0; CNode *startNode = m_nodes[ startID ]; if ( !startNode->GetNumEdges() ) {//WTF? Solitary waypoint! Bad designer! return Q3_INFINITE; // return 0; } CNode *endNode = m_nodes[ endID ]; CNode *moveNode = startNode; int bestNode; int pathCost = 0; int bestCost; int bestRank; int testRank; //Draw out our path while ( moveNode != endNode ) { bestRank = WORLD_SIZE; bestNode = -1; bestCost = 0; for ( int i = 0; i < moveNode->GetNumEdges(); i++ ) { int edgeID = moveNode->GetEdge(i); //Done if ( edgeID == endID ) { return pathCost + moveNode->GetEdgeCost( i ); } testRank = endNode->GetRank( edgeID ); //No possible connection if ( testRank == NODE_NONE ) { return Q3_INFINITE; // return 0; } //Found a better one if ( testRank < bestRank ) { bestNode = edgeID; bestRank = testRank; bestCost = moveNode->GetEdgeCost( i ); } } pathCost += bestCost; //Take a new best node moveNode = m_nodes[ bestNode ]; } return pathCost; } /* ------------------------- GetEdgeCost ------------------------- */ unsigned int CNavigator::GetEdgeCost( int startID, int endID ) { //Validate the start position if ( ( startID < 0 ) || ( startID >= (int)m_nodes.size() ) ) return Q3_INFINITE; // return 0; //Validate the end position if ( ( endID < 0 ) || ( endID >= (int)m_nodes.size() ) ) return Q3_INFINITE; // return 0; CNode *start = m_nodes[startID]; CNode *end = m_nodes[endID]; return GetEdgeCost( start, end ); } /* ------------------------- GetProjectedNode ------------------------- */ int CNavigator::GetProjectedNode( vec3_t origin, int nodeID ) { //Validate the start position if ( ( nodeID < 0 ) || ( nodeID >= (int)m_nodes.size() ) ) return NODE_NONE; CNode *node = m_nodes[nodeID]; CNode *tempNode; float bestDot = 0.0f; int bestNode = NODE_NONE; vec3_t targetDir, basePos, tempDir, tempPos; float dot; //Setup our target direction node->GetPosition( basePos ); VectorSubtract( origin, basePos, targetDir ); VectorNormalize( targetDir ); //Go through all the edges for ( int i = 0; i < node->GetNumEdges(); i++ ) { tempNode = m_nodes[node->GetEdge(i)]; tempNode->GetPosition( tempPos ); VectorSubtract( tempPos, basePos, tempDir ); VectorNormalize( tempDir ); //FIXME: Retain the length here if you want it dot = DotProduct( targetDir, tempDir ); if ( dot < 0.0f ) continue; if ( dot > bestDot ) { bestDot = dot; bestNode = tempNode->GetID(); } } return bestNode; } // This is the PriorityQueue stuff for lists of connections // better than linear (1/21/02 BJG) ////////////////////////////////////////////////////////////////// // Helper pop_mHeap algorithm class ////////////////////////////////////////////////////////////////// class NodeTotalGreater { public: bool operator()( CEdge * first, CEdge * second ) const { return( first->m_cost > second->m_cost ); } }; ////////////////////////////////////////////////////////////////// // Destructor - Deallocate any remaining pointers in the queue ////////////////////////////////////////////////////////////////// CPriorityQueue::~CPriorityQueue() { while (!Empty()) { delete Pop(); } } ////////////////////////////////////////////////////////////////// // Standard Iterative Search ////////////////////////////////////////////////////////////////// CEdge* CPriorityQueue::Find(int npNum) { for(std::vector::iterator HeapIter=mHeap.begin(); HeapIter!=mHeap.end(); HeapIter++) { if ((*HeapIter)->m_first == npNum) { return *HeapIter; } } return 0; } ////////////////////////////////////////////////////////////////// // Remove Node And Resort ////////////////////////////////////////////////////////////////// CEdge* CPriorityQueue::Pop() { CEdge *edge = mHeap.front(); //pop_mHeap will move the node at the front to the position N //and then sort the mHeap to make positions 1 through N-1 correct //(STL makes no assumptions about your data and doesn't want to change //the size of the container.) std::pop_heap(mHeap.begin(), mHeap.end(), NodeTotalGreater() ); //pop_back() will actually remove the last element from the mHeap //now the mHeap is sorted for positions 1 through N mHeap.pop_back(); return( edge ); } ////////////////////////////////////////////////////////////////// // Add New Node And Resort ////////////////////////////////////////////////////////////////// void CPriorityQueue::Push(CEdge* theEdge ) { //Pushes the node onto the back of the mHeap mHeap.push_back( theEdge ); //Sorts the new element into the mHeap std::push_heap( mHeap.begin(), mHeap.end(), NodeTotalGreater() ); } ////////////////////////////////////////////////////////////////// // Find The Node In Question And Resort mHeap Around It ////////////////////////////////////////////////////////////////// void CPriorityQueue::Update( CEdge* edge ) { for(std::vector::iterator i=mHeap.begin(); i!=mHeap.end(); i++) { if( (*i)->m_first == edge->m_first ) { //Found node - resort from this position in the mHeap //(its total value was changed before this function was called) std::push_heap( mHeap.begin(), i+1, NodeTotalGreater() ); return; } } } ////////////////////////////////////////////////////////////////// // Just a wrapper for stl empty function. ////////////////////////////////////////////////////////////////// bool CPriorityQueue::Empty() { return( mHeap.empty() ); };