sin-sdk/navigate.h

948 lines
22 KiB
C
Raw Permalink Normal View History

1998-12-20 00:00:00 +00:00
//-----------------------------------------------------------------------------
//
// $Logfile:: /Quake 2 Engine/Sin/code/game/navigate.h $
// $Revision:: 42 $
// $Author:: Markd $
// $Date:: 11/18/98 7:47p $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/navigate.h $
//
// 42 11/18/98 7:47p Markd
// Allowed nearestnode to ignore entity bbox
//
// 41 11/08/98 6:34p Jimdose
// added SetNodeFlagsEvent
//
// 40 10/26/98 6:32p Jimdose
// added fulltest to CheckPath and ClearPathTo
//
// 39 10/26/98 5:16p Jimdose
// Added CalcPath and DisconnectPath
//
// 38 10/26/98 4:42p Jimdose
// added recalcpaths
//
// 37 10/25/98 11:53p Jimdose
// added EXPORT_TEMPLATE
//
// 36 10/18/98 3:22a Jimdose
// Added code for timing paths
//
// 35 10/14/98 10:59p Jimdose
// map name is now passed into Init
//
// 34 10/14/98 5:22p Markd
// Changed jumptarget to target
//
// 33 9/22/98 12:49p Markd
// Put in archive and unarchive functions
//
// 32 9/18/98 10:59p Jimdose
// Started on swimming actors
//
// 31 9/14/98 5:40p Jimdose
// NearestNode now requires that you pass in the entity that is going to use
// the path.
// NearestNode no longer hard codes the size when testing moves
// Added ai_shownodenums
// Revamped tests for valid paths. Actors get stuck much less often.
// Made DrawAllConnections more usefull.
// ClearPathTo now uses binary search for finding the max height of a path.
// Upped version number
// Added FindEntities to fixup door entity numbers when loading pathfiles
// Fixed definition of MAX_WIDTH (it was still multiplied by 2 from the way it
// used to work).
//
// 30 9/08/98 9:51p Jimdose
// Changed NUM_PATHSPERNODE and NUM_WIDTH_VALUES
// Made CHECK_PATH use a granularity of 8 instead of 16
//
// 29 9/03/98 9:12p Jimdose
// made paths aware of doors
//
// 28 8/29/98 9:53p Jimdose
// moved prototype of SV_TestMovestep to g_phys.h
//
// 27 8/24/98 6:58p Jimdose
// Added array of heights for each width that a path supports
//
// 26 8/19/98 7:59p Jimdose
// Began adding support for ladder and jump nodes
//
// 25 8/18/98 10:03p Jimdose
// Separated checks for connection from checks for near nodes so that non-door
// nodes will not connect to nodes through doors
//
// 24 8/15/98 5:32p Jimdose
// Increased MAX_PATHNODES
//
// 23 7/26/98 6:42a Jimdose
// Added entnum to pathnode
//
// 22 7/26/98 5:37a Jimdose
// added occupiedTime
//
// 21 7/26/98 2:41a Jimdose
// Path files moved to maps directory
// Path files now contain full connection information to improve load times
//
// 20 7/25/98 2:10a Jimdose
// Preparing for door nodes
//
// 19 7/17/98 8:34p Jimdose
// Fixed bug where CreatePath would overwrite the stack if the path was longer
// than MAX_PATH_LENGTH
//
// 18 6/13/98 8:21p Jimdose
// Moved search algorithm to separate object - PathFinder
// Removed optimize path stuff
// Added maxwidth and maxheight to paths
//
// 17 6/09/98 5:32p Jimdose
// added ai_maxnode
//
// 16 5/27/98 5:11a Jimdose
// working on ai
//
// 15 5/25/98 5:31p Jimdose
// Pathnodes are no longer a subclass of Entity. This was done to save on
// edicts
//
// 14 5/22/98 9:40p Jimdose
// Made paths check through Movers
//
// 13 5/18/98 8:14p Jimdose
// Renamed Navigator back to PathManager
//
// 12 5/14/98 10:26p Jimdose
// Added UpdateNode
// Made alot of info in PathNode private
//
// 11 5/13/98 4:50p Jimdose
// Added use of SafePtrs
//
// 10 5/09/98 8:04p Jimdose
// added path saving and loading
// PathSearch now a Listener instead of a Class object
//
// 9 5/07/98 10:43p Jimdose
// added archiving
//
// 8 4/29/98 5:39p Jimdose
// added ai_checkroutes
//
// 7 4/27/98 5:27p Jimdose
// working on ai
//
// 6 4/20/98 2:45p Jimdose
// working on ai
//
// 5 4/18/98 3:02p Jimdose
// Added ai_createnodes and ai_showpath
// working on ai
//
// 4 4/16/98 2:10p Jimdose
// Major rewrite
//
// 3 3/02/98 8:49p Jimdose
// Changed CLASS_PROTOTYPE to only take the classname
//
// 2 3/02/98 5:44p Jimdose
// Continued development. Now use Path class to represent finished paths.
//
// 1 2/25/98 2:27p Jimdose
//
// 8 2/21/98 1:07p Jimdose
// Updated for Q2 dlls
//
// 6 12/06/97 6:32p Jimdose
// Further evolution on path code.
//
// 5 11/12/97 2:11p Jimdose
// Simplified the interface to PathSearch
//
// 4 11/07/97 6:39p Jimdose
// More work on integrating with Sin
//
// 3 11/05/97 4:00p Jimdose
// More work converting to work with Sin.
//
// 2 9/26/97 6:14p Jimdose
// Added standard Ritual headers
//
// DESCRIPTION:
// Potentially could be an C++ implementation of the A* search algorithm, but
// is currently unfinished.
//
#ifndef __NAVIGATE_H__
#define __NAVIGATE_H__
#include "g_local.h"
#include "class.h"
#include "entity.h"
#include "stack.h"
#include "container.h"
#include "doors.h"
extern Event EV_AI_SavePaths;
extern Event EV_AI_SaveNodes;
extern Event EV_AI_LoadNodes;
extern Event EV_AI_ClearNodes;
extern Event EV_AI_RecalcPaths;
extern Event EV_AI_CalcPath;
extern Event EV_AI_DisconnectPath;
extern cvar_t *ai_createnodes;
extern cvar_t *ai_showpath;
extern cvar_t *ai_shownodes;
extern cvar_t *ai_debugpath;
extern cvar_t *ai_debuginfo;
extern cvar_t *ai_showroutes;
extern cvar_t *ai_timepaths;
extern int ai_maxnode;
#define MAX_PATHCHECKSPERFRAME 4
extern int path_checksthisframe;
#define MAX_PATH_LENGTH 128 // should be more than plenty
#define NUM_PATHSPERNODE 16
class Path;
class PathNode;
#define NUM_WIDTH_VALUES 16
#define WIDTH_STEP 8
#define MAX_WIDTH ( WIDTH_STEP * NUM_WIDTH_VALUES )
#define MAX_HEIGHT 128
#define CHECK_PATH( path, width, height ) \
( ( ( ( width ) >= MAX_WIDTH ) || ( ( width ) < 0 ) ) ? false : \
( ( int )( path )->maxheight[ ( ( width ) / WIDTH_STEP ) - 1 ] < ( int )( height ) ) )
typedef struct
{
short node;
short moveCost;
byte maxheight[ NUM_WIDTH_VALUES ];
int door;
} pathway_t;
typedef enum { NOT_IN_LIST, IN_OPEN, IN_CLOSED } pathlist_t;
#define AI_FLEE 1
#define AI_DUCK 2
#define AI_COVER 4
#define AI_DOOR 8
#define AI_JUMP 16
#define AI_LADDER 32
#define AI_ACTION 64
void EXPORT_FROM_DLL DrawAllConnections( void );
class EXPORT_FROM_DLL PathNode : public Listener
{
public:
PathNode *chain;
pathway_t Child[ NUM_PATHSPERNODE ]; // these are the real connections between nodex
int numChildren;
// These variables are all used during the search
int f;
int h;
int g;
int gridX;
int gridY;
float drawtime;
float occupiedTime;
int entnum;
pathlist_t inlist;
// reject is used to indicate that a node is unfit for ending on during a search
qboolean reject;
PathNode *Parent;
// For the open and closed lists
PathNode *NextNode;
int nodeflags;
friend class PathSearch;
friend void DrawAllConnections( void );
private :
qboolean TestMove( Entity *ent, Vector start, Vector end, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false );
qboolean ConnectedTo( PathNode *node );
void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], float cost, Door *door = NULL );
void ConnectTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] );
void Disconnect( PathNode *node );
void FindChildren( Event *ev );
void FindEntities( Event *ev );
public:
CLASS_PROTOTYPE( PathNode );
int contents;
Vector worldorigin;
Vector worldangles;
Vector mins;
Vector maxs;
str targetname;
str target;
int nodenum;
qboolean setangles;
str animname;
PathNode();
~PathNode();
void Setup( Vector pos );
void setAngles( Vector ang );
void setOrigin( Vector org );
void setSize( Vector min, Vector max );
str &TargetName( void );
virtual void Archive( Archiver &arc );
virtual void Unarchive( Archiver &arc );
qboolean CheckPath( PathNode *node, Vector min, Vector max, qboolean fulltest = true );
Door *CheckDoor( Vector pos );
qboolean CheckMove( Entity *ent, Vector pos, Vector &min, Vector &max, qboolean allowdoors = false, qboolean fulltest = false );
qboolean CheckMove( Vector pos, Vector min, Vector max );
qboolean ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest = true );
qboolean LadderTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] );
void DrawConnections( void );
};
//
// Exported templated classes must be explicitly instantiated
//
#ifdef EXPORT_TEMPLATE
template class EXPORT_FROM_DLL Stack<PathNode *>;
template class EXPORT_FROM_DLL SafePtr<PathNode>;
#endif
typedef SafePtr<PathNode> PathNodePtr;
#define PATHMAP_GRIDSIZE 32
#define PATHMAP_CELLSIZE ( 8192 / PATHMAP_GRIDSIZE )
#define PATHMAP_NODES 126 // 128 - sizeof( int ) / sizeof( short )
class EXPORT_FROM_DLL MapCell
{
private :
int numnodes;
short nodes[ PATHMAP_NODES ];
public :
MapCell();
~MapCell();
void Init( void );
qboolean AddNode( PathNode *node );
qboolean RemoveNode( PathNode *node );
PathNode *GetNode( int index );
int NumNodes( void );
};
class EXPORT_FROM_DLL PathSearch : public Listener
{
private:
MapCell PathMap[ PATHMAP_GRIDSIZE ][ PATHMAP_GRIDSIZE ];
void AddToGrid( PathNode *node, int x, int y );
qboolean RemoveFromGrid( PathNode *node, int x, int y );
int NodeCoordinate( float coord );
int GridCoordinate( float coord );
void ClearNodes( Event *ev );
void LoadNodes( Event *ev );
void SaveNodes( Event *ev );
void SavePathsEvent( Event *ev );
void SetNodeFlagsEvent( Event *ev );
void RecalcPathsEvent( Event *ev );
void CalcPathEvent( Event *ev );
void DisconnectPathEvent( Event *ev );
public:
CLASS_PROTOTYPE( PathSearch );
void Archive( Archiver &arc );
void Unarchive( Archiver &arc );
void AddNode( PathNode *node );
void RemoveNode( PathNode *node );
void UpdateNode( PathNode *node );
MapCell *GetNodesInCell( int x, int y );
MapCell *GetNodesInCell( Vector pos );
PathNode *NearestNode( Vector pos, Entity *ent = NULL, qboolean usebbox = true );
void Teleport( Entity *teleportee, Vector from, Vector to );
void ShowNodes( void );
int NumNodes( void );
void SavePaths( void );
void Init( const char *mapname );
};
extern PathSearch PathManager;
#define MAX_PATHNODES 2048
PathNode *AI_FindNode( const char *name );
PathNode *AI_GetNode( int num );
void AI_AddNode( PathNode *node );
void AI_RemoveNode( PathNode *node );
void AI_ResetNodes( void );
#include "path.h"
template<class Heuristic>
class EXPORT_FROM_DLL PathFinder
{
private:
Stack<PathNode *> stack;
PathNode *OPEN;
PathNode *CLOSED;
PathNode *endnode;
void ClearPath( void );
void ClearOPEN( void );
void ClearCLOSED( void );
PathNode *ReturnBestNode( void );
void GenerateSuccessors( PathNode *BestNode );
void Insert( PathNode *Successor );
void PropagateDown( PathNode *Old );
Path *CreatePath( PathNode *startnode );
public:
Heuristic heuristic;
PathFinder();
~PathFinder<Heuristic>();
Path *FindPath( PathNode *from, PathNode *to );
Path *FindPath( Vector start, Vector end );
};
template <class Heuristic>
PathFinder<Heuristic>::PathFinder()
{
OPEN = NULL;
CLOSED = NULL;
}
template <class Heuristic>
PathFinder<Heuristic>::~PathFinder()
{
ClearPath();
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::ClearOPEN
(
void
)
{
PathNode *node;
while( OPEN )
{
node = OPEN;
OPEN = node->NextNode;
node->inlist = NOT_IN_LIST;
node->NextNode = NULL;
node->Parent = NULL;
// reject is used to indicate that a node is unfit for ending on during a search
node->reject = false;
}
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::ClearCLOSED
(
void
)
{
PathNode *node;
while( CLOSED )
{
node = CLOSED;
CLOSED = node->NextNode;
node->inlist = NOT_IN_LIST;
node->NextNode = NULL;
node->Parent = NULL;
// reject is used to indicate that a node is unfit for ending on during a search
node->reject = false;
}
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::ClearPath
(
void
)
{
stack.Clear();
ClearOPEN();
ClearCLOSED();
}
template <class Heuristic>
EXPORT_FROM_DLL Path *PathFinder<Heuristic>::FindPath
(
PathNode *from,
PathNode *to
)
{
Path *path;
PathNode *node;
int start;
int end;
qboolean checktime;
checktime = false;
if ( ai_timepaths->value )
{
start = G_Milliseconds();
checktime = true;
}
OPEN = NULL;
CLOSED = NULL;
endnode = to;
// Should always be NULL at this point
assert( !from->NextNode );
// make Open List point to first node
OPEN = from;
from->g = 0;
from->h = heuristic.dist( from, endnode );
from->NextNode = NULL;
node = ReturnBestNode();
while( node && !heuristic.done( node, endnode ) )
{
GenerateSuccessors( node );
node = ReturnBestNode();
}
if ( !node )
{
path = NULL;
if ( ai_debugpath->value )
{
gi.dprintf( "Search failed--no path found.\n" );
}
}
else
{
path = CreatePath( node );
}
ClearPath();
if ( checktime )
{
end = G_Milliseconds();
if ( ai_timepaths->value <= ( end - start ) )
{
G_DebugPrintf( "%d: ent #%d : %d\n", level.framenum, heuristic.entnum, end - start );
}
}
return path;
}
template <class Heuristic>
EXPORT_FROM_DLL Path *PathFinder<Heuristic>::FindPath
(
Vector start,
Vector end
)
{
PathNode *from;
PathNode *to;
Entity *ent;
ent = G_GetEntity( heuristic.entnum );
from = PathManager.NearestNode( start, ent );
to = PathManager.NearestNode( end, ent );
if ( !from )
{
if ( ai_debugpath->value )
{
gi.dprintf( "Search failed--couldn't find closest source.\n" );
}
return NULL;
}
if ( !from || !to )
{
if ( ai_debugpath->value )
{
gi.dprintf( "Search failed--couldn't find closest destination.\n" );
}
return NULL;
}
return FindPath( from, to );
}
template <class Heuristic>
EXPORT_FROM_DLL Path *PathFinder<Heuristic>::CreatePath
(
PathNode *startnode
)
{
PathNode *node;
Path *p;
int i;
int n;
PathNode *reverse[ MAX_PATH_LENGTH ];
// unfortunately, the list goes goes from end to start, so we have to reverse it
for( node = startnode, n = 0; ( node != NULL ) && ( n < MAX_PATH_LENGTH ); node = node->Parent, n++ )
{
assert( n < MAX_PATH_LENGTH );
reverse[ n ] = node;
}
p = new Path( n );
for( i = n - 1; i >= 0; i-- )
{
p->AddNode( reverse[ i ] );
}
if ( ai_debugpath->value )
{
gi.dprintf( "%d nodes in path\n", n );
gi.dprintf( "%d nodes allocated\n", PathManager.NumNodes() );
}
return p;
}
template <class Heuristic>
EXPORT_FROM_DLL PathNode *PathFinder<Heuristic>::ReturnBestNode
(
void
)
{
PathNode *bestnode;
if ( !OPEN )
{
// No more nodes on OPEN
return NULL;
}
// Pick node with lowest f, in this case it's the first node in list
// because we sort the OPEN list wrt lowest f. Call it BESTNODE.
bestnode = OPEN; // point to first node on OPEN
OPEN = bestnode->NextNode; // Make OPEN point to nextnode or NULL.
// Next take BESTNODE (or temp in this case) and put it on CLOSED
bestnode->NextNode = CLOSED;
CLOSED = bestnode;
bestnode->inlist = IN_CLOSED;
return( bestnode );
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::GenerateSuccessors
(
PathNode *BestNode
)
{
int i;
int g; // total path cost - as stored in the linked lists.
PathNode *node;
pathway_t *path;
for( i = 0; i < BestNode->numChildren; i++ )
{
path = &BestNode->Child[ i ];
node = AI_GetNode( path->node );
// g(Successor)=g(BestNode)+cost of getting from BestNode to Successor
g = BestNode->g + heuristic.cost( BestNode, i );
switch( node->inlist )
{
case NOT_IN_LIST :
// Only allow this if it's valid
if ( heuristic.validpath( BestNode, i ) )
{
node->Parent = BestNode;
node->g = g;
node->h = heuristic.dist( node, endnode );
node->f = g + node->h;
// Insert Successor on OPEN list wrt f
Insert( node );
}
break;
case IN_OPEN :
// if our new g value is < node's then reset node's parent to point to BestNode
if ( g < node->g )
{
node->Parent = BestNode;
node->g = g;
node->f = g + node->h;
}
break;
case IN_CLOSED :
// if our new g value is < Old's then reset Old's parent to point to BestNode
if ( g < node->g )
{
node->Parent = BestNode;
node->g = g;
node->f = g + node->h;
// Since we changed the g value of Old, we need
// to propagate this new value downwards, i.e.
// do a Depth-First traversal of the tree!
PropagateDown( node );
}
break;
default :
// shouldn't happen, but try to catch it during debugging phase
assert( NULL );
gi.error( "Corrupted path node" );
break;
}
}
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::Insert
(
PathNode *node
)
{
PathNode *prev;
PathNode *next;
int f;
node->inlist = IN_OPEN;
f = node->f;
// special case for if the list is empty, or it should go at head of list (lowest f)
if ( ( OPEN == NULL ) || ( f < OPEN->f ) )
{
node->NextNode = OPEN;
OPEN = node;
return;
}
// do sorted insertion into OPEN list in order of ascending f
prev = OPEN;
next = OPEN->NextNode;
while( ( next != NULL ) && ( next->f < f ) )
{
prev = next;
next = next->NextNode;
}
// insert it between the two nodes
node->NextNode = next;
prev->NextNode = node;
}
template <class Heuristic>
EXPORT_FROM_DLL void PathFinder<Heuristic>::PropagateDown
(
PathNode *node
)
{
int c;
unsigned g;
unsigned movecost;
PathNode *child;
PathNode *parent;
pathway_t *path;
int n;
g = node->g;
n = node->numChildren;
for( c = 0; c < n; c++ )
{
path = &node->Child[ c ];
child = AI_GetNode( path->node );
movecost = g + heuristic.cost( node, c );
if ( movecost < child->g )
{
child->g = movecost;
child->f = child->g + child->h;
child->Parent = node;
// reset parent to new path.
// Now the Child's branch need to be
// checked out. Remember the new cost must be propagated down.
stack.Push( child );
}
}
while( !stack.Empty() )
{
parent = stack.Pop();
n = parent->numChildren;
for( c = 0; c < n; c++ )
{
path = &parent->Child[ c ];
child = AI_GetNode( path->node );
// we stop the propagation when the g value of the child is equal or better than
// the cost we're propagating
movecost = parent->g + path->moveCost;
if ( movecost < child->g )
{
child->g = movecost;
child->f = child->g + child->h;
child->Parent = parent;
stack.Push( child );
}
}
}
}
class EXPORT_FROM_DLL StandardMovement
{
public:
int minwidth;
int minheight;
int entnum;
inline void setSize
(
Vector size
)
{
minwidth = max( size.x, size.y );
minheight = size.z;
}
inline int dist
(
PathNode *node,
PathNode *end
)
{
Vector delta;
int d1;
int d2;
int d3;
int h;
delta = node->worldorigin - end->worldorigin;
d1 = abs( ( int )delta[ 0 ] );
d2 = abs( ( int )delta[ 1 ] );
d3 = abs( ( int )delta[ 2 ] );
h = max( d1, d2 );
h = max( d3, h );
return h;
}
inline qboolean validpath
(
PathNode *node,
int i
)
{
pathway_t *path;
PathNode *n;
path = &node->Child[ i ];
if ( CHECK_PATH( path, minwidth, minheight ) )
{
return false;
}
if ( path->door )
{
Door *door;
door = ( Door * )G_GetEntity( path->door );
if ( !door->CanBeOpenedBy( G_GetEntity( entnum ) ) )
{
return false;
}
}
n = AI_GetNode( path->node );
if ( n && ( n->occupiedTime > level.time ) && ( n->entnum != entnum ) )
{
return false;
}
return true;
}
inline int cost
(
PathNode *node,
int i
)
{
return node->Child[ i ].moveCost;
}
inline qboolean done
(
PathNode *node,
PathNode *end
)
{
return node == end;
}
};
#ifdef EXPORT_TEMPLATE
template class EXPORT_FROM_DLL PathFinder<StandardMovement>;
#endif
typedef PathFinder<StandardMovement> StandardMovePath;
#endif /* navigate.h */