2008-09-15 01:00:17 -05:00
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
# include "cbase.h"
# include "util_shared.h"
# include "nav_mesh.h"
# include "nav_node.h"
# include "nav_pathfind.h"
# include "viewport_panel_names.h"
enum { MAX_BLOCKED_AREAS = 256 } ;
static unsigned int blockedID [ MAX_BLOCKED_AREAS ] ;
static int blockedIDCount = 0 ;
static float lastMsgTime = 0.0f ;
ConVar nav_slope_limit ( " nav_slope_limit " , " 0.7 " , FCVAR_GAMEDLL , " The ground unit normal's Z component must be greater than this for nav areas to be generated. " ) ;
ConVar nav_restart_after_analysis ( " nav_restart_after_analysis " , " 1 " , FCVAR_GAMEDLL , " When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation. " ) ;
//--------------------------------------------------------------------------------------------------------------
inline float round ( float val , float unit )
{
val = val + ( ( val < 0.0f ) ? - unit * 0.5f : unit * 0.5f ) ;
return ( float ) ( ( ( int ) val ) / ( int ) unit ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost , paying attention to " blocked " areas
*/
class ApproachAreaCost
{
public :
float operator ( ) ( CNavArea * area , CNavArea * fromArea , const CNavLadder * ladder )
{
// check if this area is "blocked"
for ( int i = 0 ; i < blockedIDCount ; + + i )
{
if ( area - > GetID ( ) = = blockedID [ i ] )
{
return - 1.0f ;
}
}
if ( fromArea = = NULL )
{
// first area in path, no cost
return 0.0f ;
}
else
{
// compute distance traveled along path so far
float dist ;
if ( ladder )
{
dist = ladder - > m_length ;
}
else
{
dist = ( area - > GetCenter ( ) - fromArea - > GetCenter ( ) ) . Length ( ) ;
}
float cost = dist + fromArea - > GetCostSoFar ( ) ;
return cost ;
}
}
} ;
//--------------------------------------------------------------------------------------------------------------
/**
* Determine the set of " approach areas " .
* An approach area is an area representing a place where players
* move into / out of our local neighborhood of areas .
* @ todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi - directional
*/
void CNavArea : : ComputeApproachAreas ( void )
{
m_approachCount = 0 ;
if ( nav_quicksave . GetBool ( ) )
return ;
// use the center of the nav area as the "view" point
Vector eye = m_center ;
if ( TheNavMesh - > GetGroundHeight ( eye , & eye . z ) = = false )
return ;
// approximate eye position
if ( GetAttributes ( ) & NAV_MESH_CROUCH )
eye . z + = 0.9f * HalfHumanHeight ;
else
eye . z + = 0.9f * HumanHeight ;
enum { MAX_PATH_LENGTH = 256 } ;
CNavArea * path [ MAX_PATH_LENGTH ] ;
ApproachAreaCost cost ;
enum SearchType
{
FROM_EYE , ///< start search from our eyepoint outward to farArea
TO_EYE , ///< start search from farArea beack towards our eye
SEARCH_FINISHED
} ;
//
// In order to *completely* enumerate all of the approach areas, we
// need to search from our eyepoint outward, as well as from outwards
// towards our eyepoint
//
for ( int searchType = FROM_EYE ; searchType ! = SEARCH_FINISHED ; + + searchType )
{
//
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
//
int it ;
for ( it = TheNavAreaList . Head ( ) ; it ! = TheNavAreaList . InvalidIndex ( ) ; it = TheNavAreaList . Next ( it ) )
{
CNavArea * farArea = TheNavAreaList [ it ] ;
blockedIDCount = 0 ;
// skip the small areas
const float minSize = 200.0f ; // 150
const Extent & extent = farArea - > GetExtent ( ) ;
if ( extent . SizeX ( ) < minSize | | extent . SizeY ( ) < minSize )
{
continue ;
}
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if ( farArea - > IsVisible ( eye ) )
{
continue ;
}
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while ( m_approachCount < MAX_APPROACH_AREAS )
{
CNavArea * from , * to ;
if ( searchType = = FROM_EYE )
{
// find another path *to* 'farArea'
// we must pathfind from us in order to pick up one-way paths OUT OF our area
from = this ;
to = farArea ;
}
else // TO_EYE
{
// find another path *from* 'farArea'
// we must pathfind to us in order to pick up one-way paths INTO our area
from = farArea ;
to = this ;
}
// build the actual path
if ( NavAreaBuildPath ( from , to , NULL , cost ) = = false )
{
break ;
}
// find number of areas on path
int count = 0 ;
CNavArea * area ;
for ( area = to ; area ; area = area - > GetParent ( ) )
{
+ + count ;
}
if ( count > MAX_PATH_LENGTH )
{
count = MAX_PATH_LENGTH ;
}
// if the path is only two areas long, there can be no approach points
if ( count < = 2 )
{
break ;
}
// build path starting from eye
int i = 0 ;
if ( searchType = = FROM_EYE )
{
for ( area = to ; i < count & & area ; area = area - > GetParent ( ) )
{
path [ count - i - 1 ] = area ;
+ + i ;
}
}
else // TO_EYE
{
for ( area = to ; i < count & & area ; area = area - > GetParent ( ) )
{
path [ i + + ] = area ;
}
}
// traverse path to find first area we cannot see (skip the first area)
for ( i = 1 ; i < count ; + + i )
{
// if we see this area, continue on
if ( path [ i ] - > IsVisible ( eye ) )
{
continue ;
}
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
if ( blockedIDCount = = MAX_BLOCKED_AREAS )
{
Msg ( " Overflow computing approach areas for area #%d. \n " , m_id ) ;
return ;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = ( path [ i ] = = farArea ) ? i - 1 : i ;
// dont block the start area, or all subsequence pathfinds will fail
if ( block = = 0 )
{
continue ;
}
blockedID [ blockedIDCount + + ] = path [ block ] - > GetID ( ) ;
// store new approach area if not already in set
int a ;
for ( a = 0 ; a < m_approachCount ; + + a )
{
if ( m_approach [ a ] . here . area = = path [ block - 1 ] )
{
break ;
}
}
if ( a = = m_approachCount )
{
m_approach [ m_approachCount ] . prev . area = ( block > = 2 ) ? path [ block - 2 ] : NULL ;
m_approach [ m_approachCount ] . here . area = path [ block - 1 ] ;
m_approach [ m_approachCount ] . prevToHereHow = path [ block - 1 ] - > GetParentHow ( ) ;
m_approach [ m_approachCount ] . next . area = path [ block ] ;
m_approach [ m_approachCount ] . hereToNextHow = path [ block ] - > GetParentHow ( ) ;
+ + m_approachCount ;
}
// we are done with this path
break ;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Start at given position and find first area in given direction
*/
inline CNavArea * findFirstAreaInDirection ( const Vector * start , NavDirType dir , float range , float beneathLimit , CBaseEntity * traceIgnore = NULL , Vector * closePos = NULL )
{
CNavArea * area = NULL ;
Vector pos = * start ;
int end = ( int ) ( ( range / GenerationStepSize ) + 0.5f ) ;
for ( int i = 1 ; i < = end ; i + + )
{
AddDirectionVector ( & pos , dir , GenerationStepSize ) ;
// make sure we dont look thru the wall
trace_t result ;
UTIL_TraceLine ( * start , pos , MASK_PLAYERSOLID_BRUSHONLY , traceIgnore , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction < 1.0f )
break ;
area = TheNavMesh - > GetNavArea ( pos , beneathLimit ) ;
if ( area )
{
if ( closePos )
{
closePos - > x = pos . x ;
closePos - > y = pos . y ;
closePos - > z = area - > GetZ ( pos . x , pos . y ) ;
}
break ;
}
}
return area ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* For each ladder in the map , create a navigation representation of it .
*/
void CNavMesh : : BuildLadders ( void )
{
// remove any left-over ladders
DestroyLadders ( ) ;
trace_t result ;
CInfoLadder * entity = NULL ;
while ( ( entity = dynamic_cast < CInfoLadder * > ( gEntList . FindEntityByClassname ( entity , " info_ladder " ) ) ) ! = NULL )
{
CreateLadder ( entity - > WorldAlignMins ( ) , entity - > WorldAlignMaxs ( ) , NULL ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a navigation representation of a ladder .
*/
void CNavMesh : : CreateLadder ( const Vector & absMin , const Vector & absMax , const Vector2D * ladderDir )
{
CNavLadder * ladder = new CNavLadder ;
// compute top & bottom of ladder
ladder - > m_top . x = ( absMin . x + absMax . x ) / 2.0f ;
ladder - > m_top . y = ( absMin . y + absMax . y ) / 2.0f ;
ladder - > m_top . z = absMax . z ;
ladder - > m_bottom . x = ladder - > m_top . x ;
ladder - > m_bottom . y = ladder - > m_top . y ;
ladder - > m_bottom . z = absMin . z ;
// determine facing - assumes "normal" runged ladder
float xSize = absMax . x - absMin . x ;
float ySize = absMax . y - absMin . y ;
trace_t result ;
if ( xSize > ySize )
{
// ladder is facing north or south - determine which way
// "pull in" traceline from bottom and top in case ladder abuts floor and/or ceiling
Vector from = ladder - > m_bottom + Vector ( 0.0f , GenerationStepSize , GenerationStepSize / 2 ) ;
Vector to = ladder - > m_top + Vector ( 0.0f , GenerationStepSize , - GenerationStepSize / 2 ) ;
UTIL_TraceLine ( from , to , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f | | result . startsolid )
ladder - > SetDir ( NORTH ) ;
else
ladder - > SetDir ( SOUTH ) ;
ladder - > m_width = xSize ;
}
else
{
// ladder is facing east or west - determine which way
Vector from = ladder - > m_bottom + Vector ( GenerationStepSize , 0.0f , GenerationStepSize / 2 ) ;
Vector to = ladder - > m_top + Vector ( GenerationStepSize , 0.0f , - GenerationStepSize / 2 ) ;
UTIL_TraceLine ( from , to , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f | | result . startsolid )
ladder - > SetDir ( WEST ) ;
else
ladder - > SetDir ( EAST ) ;
ladder - > m_width = ySize ;
}
// adjust top and bottom of ladder to make sure they are reachable
// (cs_office has a crate right in front of the base of a ladder)
Vector along = ladder - > m_top - ladder - > m_bottom ;
float length = along . NormalizeInPlace ( ) ;
Vector on , out ;
const float minLadderClearance = 32.0f ;
// adjust bottom to bypass blockages
const float inc = 10.0f ;
float t ;
for ( t = 0.0f ; t < = length ; t + = inc )
{
on = ladder - > m_bottom + t * along ;
out = on + ladder - > GetNormal ( ) * minLadderClearance ;
UTIL_TraceLine ( on , out , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction = = 1.0f & & ! result . startsolid )
{
// found viable ladder bottom
ladder - > m_bottom = on ;
break ;
}
}
// adjust top to bypass blockages
for ( t = 0.0f ; t < = length ; t + = inc )
{
on = ladder - > m_top - t * along ;
out = on + ladder - > GetNormal ( ) * minLadderClearance ;
UTIL_TraceLine ( on , out , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction = = 1.0f & & ! result . startsolid )
{
// found viable ladder top
ladder - > m_top = on ;
break ;
}
}
ladder - > m_length = ( ladder - > m_top - ladder - > m_bottom ) . Length ( ) ;
ladder - > SetDir ( ladder - > GetDir ( ) ) ; // now that we've adjusted the top and bottom, re-check the normal
ladder - > m_bottomArea = NULL ;
ladder - > m_topForwardArea = NULL ;
ladder - > m_topLeftArea = NULL ;
ladder - > m_topRightArea = NULL ;
ladder - > m_topBehindArea = NULL ;
ladder - > ConnectGeneratedLadder ( ) ;
// add ladder to global list
m_ladderList . AddToTail ( ladder ) ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavLadder : : ConnectGeneratedLadder ( void )
{
const float nearLadderRange = 75.0f ; // 50
//
// Find naviagtion area at bottom of ladder
//
// get approximate postion of player on ladder
Vector center = m_bottom + Vector ( 0 , 0 , GenerationStepSize ) ;
AddDirectionVector ( & center , m_dir , HalfHumanWidth ) ;
m_bottomArea = TheNavMesh - > GetNearestNavArea ( center , true ) ;
if ( ! m_bottomArea )
{
DevMsg ( " ERROR: Unconnected ladder bottom at ( %g, %g, %g ) \n " , m_bottom . x , m_bottom . y , m_bottom . z ) ;
}
else
{
// store reference to ladder in the area
m_bottomArea - > AddLadderUp ( this ) ;
}
//
// Find adjacent navigation areas at the top of the ladder
//
// get approximate postion of player on ladder
center = m_top + Vector ( 0 , 0 , GenerationStepSize ) ;
AddDirectionVector ( & center , m_dir , HalfHumanWidth ) ;
2011-04-28 01:29:22 -05:00
float beneathLimit = MIN ( 120.0f , m_top . z - m_bottom . z + HalfHumanWidth ) ;
2008-09-15 01:00:17 -05:00
// find "ahead" area
m_topForwardArea = findFirstAreaInDirection ( & center , OppositeDirection ( m_dir ) , nearLadderRange , beneathLimit , NULL ) ;
if ( m_topForwardArea = = m_bottomArea )
m_topForwardArea = NULL ;
// find "left" area
m_topLeftArea = findFirstAreaInDirection ( & center , DirectionLeft ( m_dir ) , nearLadderRange , beneathLimit , NULL ) ;
if ( m_topLeftArea = = m_bottomArea )
m_topLeftArea = NULL ;
// find "right" area
m_topRightArea = findFirstAreaInDirection ( & center , DirectionRight ( m_dir ) , nearLadderRange , beneathLimit , NULL ) ;
if ( m_topRightArea = = m_bottomArea )
m_topRightArea = NULL ;
// find "behind" area - must look farther, since ladder is against the wall away from this area
m_topBehindArea = findFirstAreaInDirection ( & center , m_dir , 2.0f * nearLadderRange , beneathLimit , NULL ) ;
if ( m_topBehindArea = = m_bottomArea )
m_topBehindArea = NULL ;
// can't include behind area, since it is not used when going up a ladder
if ( ! m_topForwardArea & & ! m_topLeftArea & & ! m_topRightArea )
DevMsg ( " ERROR: Unconnected ladder top at ( %g, %g, %g ) \n " , m_top . x , m_top . y , m_top . z ) ;
// store reference to ladder in the area(s)
if ( m_topForwardArea )
m_topForwardArea - > AddLadderDown ( this ) ;
if ( m_topLeftArea )
m_topLeftArea - > AddLadderDown ( this ) ;
if ( m_topRightArea )
m_topRightArea - > AddLadderDown ( this ) ;
if ( m_topBehindArea )
{
m_topBehindArea - > AddLadderDown ( this ) ;
Disconnect ( m_topBehindArea ) ;
}
// adjust top of ladder to highest connected area
float topZ = m_bottom . z + 5.0f ;
bool topAdjusted = false ;
CNavArea * topAreaList [ 4 ] ;
topAreaList [ 0 ] = m_topForwardArea ;
topAreaList [ 1 ] = m_topLeftArea ;
topAreaList [ 2 ] = m_topRightArea ;
topAreaList [ 3 ] = m_topBehindArea ;
for ( int a = 0 ; a < 4 ; + + a )
{
CNavArea * topArea = topAreaList [ a ] ;
if ( topArea = = NULL )
continue ;
Vector close ;
topArea - > GetClosestPointOnArea ( m_top , & close ) ;
if ( topZ < close . z )
{
topZ = close . z ;
topAdjusted = true ;
}
}
if ( topAdjusted )
{
m_top . z = topZ ;
}
//
// Determine whether this ladder is "dangling" or not
// "Dangling" ladders are too high to go up
//
if ( m_bottomArea )
{
Vector bottomSpot ;
m_bottomArea - > GetClosestPointOnArea ( m_bottom , & bottomSpot ) ;
if ( m_bottom . z - bottomSpot . z > HumanHeight )
{
m_bottomArea - > Disconnect ( this ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Mark all areas that require a jump to get through them .
* This currently relies on jump areas having extreme slope .
*/
void CNavMesh : : MarkJumpAreas ( void )
{
FOR_EACH_LL ( TheNavAreaList , it )
{
CNavArea * area = TheNavAreaList [ it ] ;
if ( ! area - > HasNodes ( ) )
continue ;
Vector normal , otherNormal ;
area - > ComputeNormal ( & normal ) ;
area - > ComputeNormal ( & otherNormal , true ) ;
if ( normal . z < nav_slope_limit . GetFloat ( ) | |
otherNormal . z < nav_slope_limit . GetFloat ( ) )
{
area - > SetAttributes ( area - > GetAttributes ( ) | NAV_MESH_JUMP ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Jump areas that are connected to only one non - jump area won ' t be used . Delete them .
*/
void CNavMesh : : RemoveUnusedJumpAreas ( void )
{
CUtlLinkedList < CNavArea * , int > unusedAreas ;
FOR_EACH_LL ( TheNavAreaList , it )
{
CNavArea * testArea = TheNavAreaList [ it ] ;
if ( ! ( testArea - > GetAttributes ( ) & NAV_MESH_JUMP ) )
{
continue ;
}
NavConnect connectedArea ;
NavLadderConnect connectedLadder ;
connectedArea . area = NULL ;
connectedLadder . ladder = NULL ;
bool doubleConnected = false ;
// Look at testArea->ladder connections
int i ;
for ( i = 0 ; i < CNavLadder : : NUM_LADDER_DIRECTIONS ; + + i )
{
const NavLadderConnectList * ladderList = testArea - > GetLadderList ( ( CNavLadder : : LadderDirectionType ) i ) ;
if ( ! ladderList )
{
continue ;
}
if ( ladderList - > Count ( ) = = 0 )
{
continue ;
}
if ( ladderList - > Count ( ) > 1 )
{
doubleConnected = true ;
break ;
}
NavLadderConnect ladderConnect = ( * ladderList ) [ ladderList - > Head ( ) ] ;
if ( connectedArea . area | | ( connectedLadder . ladder & & connectedLadder . ladder ! = ladderConnect . ladder ) )
{
doubleConnected = true ;
break ;
}
connectedLadder = ladderConnect ;
}
if ( doubleConnected )
{
continue ;
}
// Look at testArea->area connections
for ( i = 0 ; i < NUM_DIRECTIONS ; + + i )
{
const NavConnectList * areaList = testArea - > GetAdjacentList ( ( NavDirType ) i ) ;
if ( ! areaList )
{
continue ;
}
if ( areaList - > Count ( ) = = 0 )
{
continue ;
}
FOR_EACH_LL ( ( * areaList ) , ait )
{
NavConnect areaConnect = ( * areaList ) [ ait ] ;
if ( areaConnect . area - > GetAttributes ( ) & NAV_MESH_JUMP )
{
continue ;
}
if ( connectedLadder . ladder | | ( connectedArea . area & & connectedArea . area ! = areaConnect . area ) )
{
doubleConnected = true ;
break ;
}
connectedArea = areaConnect ;
}
}
if ( doubleConnected )
{
continue ;
}
// Look at ladder->testArea connections
FOR_EACH_LL ( m_ladderList , lit )
{
CNavLadder * ladder = m_ladderList [ lit ] ;
if ( ! ladder )
{
continue ;
}
if ( ! ladder - > IsConnected ( testArea , CNavLadder : : NUM_LADDER_DIRECTIONS ) )
{
continue ;
}
if ( connectedArea . area )
{
doubleConnected = true ;
break ;
}
if ( ladder = = connectedLadder . ladder )
{
continue ;
}
if ( connectedLadder . ladder )
{
doubleConnected = true ;
break ;
}
connectedLadder . ladder = ladder ;
}
if ( doubleConnected )
{
continue ;
}
// Look at area->testArea connections
FOR_EACH_LL ( TheNavAreaList , ait )
{
CNavArea * area = TheNavAreaList [ ait ] ;
if ( ! area )
{
continue ;
}
if ( area - > GetAttributes ( ) & NAV_MESH_JUMP )
{
continue ;
}
if ( ! area - > IsConnected ( testArea , NUM_DIRECTIONS ) )
{
continue ;
}
if ( connectedLadder . ladder )
{
doubleConnected = true ;
break ;
}
if ( area = = connectedArea . area )
{
continue ;
}
if ( connectedArea . area )
{
doubleConnected = true ;
break ;
}
connectedArea . area = area ;
}
if ( doubleConnected )
{
continue ;
}
// Since we got here, at most one ladder or non-jump area is connected to us, so we can be deleted.
unusedAreas . AddToTail ( testArea ) ;
}
FOR_EACH_LL ( unusedAreas , uit )
{
CNavArea * areaToDelete = unusedAreas [ uit ] ;
TheNavAreaList . FindAndRemove ( areaToDelete ) ;
delete areaToDelete ;
}
StripNavigationAreas ( ) ;
SetMarkedArea ( NULL ) ; // unmark the mark area
m_markedCorner = NUM_CORNERS ; // clear the corner selection
}
//--------------------------------------------------------------------------------------------------------------
void CNavMesh : : CommandNavRemoveUnusedJumpAreas ( void )
{
RemoveUnusedJumpAreas ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Recursively chop area in half along X until child areas are roughly square
*/
static void splitX ( CNavArea * area )
{
if ( area - > IsRoughlySquare ( ) )
return ;
float split = area - > GetSizeX ( ) ;
split / = 2.0f ;
split + = area - > GetExtent ( ) . lo . x ;
split = TheNavMesh - > SnapToGrid ( split ) ;
const float epsilon = 0.1f ;
if ( fabs ( split - area - > GetExtent ( ) . lo . x ) < epsilon | |
fabs ( split - area - > GetExtent ( ) . hi . x ) < epsilon )
{
// too small to subdivide
return ;
}
CNavArea * alpha , * beta ;
if ( area - > SplitEdit ( false , split , & alpha , & beta ) )
{
// split each new area until square
splitX ( alpha ) ;
splitX ( beta ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Recursively chop area in half along Y until child areas are roughly square
*/
static void splitY ( CNavArea * area )
{
if ( area - > IsRoughlySquare ( ) )
return ;
float split = area - > GetSizeY ( ) ;
split / = 2.0f ;
split + = area - > GetExtent ( ) . lo . y ;
split = TheNavMesh - > SnapToGrid ( split ) ;
const float epsilon = 0.1f ;
if ( fabs ( split - area - > GetExtent ( ) . lo . y ) < epsilon | |
fabs ( split - area - > GetExtent ( ) . hi . y ) < epsilon )
{
// too small to subdivide
return ;
}
CNavArea * alpha , * beta ;
if ( area - > SplitEdit ( true , split , & alpha , & beta ) )
{
// split each new area until square
splitY ( alpha ) ;
splitY ( beta ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Split any long , thin , areas into roughly square chunks .
*/
void CNavMesh : : SquareUpAreas ( void )
{
int it = TheNavAreaList . Head ( ) ;
while ( it ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ it ] ;
// move the iterator in case the current area is split and deleted
it = TheNavAreaList . Next ( it ) ;
if ( area - > HasNodes ( ) & & ! area - > IsRoughlySquare ( ) )
{
// chop this area into square pieces
if ( area - > GetSizeX ( ) > area - > GetSizeY ( ) )
splitX ( area ) ;
else
splitY ( area ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine if we can " jump down " from given point
*/
inline bool testJumpDown ( const Vector * fromPos , const Vector * toPos )
{
float dz = fromPos - > z - toPos - > z ;
// drop can't be too far, or too short (or nonexistant)
if ( dz < = JumpCrouchHeight | | dz > = DeathDrop )
return false ;
//
// Check LOS out and down
//
// ------+
// |
// F |
// |
// T
//
Vector from ( fromPos - > x , fromPos - > y , fromPos - > z + HumanHeight ) ;
Vector to ( toPos - > x , toPos - > y , from . z ) ;
trace_t result ;
UTIL_TraceLine ( from , to , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f | | result . startsolid )
return false ;
from = to ;
to . z = toPos - > z + 2.0f ;
UTIL_TraceLine ( from , to , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f | | result . startsolid )
return false ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
inline CNavArea * findJumpDownArea ( const Vector * fromPos , NavDirType dir )
{
Vector start ( fromPos - > x , fromPos - > y , fromPos - > z + HalfHumanHeight ) ;
AddDirectionVector ( & start , dir , GenerationStepSize / 2.0f ) ;
Vector toPos ;
CNavArea * downArea = findFirstAreaInDirection ( & start , dir , 4.0f * GenerationStepSize , DeathDrop , NULL , & toPos ) ;
if ( downArea & & testJumpDown ( fromPos , & toPos ) )
return downArea ;
return NULL ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Define connections between adjacent generated areas
*/
void CNavMesh : : ConnectGeneratedAreas ( void )
{
Msg ( " Connecting navigation areas... \n " ) ;
FOR_EACH_LL ( TheNavAreaList , it )
{
CNavArea * area = TheNavAreaList [ it ] ;
// scan along edge nodes, stepping one node over into the next area
// for now, only use bi-directional connections
// north edge
CNavNode * node ;
for ( node = area - > m_node [ NORTH_WEST ] ; node ! = area - > m_node [ NORTH_EAST ] ; node = node - > GetConnectedNode ( EAST ) )
{
CNavNode * adj = node - > GetConnectedNode ( NORTH ) ;
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( SOUTH ) = = node )
{
area - > ConnectTo ( adj - > GetArea ( ) , NORTH ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , NORTH ) ;
if ( downArea & & downArea ! = area )
area - > ConnectTo ( downArea , NORTH ) ;
}
}
// west edge
for ( node = area - > m_node [ NORTH_WEST ] ; node ! = area - > m_node [ SOUTH_WEST ] ; node = node - > GetConnectedNode ( SOUTH ) )
{
CNavNode * adj = node - > GetConnectedNode ( WEST ) ;
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( EAST ) = = node )
{
area - > ConnectTo ( adj - > GetArea ( ) , WEST ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , WEST ) ;
if ( downArea & & downArea ! = area )
area - > ConnectTo ( downArea , WEST ) ;
}
}
// south edge - this edge's nodes are actually part of adjacent areas
// move one node north, and scan west to east
/// @todo This allows one-node-wide areas - do we want this?
node = area - > m_node [ SOUTH_WEST ] ;
if ( node ) // pre-existing areas in incremental generates won't have nodes
{
node = node - > GetConnectedNode ( NORTH ) ;
}
if ( node )
{
CNavNode * end = area - > m_node [ SOUTH_EAST ] - > GetConnectedNode ( NORTH ) ;
/// @todo Figure out why cs_backalley gets a NULL node in here...
for ( ; node & & node ! = end ; node = node - > GetConnectedNode ( EAST ) )
{
CNavNode * adj = node - > GetConnectedNode ( SOUTH ) ;
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( NORTH ) = = node )
{
area - > ConnectTo ( adj - > GetArea ( ) , SOUTH ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , SOUTH ) ;
if ( downArea & & downArea ! = area )
area - > ConnectTo ( downArea , SOUTH ) ;
}
}
}
// east edge - this edge's nodes are actually part of adjacent areas
node = area - > m_node [ NORTH_EAST ] ;
if ( node ) // pre-existing areas in incremental generates won't have nodes
{
node = node - > GetConnectedNode ( WEST ) ;
}
if ( node )
{
CNavNode * end = area - > m_node [ SOUTH_EAST ] - > GetConnectedNode ( WEST ) ;
for ( ; node & & node ! = end ; node = node - > GetConnectedNode ( SOUTH ) )
{
CNavNode * adj = node - > GetConnectedNode ( EAST ) ;
if ( adj & & adj - > GetArea ( ) & & adj - > GetConnectedNode ( WEST ) = = node )
{
area - > ConnectTo ( adj - > GetArea ( ) , EAST ) ;
}
else
{
CNavArea * downArea = findJumpDownArea ( node - > GetPosition ( ) , EAST ) ;
if ( downArea & & downArea ! = area )
area - > ConnectTo ( downArea , EAST ) ;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Merge areas together to make larger ones ( must remain rectangular - convex ) .
* Areas can only be merged if their attributes match .
*/
void CNavMesh : : MergeGeneratedAreas ( void )
{
Msg ( " Merging navigation areas... \n " ) ;
bool merged ;
do
{
merged = false ;
FOR_EACH_LL ( TheNavAreaList , it )
{
CNavArea * area = TheNavAreaList [ it ] ;
if ( ! area - > HasNodes ( ) )
continue ;
// north edge
FOR_EACH_LL ( area - > m_connect [ NORTH ] , nit )
{
CNavArea * adjArea = area - > m_connect [ NORTH ] [ nit ] . area ;
if ( ! adjArea - > HasNodes ( ) ) // pre-existing areas in incremental generates won't have nodes
continue ;
if ( area - > m_node [ NORTH_WEST ] = = adjArea - > m_node [ SOUTH_WEST ] & &
area - > m_node [ NORTH_EAST ] = = adjArea - > m_node [ SOUTH_EAST ] & &
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge vertical
area - > m_node [ NORTH_WEST ] = adjArea - > m_node [ NORTH_WEST ] ;
area - > m_node [ NORTH_EAST ] = adjArea - > m_node [ NORTH_EAST ] ;
merged = true ;
//CONSOLE_ECHO( " Merged (north) areas #%d and #%d\n", area->m_id, adjArea->m_id );
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// south edge
FOR_EACH_LL ( area - > m_connect [ SOUTH ] , sit )
{
CNavArea * adjArea = area - > m_connect [ SOUTH ] [ sit ] . area ;
if ( ! adjArea - > HasNodes ( ) ) // pre-existing areas in incremental generates won't have nodes
continue ;
if ( adjArea - > m_node [ NORTH_WEST ] = = area - > m_node [ SOUTH_WEST ] & &
adjArea - > m_node [ NORTH_EAST ] = = area - > m_node [ SOUTH_EAST ] & &
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge vertical
area - > m_node [ SOUTH_WEST ] = adjArea - > m_node [ SOUTH_WEST ] ;
area - > m_node [ SOUTH_EAST ] = adjArea - > m_node [ SOUTH_EAST ] ;
merged = true ;
//CONSOLE_ECHO( " Merged (south) areas #%d and #%d\n", area->m_id, adjArea->m_id );
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// west edge
FOR_EACH_LL ( area - > m_connect [ WEST ] , wit )
{
CNavArea * adjArea = area - > m_connect [ WEST ] [ wit ] . area ;
if ( ! adjArea - > HasNodes ( ) ) // pre-existing areas in incremental generates won't have nodes
continue ;
if ( area - > m_node [ NORTH_WEST ] = = adjArea - > m_node [ NORTH_EAST ] & &
area - > m_node [ SOUTH_WEST ] = = adjArea - > m_node [ SOUTH_EAST ] & &
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge horizontal
area - > m_node [ NORTH_WEST ] = adjArea - > m_node [ NORTH_WEST ] ;
area - > m_node [ SOUTH_WEST ] = adjArea - > m_node [ SOUTH_WEST ] ;
merged = true ;
//CONSOLE_ECHO( " Merged (west) areas #%d and #%d\n", area->m_id, adjArea->m_id );
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
// east edge
FOR_EACH_LL ( area - > m_connect [ EAST ] , eit )
{
CNavArea * adjArea = area - > m_connect [ EAST ] [ eit ] . area ;
if ( ! adjArea - > HasNodes ( ) ) // pre-existing areas in incremental generates won't have nodes
continue ;
if ( adjArea - > m_node [ NORTH_WEST ] = = area - > m_node [ NORTH_EAST ] & &
adjArea - > m_node [ SOUTH_WEST ] = = area - > m_node [ SOUTH_EAST ] & &
area - > GetAttributes ( ) = = adjArea - > GetAttributes ( ) & &
area - > IsCoplanar ( adjArea ) )
{
// merge horizontal
area - > m_node [ NORTH_EAST ] = adjArea - > m_node [ NORTH_EAST ] ;
area - > m_node [ SOUTH_EAST ] = adjArea - > m_node [ SOUTH_EAST ] ;
merged = true ;
//CONSOLE_ECHO( " Merged (east) areas #%d and #%d\n", area->m_id, adjArea->m_id );
area - > FinishMerge ( adjArea ) ;
// restart scan - iterator is invalidated
break ;
}
}
if ( merged )
break ;
}
}
while ( merged ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check if an rectangular area of the given size can be
* made starting from the given node as the NW corner .
* Only consider fully connected nodes for this check .
* All of the nodes within the test area must have the same attributes .
* All of the nodes must be approximately co - planar w . r . t the NW node ' s normal , with the
* exception of 1 x1 areas which can be any angle .
*/
bool CNavMesh : : TestArea ( CNavNode * node , int width , int height )
{
Vector normal = * node - > GetNormal ( ) ;
float d = - DotProduct ( normal , * node - > GetPosition ( ) ) ;
bool nodeCrouch = node - > m_crouch [ SOUTH_EAST ] ;
unsigned char nodeAttributes = node - > GetAttributes ( ) & ~ NAV_MESH_CROUCH ;
const float offPlaneTolerance = 5.0f ;
CNavNode * vertNode , * horizNode ;
vertNode = node ;
for ( int y = 0 ; y < height ; y + + )
{
horizNode = vertNode ;
for ( int x = 0 ; x < width ; x + + )
{
//
// Compute the crouch attributes for the test node, taking into account only the side(s) of the node
// that are in the area
bool horizNodeCrouch = false ;
bool westEdge = ( x = = 0 ) ;
bool eastEdge = ( x = = width - 1 ) ;
bool northEdge = ( y = = 0 ) ;
bool southEdge = ( y = = height - 1 ) ;
// Check corners first
if ( northEdge & & westEdge )
{
horizNodeCrouch = horizNode - > m_crouch [ SOUTH_EAST ] ;
}
else if ( northEdge & & eastEdge )
{
horizNodeCrouch = horizNode - > m_crouch [ SOUTH_EAST ] | | horizNode - > m_crouch [ SOUTH_WEST ] ;
}
else if ( southEdge & & westEdge )
{
horizNodeCrouch = horizNode - > m_crouch [ SOUTH_EAST ] | | horizNode - > m_crouch [ NORTH_EAST ] ;
}
else if ( southEdge & & eastEdge )
{
horizNodeCrouch = ( horizNode - > GetAttributes ( ) & NAV_MESH_CROUCH ) ! = 0 ;
}
// check sides next
else if ( northEdge )
{
horizNodeCrouch = horizNode - > m_crouch [ SOUTH_EAST ] | | horizNode - > m_crouch [ SOUTH_WEST ] ;
}
else if ( southEdge )
{
horizNodeCrouch = ( horizNode - > GetAttributes ( ) & NAV_MESH_CROUCH ) ! = 0 ;
}
else if ( eastEdge )
{
horizNodeCrouch = ( horizNode - > GetAttributes ( ) & NAV_MESH_CROUCH ) ! = 0 ;
}
else if ( westEdge )
{
horizNodeCrouch = horizNode - > m_crouch [ SOUTH_EAST ] | | horizNode - > m_crouch [ NORTH_EAST ] ;
}
// finally, we have a center node
else
{
horizNodeCrouch = ( horizNode - > GetAttributes ( ) & NAV_MESH_CROUCH ) ! = 0 ;
}
// all nodes must be crouch/non-crouch
if ( nodeCrouch ! = horizNodeCrouch )
return false ;
// all nodes must have the same non-crouch attributes
unsigned char horizNodeAttributes = horizNode - > GetAttributes ( ) & ~ NAV_MESH_CROUCH ;
if ( horizNodeAttributes ! = nodeAttributes )
return false ;
if ( horizNode - > IsCovered ( ) )
return false ;
if ( ! horizNode - > IsClosedCell ( ) )
return false ;
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
if ( horizNode = = NULL )
return false ;
// nodes must lie on/near the plane
if ( width > 1 | | height > 1 )
{
float dist = ( float ) fabs ( DotProduct ( * horizNode - > GetPosition ( ) , normal ) + d ) ;
if ( dist > offPlaneTolerance )
return false ;
}
}
vertNode = vertNode - > GetConnectedNode ( SOUTH ) ;
if ( vertNode = = NULL )
return false ;
// nodes must lie on/near the plane
if ( width > 1 | | height > 1 )
{
float dist = ( float ) fabs ( DotProduct ( * vertNode - > GetPosition ( ) , normal ) + d ) ;
if ( dist > offPlaneTolerance )
return false ;
}
}
// check planarity of southern edge
if ( width > 1 | | height > 1 )
{
horizNode = vertNode ;
for ( int x = 0 ; x < width ; x + + )
{
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
if ( horizNode = = NULL )
return false ;
// nodes must lie on/near the plane
float dist = ( float ) fabs ( DotProduct ( * horizNode - > GetPosition ( ) , normal ) + d ) ;
if ( dist > offPlaneTolerance )
return false ;
}
}
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a nav area , and mark all nodes it overlaps as " covered "
* NOTE : Nodes on the east and south edges are not included .
* Returns number of nodes covered by this area , or - 1 for error ;
*/
int CNavMesh : : BuildArea ( CNavNode * node , int width , int height )
{
CNavNode * nwNode = node ;
CNavNode * neNode = NULL ;
CNavNode * swNode = NULL ;
CNavNode * seNode = NULL ;
CNavNode * vertNode = node ;
CNavNode * horizNode ;
int coveredNodes = 0 ;
for ( int y = 0 ; y < height ; y + + )
{
horizNode = vertNode ;
for ( int x = 0 ; x < width ; x + + )
{
horizNode - > Cover ( ) ;
+ + coveredNodes ;
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
}
if ( y = = 0 )
neNode = horizNode ;
vertNode = vertNode - > GetConnectedNode ( SOUTH ) ;
}
swNode = vertNode ;
horizNode = vertNode ;
for ( int x = 0 ; x < width ; x + + )
{
horizNode = horizNode - > GetConnectedNode ( EAST ) ;
}
seNode = horizNode ;
if ( ! nwNode | | ! neNode | | ! seNode | | ! swNode )
{
Error ( " BuildArea - NULL node. \n " ) ;
return - 1 ;
}
CNavArea * area = new CNavArea ( nwNode , neNode , seNode , swNode ) ;
TheNavAreaList . AddToTail ( area ) ;
// since all internal nodes have the same attributes, set this area's attributes
area - > SetAttributes ( node - > GetAttributes ( ) ) ;
// Check that the node was crouch in the right direction
bool nodeCrouch = node - > m_crouch [ SOUTH_EAST ] ;
if ( ( area - > GetAttributes ( ) & NAV_MESH_CROUCH ) & & ! nodeCrouch )
{
area - > SetAttributes ( area - > GetAttributes ( ) & ~ NAV_MESH_CROUCH ) ;
}
return coveredNodes ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* This function uses the CNavNodes that have been sampled from the map to
* generate CNavAreas - rectangular areas of " walkable " space . These areas
* are connected to each other , proving information on know how to move from
* area to area .
*
* This is a " greedy " algorithm that attempts to cover the walkable area
* with the fewest , largest , rectangles .
*/
void CNavMesh : : CreateNavAreasFromNodes ( void )
{
// haven't yet seen a map use larger than 30...
int tryWidth = 50 ;
int tryHeight = 50 ;
int uncoveredNodes = CNavNode : : GetListLength ( ) ;
while ( uncoveredNodes > 0 )
{
for ( CNavNode * node = CNavNode : : GetFirst ( ) ; node ; node = node - > GetNext ( ) )
{
if ( node - > IsCovered ( ) )
continue ;
if ( TestArea ( node , tryWidth , tryHeight ) )
{
int covered = BuildArea ( node , tryWidth , tryHeight ) ;
if ( covered < 0 )
{
Error ( " Generate: Error - Data corrupt. \n " ) ;
return ;
}
uncoveredNodes - = covered ;
}
}
if ( tryWidth > = tryHeight )
- - tryWidth ;
else
- - tryHeight ;
if ( tryWidth < = 0 | | tryHeight < = 0 )
break ;
}
if ( ! TheNavAreaList . Count ( ) )
{
// If we somehow have no areas, don't try to create an impossibly-large grid
AllocateGrid ( 0 , 0 , 0 , 0 ) ;
return ;
}
Extent extent ;
extent . lo . x = 9999999999.9f ;
extent . lo . y = 9999999999.9f ;
extent . hi . x = - 9999999999.9f ;
extent . hi . y = - 9999999999.9f ;
// compute total extent
FOR_EACH_LL ( TheNavAreaList , it )
{
CNavArea * area = TheNavAreaList [ it ] ;
const Extent & areaExtent = area - > GetExtent ( ) ;
if ( areaExtent . lo . x < extent . lo . x )
extent . lo . x = areaExtent . lo . x ;
if ( areaExtent . lo . y < extent . lo . y )
extent . lo . y = areaExtent . lo . y ;
if ( areaExtent . hi . x > extent . hi . x )
extent . hi . x = areaExtent . hi . x ;
if ( areaExtent . hi . y > extent . hi . y )
extent . hi . y = areaExtent . hi . y ;
}
// add the areas to the grid
AllocateGrid ( extent . lo . x , extent . hi . x , extent . lo . y , extent . hi . y ) ;
FOR_EACH_LL ( TheNavAreaList , git )
{
AddNavArea ( TheNavAreaList [ git ] ) ;
}
ConnectGeneratedAreas ( ) ;
MergeGeneratedAreas ( ) ;
SquareUpAreas ( ) ;
MarkJumpAreas ( ) ;
/// @TODO: incremental generation doesn't create ladders yet
if ( m_generationMode ! = GENERATE_INCREMENTAL )
{
FOR_EACH_LL ( m_ladderList , lit )
{
m_ladderList [ lit ] - > ConnectGeneratedLadder ( ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Initiate the generation process
*/
void CNavMesh : : BeginGeneration ( bool incremental )
{
IGameEvent * event = gameeventmanager - > CreateEvent ( " nav_generate " ) ;
if ( event )
{
gameeventmanager - > FireEvent ( event ) ;
}
engine - > ServerCommand ( " bot_kick \n " ) ;
// Right now, incrementally-generated areas won't connect to existing areas automatically.
// Since this means hand-editing will be necessary, don't do a full analyze.
if ( incremental )
{
nav_quicksave . SetValue ( 1 ) ;
}
m_generationState = SAMPLE_WALKABLE_SPACE ;
m_sampleTick = 0 ;
m_generationMode = ( incremental ) ? GENERATE_INCREMENTAL : GENERATE_FULL ;
lastMsgTime = 0.0f ;
// clear any previous mesh
DestroyNavigationMesh ( incremental ) ;
SetNavPlace ( UNDEFINED_PLACE ) ;
// build internal representations of ladders, which are used to find new walkable areas
if ( ! incremental ) ///< @incremental update doesn't build ladders to avoid overlapping existing ones
{
BuildLadders ( ) ;
}
// start sampling from a spawn point
CBaseEntity * spawn = gEntList . FindEntityByClassname ( NULL , GetPlayerSpawnName ( ) ) ;
if ( spawn & & ! incremental )
{
// snap it to the sampling grid
Vector pos = spawn - > GetAbsOrigin ( ) ;
pos . x = TheNavMesh - > SnapToGrid ( pos . x ) ;
pos . y = TheNavMesh - > SnapToGrid ( pos . y ) ;
Vector normal ;
if ( GetGroundHeight ( pos , & pos . z , & normal ) )
{
m_currentNode = new CNavNode ( pos , normal , NULL ) ;
}
}
else
{
// the system will see this NULL and select the next walkable seed
m_currentNode = NULL ;
}
// if there are no seed points, we can't generate
if ( m_walkableSeedList . Count ( ) = = 0 & & m_currentNode = = NULL )
{
m_generationMode = GENERATE_NONE ;
Msg ( " No valid walkable seed positions. Cannot generate Navigation Mesh. \n " ) ;
return ;
}
// initialize seed list index
m_seedIdx = m_walkableSeedList . Head ( ) ;
Msg ( " Generating Navigation Mesh... \n " ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Re - analyze an existing Mesh . Determine Hiding Spots , Encounter Spots , etc .
*/
void CNavMesh : : BeginAnalysis ( void )
{
// Remove and re-add elements in TheNavAreaList, to ensure indices are useful for progress feedback
NavAreaList tmpList ;
{
FOR_EACH_LL ( TheNavAreaList , it )
{
tmpList . AddToTail ( TheNavAreaList [ it ] ) ;
}
}
TheNavAreaList . RemoveAll ( ) ;
{
FOR_EACH_LL ( tmpList , it )
{
TheNavAreaList . AddToTail ( tmpList [ it ] ) ;
}
}
DestroyHidingSpots ( ) ;
m_generationState = FIND_HIDING_SPOTS ;
m_generationIndex = TheNavAreaList . Head ( ) ;
m_generationMode = GENERATE_ANALYSIS_ONLY ;
lastMsgTime = 0.0f ;
}
//--------------------------------------------------------------------------------------------------------------
void ShowViewPortPanelToAll ( const char * name , bool bShow , KeyValues * data )
{
CRecipientFilter filter ;
filter . AddAllPlayers ( ) ;
filter . MakeReliable ( ) ;
int count = 0 ;
KeyValues * subkey = NULL ;
if ( data )
{
subkey = data - > GetFirstSubKey ( ) ;
while ( subkey )
{
count + + ; subkey = subkey - > GetNextKey ( ) ;
}
subkey = data - > GetFirstSubKey ( ) ; // reset
}
UserMessageBegin ( filter , " VGUIMenu " ) ;
WRITE_STRING ( name ) ; // menu name
WRITE_BYTE ( bShow ? 1 : 0 ) ;
WRITE_BYTE ( count ) ;
// write additional data (be carefull not more than 192 bytes!)
while ( subkey )
{
WRITE_STRING ( subkey - > GetName ( ) ) ;
WRITE_STRING ( subkey - > GetString ( ) ) ;
subkey = subkey - > GetNextKey ( ) ;
}
MessageEnd ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
static void AnalysisProgress ( const char * msg , int ticks , int current , bool showPercent = true )
{
const float MsgInterval = 10.0f ;
float now = Plat_FloatTime ( ) ;
if ( now > lastMsgTime + MsgInterval )
{
if ( showPercent & & ticks )
{
Msg ( " %s %.0f%% \n " , msg , current * 100.0f / ticks ) ;
}
else
{
Msg ( " %s \n " , msg ) ;
}
lastMsgTime = now ;
}
KeyValues * data = new KeyValues ( " data " ) ;
data - > SetString ( " msg " , msg ) ;
data - > SetInt ( " total " , ticks ) ;
data - > SetInt ( " current " , current ) ;
ShowViewPortPanelToAll ( PANEL_NAV_PROGRESS , true , data ) ;
data - > deleteThis ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
static void HideAnalysisProgress ( void )
{
KeyValues * data = new KeyValues ( " data " ) ;
ShowViewPortPanelToAll ( PANEL_NAV_PROGRESS , false , data ) ;
data - > deleteThis ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Process the auto - generation for ' maxTime ' seconds . return false if generation is complete .
*/
bool CNavMesh : : UpdateGeneration ( float maxTime )
{
double startTime = Plat_FloatTime ( ) ;
switch ( m_generationState )
{
//---------------------------------------------------------------------------
case SAMPLE_WALKABLE_SPACE :
{
AnalysisProgress ( " Sampling walkable space... " , 100 , m_sampleTick / 10 , false ) ;
m_sampleTick = ( m_sampleTick + 1 ) % 1000 ;
while ( SampleStep ( ) )
{
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
return true ;
}
}
// sampling is complete, now build nav areas
m_generationState = CREATE_AREAS_FROM_SAMPLES ;
return true ;
}
//---------------------------------------------------------------------------
case CREATE_AREAS_FROM_SAMPLES :
{
Msg ( " Creating navigation areas from sampled data... \n " ) ;
CreateNavAreasFromNodes ( ) ;
DestroyHidingSpots ( ) ;
// Remove and re-add elements in TheNavAreaList, to ensure indices are useful for progress feedback
NavAreaList tmpList ;
{
FOR_EACH_LL ( TheNavAreaList , it )
{
tmpList . AddToTail ( TheNavAreaList [ it ] ) ;
}
}
TheNavAreaList . RemoveAll ( ) ;
{
FOR_EACH_LL ( tmpList , it )
{
TheNavAreaList . AddToTail ( tmpList [ it ] ) ;
}
}
m_generationState = FIND_HIDING_SPOTS ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case FIND_HIDING_SPOTS :
{
while ( m_generationIndex ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ m_generationIndex ] ;
m_generationIndex = TheNavAreaList . Next ( m_generationIndex ) ;
area - > ComputeHidingSpots ( ) ;
// don't go over our time allotment
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
AnalysisProgress ( " Finding hiding spots... " , 100 , 100 * m_generationIndex / TheNavAreaList . Count ( ) ) ;
return true ;
}
}
Msg ( " Finding hiding spots...DONE \n " ) ;
m_generationState = FIND_APPROACH_AREAS ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case FIND_APPROACH_AREAS :
{
while ( m_generationIndex ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ m_generationIndex ] ;
m_generationIndex = TheNavAreaList . Next ( m_generationIndex ) ;
area - > ComputeApproachAreas ( ) ;
// don't go over our time allotment
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
AnalysisProgress ( " Finding approach areas... " , 100 , 100 * m_generationIndex / TheNavAreaList . Count ( ) ) ;
return true ;
}
}
Msg ( " Finding approach areas...DONE \n " ) ;
m_generationState = FIND_ENCOUNTER_SPOTS ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case FIND_ENCOUNTER_SPOTS :
{
while ( m_generationIndex ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ m_generationIndex ] ;
m_generationIndex = TheNavAreaList . Next ( m_generationIndex ) ;
area - > ComputeSpotEncounters ( ) ;
// don't go over our time allotment
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
AnalysisProgress ( " Finding encounter spots... " , 100 , 100 * m_generationIndex / TheNavAreaList . Count ( ) ) ;
return true ;
}
}
Msg ( " Finding encounter spots...DONE \n " ) ;
m_generationState = FIND_SNIPER_SPOTS ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case FIND_SNIPER_SPOTS :
{
while ( m_generationIndex ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ m_generationIndex ] ;
m_generationIndex = TheNavAreaList . Next ( m_generationIndex ) ;
area - > ComputeSniperSpots ( ) ;
// don't go over our time allotment
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
AnalysisProgress ( " Finding sniper spots... " , 100 , 100 * m_generationIndex / TheNavAreaList . Count ( ) ) ;
return true ;
}
}
Msg ( " Finding sniper spots...DONE \n " ) ;
m_generationState = FIND_EARLIEST_OCCUPY_TIMES ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case FIND_EARLIEST_OCCUPY_TIMES :
{
while ( m_generationIndex ! = TheNavAreaList . InvalidIndex ( ) )
{
CNavArea * area = TheNavAreaList [ m_generationIndex ] ;
m_generationIndex = TheNavAreaList . Next ( m_generationIndex ) ;
area - > ComputeEarliestOccupyTimes ( ) ;
// don't go over our time allotment
if ( Plat_FloatTime ( ) - startTime > maxTime )
{
AnalysisProgress ( " Finding earliest occupy times... " , 100 , 100 * m_generationIndex / TheNavAreaList . Count ( ) ) ;
return true ;
}
}
Msg ( " Finding earliest occupy times...DONE \n " ) ;
m_generationState = SAVE_NAV_MESH ;
m_generationIndex = TheNavAreaList . Head ( ) ;
return true ;
}
//---------------------------------------------------------------------------
case SAVE_NAV_MESH :
{
// generation complete!
Msg ( " Generation complete! \n " ) ;
m_generationMode = GENERATE_NONE ;
m_isLoaded = true ;
HideAnalysisProgress ( ) ;
if ( nav_restart_after_analysis . GetBool ( ) )
{
// save the mesh
if ( Save ( ) )
{
Msg ( " Navigation map '%s' saved. \n " , GetFilename ( ) ) ;
}
else
{
const char * filename = GetFilename ( ) ;
Msg ( " ERROR: Cannot save navigation map '%s'. \n " , ( filename ) ? filename : " (null) " ) ;
}
engine - > ChangeLevel ( STRING ( gpGlobals - > mapname ) , NULL ) ;
}
return false ;
}
2008-09-15 01:33:59 -05:00
default :
break ;
2008-09-15 01:00:17 -05:00
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Define the name of player spawn entities
*/
void CNavMesh : : SetPlayerSpawnName ( const char * name )
{
if ( m_spawnName )
{
delete [ ] m_spawnName ;
}
m_spawnName = new char [ strlen ( name ) + 1 ] ;
strcpy ( m_spawnName , name ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return name of player spawn entity
*/
const char * CNavMesh : : GetPlayerSpawnName ( void ) const
{
if ( m_spawnName )
return m_spawnName ;
// default value
return " info_player_start " ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add a nav node and connect it .
* Node Z positions are ground level .
*/
CNavNode * CNavMesh : : AddNode ( const Vector & destPos , const Vector & normal , NavDirType dir , CNavNode * source )
{
// check if a node exists at this location
CNavNode * node = CNavNode : : GetNode ( destPos ) ;
// if no node exists, create one
bool useNew = false ;
if ( node = = NULL )
{
node = new CNavNode ( destPos , normal , source ) ;
useNew = true ;
}
// connect source node to new node
source - > ConnectTo ( node , dir ) ;
// optimization: if deltaZ changes very little, assume connection is commutative
const float zTolerance = 50.0f ;
if ( fabs ( source - > GetPosition ( ) - > z - destPos . z ) < zTolerance )
{
node - > ConnectTo ( source , OppositeDirection ( dir ) ) ;
node - > MarkAsVisited ( OppositeDirection ( dir ) ) ;
}
if ( useNew )
{
// new node becomes current node
m_currentNode = node ;
}
node - > CheckCrouch ( ) ;
return node ;
}
//--------------------------------------------------------------------------------------------------------------
inline CNavNode * LadderEndSearch ( const Vector * pos , NavDirType mountDir )
{
Vector center = * pos ;
AddDirectionVector ( & center , mountDir , HalfHumanWidth ) ;
//
// Test the ladder dismount point first, then each cardinal direction one and two steps away
//
for ( int d = ( - 1 ) ; d < 2 * NUM_DIRECTIONS ; + + d )
{
Vector tryPos = center ;
if ( d > = NUM_DIRECTIONS )
AddDirectionVector ( & tryPos , ( NavDirType ) ( d - NUM_DIRECTIONS ) , 2.0f * GenerationStepSize ) ;
else if ( d > = 0 )
AddDirectionVector ( & tryPos , ( NavDirType ) d , GenerationStepSize ) ;
// step up a rung, to ensure adjacent floors are below us
tryPos . z + = GenerationStepSize ;
tryPos . x = TheNavMesh - > SnapToGrid ( tryPos . x ) ;
tryPos . y = TheNavMesh - > SnapToGrid ( tryPos . y ) ;
// adjust height to account for sloping areas
Vector tryNormal ;
if ( TheNavMesh - > GetGroundHeight ( tryPos , & tryPos . z , & tryNormal ) = = false )
continue ;
// make sure this point is not on the other side of a wall
const float fudge = 2.0f ;
trace_t result ;
UTIL_TraceLine ( center + Vector ( 0 , 0 , fudge ) , tryPos + Vector ( 0 , 0 , fudge ) , MASK_PLAYERSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f | | result . startsolid )
continue ;
// if no node exists here, create one and continue the search
if ( CNavNode : : GetNode ( tryPos ) = = NULL )
{
return new CNavNode ( tryPos , tryNormal , NULL ) ;
}
}
return NULL ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Search the world and build a map of possible movements .
* The algorithm begins at the bot ' s current location , and does a recursive search
* outwards , tracking all valid steps and generating a directed graph of CNavNodes .
*
* Sample the map one " step " in a cardinal direction to learn the map .
*
* Returns true if sampling needs to continue , or false if done .
*/
bool CNavMesh : : SampleStep ( void )
{
// take a step
while ( true )
{
if ( m_currentNode = = NULL )
{
// sampling is complete from current seed, try next one
m_currentNode = GetNextWalkableSeedNode ( ) ;
if ( m_currentNode = = NULL )
{
// search is exhausted - continue search from ends of ladders
FOR_EACH_LL ( m_ladderList , it )
{
CNavLadder * ladder = m_ladderList [ it ] ;
// check ladder bottom
if ( ( m_currentNode = LadderEndSearch ( & ladder - > m_bottom , ladder - > GetDir ( ) ) ) ! = 0 )
break ;
// check ladder top
if ( ( m_currentNode = LadderEndSearch ( & ladder - > m_top , ladder - > GetDir ( ) ) ) ! = 0 )
break ;
}
if ( m_currentNode = = NULL )
{
// all seeds exhausted, sampling complete
return false ;
}
}
}
//
// Take a step from this node
//
for ( int dir = NORTH ; dir < NUM_DIRECTIONS ; dir + + )
{
if ( ! m_currentNode - > HasVisited ( ( NavDirType ) dir ) )
{
// have not searched in this direction yet
// start at current node position
Vector pos = * m_currentNode - > GetPosition ( ) ;
// snap to grid
2008-09-15 01:33:59 -05:00
int cx = static_cast < int > ( SnapToGrid ( pos . x ) ) ;
int cy = static_cast < int > ( SnapToGrid ( pos . y ) ) ;
2008-09-15 01:00:17 -05:00
// attempt to move to adjacent node
switch ( dir )
{
2008-09-15 01:33:59 -05:00
case NORTH : cy - = static_cast < int > ( GenerationStepSize ) ; break ;
case SOUTH : cy + = static_cast < int > ( GenerationStepSize ) ; break ;
case EAST : cx + = static_cast < int > ( GenerationStepSize ) ; break ;
case WEST : cx - = static_cast < int > ( GenerationStepSize ) ; break ;
2008-09-15 01:00:17 -05:00
}
pos . x = cx ;
pos . y = cy ;
m_generationDir = ( NavDirType ) dir ;
// mark direction as visited
m_currentNode - > MarkAsVisited ( m_generationDir ) ;
// test if we can move to new position
trace_t result ;
Vector from , to ;
// modify position to account for change in ground level during step
to . x = pos . x ;
to . y = pos . y ;
Vector toNormal ;
if ( GetGroundHeight ( pos , & to . z , & toNormal ) = = false )
{
return true ;
}
from = * m_currentNode - > GetPosition ( ) ;
Vector fromOrigin = from + Vector ( 0 , 0 , HalfHumanHeight ) ;
Vector toOrigin = to + Vector ( 0 , 0 , HalfHumanHeight ) ;
CTraceFilterWalkableEntities filter ( NULL , COLLISION_GROUP_NONE , WALK_THRU_EVERYTHING ) ;
UTIL_TraceLine ( fromOrigin , toOrigin , MASK_PLAYERSOLID_BRUSHONLY , & filter , & result ) ;
bool walkable ;
if ( result . fraction = = 1.0f & & ! result . startsolid )
{
// the trace didnt hit anything - clear
float toGround = to . z ;
float fromGround = from . z ;
float epsilon = 0.1f ;
// check if ledge is too high to reach or will cause us to fall to our death
if ( toGround - fromGround > JumpCrouchHeight + epsilon | | fromGround - toGround > DeathDrop )
{
walkable = false ;
}
else
{
// check surface normals along this step to see if we would cross any impassable slopes
Vector delta = to - from ;
const float inc = 2.0f ;
float along = inc ;
bool done = false ;
float ground ;
Vector normal ;
walkable = true ;
while ( ! done )
{
Vector p ;
// need to guarantee that we test the exact edges
if ( along > = GenerationStepSize )
{
p = to ;
done = true ;
}
else
{
p = from + delta * ( along / GenerationStepSize ) ;
}
if ( GetGroundHeight ( p , & ground , & normal ) = = false )
{
walkable = false ;
break ;
}
// check for maximum allowed slope
if ( normal . z < nav_slope_limit . GetFloat ( ) )
{
walkable = false ;
break ;
}
along + = inc ;
}
}
}
else // TraceLine hit something...
{
if ( IsEntityWalkable ( result . m_pEnt , WALK_THRU_EVERYTHING ) )
{
walkable = true ;
}
else
{
walkable = false ;
}
}
// if we're incrementally generating, don't overlap existing nav areas
CNavArea * overlap = GetNavArea ( to , HumanHeight ) ;
if ( overlap )
{
walkable = false ;
}
if ( walkable )
{
// we can move here
// create a new navigation node, and update current node pointer
AddNode ( to , toNormal , m_generationDir , m_currentNode ) ;
}
return true ;
}
}
// all directions have been searched from this node - pop back to its parent and continue
m_currentNode = m_currentNode - > GetParent ( ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add given walkable position to list of seed positions for map sampling
*/
void CNavMesh : : AddWalkableSeed ( const Vector & pos , const Vector & normal )
{
WalkableSeedSpot seed ;
seed . pos = pos ;
seed . normal = normal ;
m_walkableSeedList . AddToTail ( seed ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the next walkable seed as a node
*/
CNavNode * CNavMesh : : GetNextWalkableSeedNode ( void )
{
if ( ! m_walkableSeedList . IsValidIndex ( m_seedIdx ) )
return NULL ;
WalkableSeedSpot spot = m_walkableSeedList . Element ( m_seedIdx ) ;
m_seedIdx = m_walkableSeedList . Next ( m_seedIdx ) ;
return new CNavNode ( spot . pos , spot . normal , NULL ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check LOS , ignoring any entities that we can walk through
*/
bool IsWalkableTraceLineClear ( Vector & from , Vector & to , unsigned int flags )
{
trace_t result ;
CBaseEntity * ignore = NULL ;
Vector useFrom = from ;
CTraceFilterWalkableEntities traceFilter ( NULL , COLLISION_GROUP_NONE , flags ) ;
result . fraction = 0.0f ;
const int maxTries = 50 ;
for ( int t = 0 ; t < maxTries ; + + t )
{
UTIL_TraceLine ( useFrom , to , MASK_PLAYERSOLID , & traceFilter , & result ) ;
// if we hit a walkable entity, try again
if ( result . fraction ! = 1.0f & & IsEntityWalkable ( result . m_pEnt , flags ) )
{
ignore = result . m_pEnt ;
// start from just beyond where we hit to avoid infinite loops
Vector dir = to - from ;
dir . NormalizeInPlace ( ) ;
useFrom = result . endpos + 5.0f * dir ;
}
else
{
break ;
}
}
if ( result . fraction = = 1.0f )
return true ;
return false ;
}