//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.cpp
// AI Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
# include "cbase.h"
# include "tier0/vprof.h"
# include "tier0/tslist.h"
# include "tier1/utlhash.h"
# include "vstdlib/jobthread.h"
# include "nav_mesh.h"
# include "nav_node.h"
# include "nav_pathfind.h"
# include "nav_colors.h"
# include "fmtstr.h"
# include "props_shared.h"
# include "func_breakablesurf.h"
# ifdef TERROR
# include "func_elevator.h"
# include "AmbientLight.h"
# endif
# include "Color.h"
# include "collisionutils.h"
# include "functorutils.h"
# include "team.h"
# include "nav_entities.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
extern void HintMessageToAllPlayers ( const char * message ) ;
unsigned int CNavArea : : m_nextID = 1 ;
NavAreaVector TheNavAreas ;
unsigned int CNavArea : : m_masterMarker = 1 ;
CNavArea * CNavArea : : m_openList = NULL ;
CNavArea * CNavArea : : m_openListTail = NULL ;
bool CNavArea : : m_isReset = false ;
uint32 CNavArea : : s_nCurrVisTestCounter = 0 ;
ConVar nav_coplanar_slope_limit ( " nav_coplanar_slope_limit " , " 0.99 " , FCVAR_CHEAT ) ;
ConVar nav_coplanar_slope_limit_displacement ( " nav_coplanar_slope_limit_displacement " , " 0.7 " , FCVAR_CHEAT ) ;
ConVar nav_split_place_on_ground ( " nav_split_place_on_ground " , " 0 " , FCVAR_CHEAT , " If true, nav areas will be placed flush with the ground when split. " ) ;
ConVar nav_area_bgcolor ( " nav_area_bgcolor " , " 0 0 0 30 " , FCVAR_CHEAT , " RGBA color to draw as the background color for nav areas while editing. " ) ;
ConVar nav_corner_adjust_adjacent ( " nav_corner_adjust_adjacent " , " 18 " , FCVAR_CHEAT , " radius used to raise/lower corners in nearby areas when raising/lowering corners. " ) ;
ConVar nav_show_light_intensity ( " nav_show_light_intensity " , " 0 " , FCVAR_CHEAT ) ;
ConVar nav_debug_blocked ( " nav_debug_blocked " , " 0 " , FCVAR_CHEAT ) ;
ConVar nav_show_contiguous ( " nav_show_continguous " , " 0 " , FCVAR_CHEAT , " Highlight non-contiguous connections " ) ;
const float DEF_NAV_VIEW_DISTANCE = 1500.0 ;
ConVar nav_max_view_distance ( " nav_max_view_distance " , " 6000 " , FCVAR_CHEAT , " Maximum range for precomputed nav mesh visibility (0 = default 1500 units) " ) ;
ConVar nav_update_visibility_on_edit ( " nav_update_visibility_on_edit " , " 0 " , FCVAR_CHEAT , " If nonzero editing the mesh will incrementally recompue visibility " ) ;
ConVar nav_potentially_visible_dot_tolerance ( " nav_potentially_visible_dot_tolerance " , " 0.98 " , FCVAR_CHEAT ) ;
ConVar nav_show_potentially_visible ( " nav_show_potentially_visible " , " 0 " , FCVAR_CHEAT , " Show areas that are potentially visible from the current nav area " ) ;
Color s_selectedSetColor ( 255 , 255 , 200 , 96 ) ;
Color s_selectedSetBorderColor ( 100 , 100 , 0 , 255 ) ;
Color s_dragSelectionSetBorderColor ( 50 , 50 , 50 , 255 ) ;
static void SelectedSetColorChaged ( IConVar * var , const char * pOldValue , float flOldValue )
{
ConVarRef colorVar ( var - > GetName ( ) ) ;
Color * color = & s_selectedSetColor ;
if ( FStrEq ( var - > GetName ( ) , " nav_selected_set_border_color " ) )
{
color = & s_selectedSetBorderColor ;
}
// Xbox compiler needs these to be in this explicit form
// likely due to sscanf expecting word aligned boundaries
int r = color - > r ( ) ;
int g = color - > r ( ) ;
int b = color - > b ( ) ;
int a = color - > a ( ) ;
int numFound = sscanf ( colorVar . GetString ( ) , " %d %d %d %d " , & r , & g , & b , & a ) ;
( * color ) [ 0 ] = r ;
( * color ) [ 1 ] = g ;
( * color ) [ 2 ] = b ;
if ( numFound > 3 )
{
( * color ) [ 3 ] = a ;
}
}
ConVar nav_selected_set_color ( " nav_selected_set_color " , " 255 255 200 96 " , FCVAR_CHEAT , " Color used to draw the selected set background while editing. " , false , 0.0f , false , 0.0f , SelectedSetColorChaged ) ;
ConVar nav_selected_set_border_color ( " nav_selected_set_border_color " , " 100 100 0 255 " , FCVAR_CHEAT , " Color used to draw the selected set borders while editing. " , false , 0.0f , false , 0.0f , SelectedSetColorChaged ) ;
//--------------------------------------------------------------------------------------------------------------
CMemoryStack CNavVectorNoEditAllocator : : m_memory ;
void * CNavVectorNoEditAllocator : : m_pCurrent ;
int CNavVectorNoEditAllocator : : m_nBytesCurrent ;
CNavVectorNoEditAllocator : : CNavVectorNoEditAllocator ( )
{
m_pCurrent = NULL ;
m_nBytesCurrent = 0 ;
}
void CNavVectorNoEditAllocator : : Reset ( )
{
m_memory . FreeAll ( ) ;
m_pCurrent = NULL ;
m_nBytesCurrent = 0 ;
}
void * CNavVectorNoEditAllocator : : Alloc ( size_t nSize )
{
if ( ! m_memory . GetBase ( ) )
{
m_memory . Init ( 1024 * 1024 , 0 , 0 , 4 ) ;
}
m_pCurrent = ( int * ) m_memory . Alloc ( nSize ) ;
m_nBytesCurrent = nSize ;
return m_pCurrent ;
}
void * CNavVectorNoEditAllocator : : Realloc ( void * pMem , size_t nSize )
{
if ( pMem ! = m_pCurrent )
{
Assert ( 0 ) ;
Error ( " Nav mesh cannot be mutated after load \n " ) ;
}
if ( nSize > ( size_t ) m_nBytesCurrent )
{
m_memory . Alloc ( nSize - m_nBytesCurrent ) ;
m_nBytesCurrent = nSize ;
}
return m_pCurrent ;
}
void CNavVectorNoEditAllocator : : Free ( void * pMem )
{
}
size_t CNavVectorNoEditAllocator : : GetSize ( void * pMem )
{
if ( pMem ! = m_pCurrent )
{
Assert ( 0 ) ;
Error ( " Nav mesh cannot be mutated after load \n " ) ;
}
return m_nBytesCurrent ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : CompressIDs ( void )
{
m_nextID = 1 ;
FOR_EACH_VEC ( TheNavAreas , id )
{
CNavArea * area = TheNavAreas [ id ] ;
area - > m_id = m_nextID + + ;
// remove and re-add the area from the nav mesh to update the hashed ID
TheNavMesh - > RemoveNavArea ( area ) ;
TheNavMesh - > AddNavArea ( area ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor used during normal runtime .
*/
CNavArea : : CNavArea ( void )
{
m_marker = 0 ;
m_nearNavSearchMarker = 0 ;
m_damagingTickCount = 0 ;
m_openMarker = 0 ;
m_parent = NULL ;
m_parentHow = GO_NORTH ;
m_attributeFlags = 0 ;
m_place = TheNavMesh - > GetNavPlace ( ) ;
m_isUnderwater = false ;
m_avoidanceObstacleHeight = 0.0f ;
m_totalCost = 0.0f ;
ResetNodes ( ) ;
int i ;
for ( i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
m_isBlocked [ i ] = false ;
m_danger [ i ] = 0.0f ;
m_dangerTimestamp [ i ] = 0.0f ;
m_clearedTimestamp [ i ] = 0.0f ;
m_earliestOccupyTime [ i ] = 0.0f ;
m_playerCount [ i ] = 0 ;
}
// set an ID for splitting and other interactive editing - loads will overwrite this
m_id = m_nextID + + ;
m_debugid = 0 ;
m_prevHash = NULL ;
m_nextHash = NULL ;
m_isBattlefront = false ;
for ( i = 0 ; i < NUM_DIRECTIONS ; + + i )
{
m_connect [ i ] . RemoveAll ( ) ;
}
for ( i = 0 ; i < CNavLadder : : NUM_LADDER_DIRECTIONS ; + + i )
{
m_ladder [ i ] . RemoveAll ( ) ;
}
for ( i = 0 ; i < NUM_CORNERS ; + + i )
{
m_lightIntensity [ i ] = 1.0f ;
}
m_elevator = NULL ;
m_elevatorAreas . RemoveAll ( ) ;
m_invDxCorners = 0 ;
m_invDyCorners = 0 ;
m_inheritVisibilityFrom . area = NULL ;
m_isInheritedFrom = false ;
m_funcNavCostVector . RemoveAll ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Assumes Z is flat
*/
void CNavArea : : Build ( const Vector & corner , const Vector & otherCorner )
{
if ( corner . x < otherCorner . x )
{
m_nwCorner . x = corner . x ;
m_seCorner . x = otherCorner . x ;
}
else
{
m_seCorner . x = corner . x ;
m_nwCorner . x = otherCorner . x ;
}
if ( corner . y < otherCorner . y )
{
m_nwCorner . y = corner . y ;
m_seCorner . y = otherCorner . y ;
}
else
{
m_seCorner . y = corner . y ;
m_nwCorner . y = otherCorner . y ;
}
m_nwCorner . z = corner . z ;
m_seCorner . z = corner . z ;
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
m_neZ = corner . z ;
m_swZ = otherCorner . z ;
CalcDebugID ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Build a nav area given the positions of its four corners .
*/
void CNavArea : : Build ( const Vector & nwCorner , const Vector & neCorner , const Vector & seCorner , const Vector & swCorner )
{
m_nwCorner = nwCorner ;
m_seCorner = seCorner ;
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
m_neZ = neCorner . z ;
m_swZ = swCorner . z ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
CalcDebugID ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Used during generation phase to build nav areas from sampled nodes .
*/
void CNavArea : : Build ( CNavNode * nwNode , CNavNode * neNode , CNavNode * seNode , CNavNode * swNode )
{
m_nwCorner = * nwNode - > GetPosition ( ) ;
m_seCorner = * seNode - > GetPosition ( ) ;
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
m_neZ = neNode - > GetPosition ( ) - > z ;
m_swZ = swNode - > GetPosition ( ) - > z ;
m_node [ NORTH_WEST ] = nwNode ;
m_node [ NORTH_EAST ] = neNode ;
m_node [ SOUTH_EAST ] = seNode ;
m_node [ SOUTH_WEST ] = swNode ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
// mark internal nodes as part of this area
AssignNodes ( this ) ;
CalcDebugID ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
// Return a computed extent (XY is in m_nwCorner and m_seCorner, Z is computed)
void CNavArea : : GetExtent ( Extent * extent ) const
{
extent - > lo = m_nwCorner ;
extent - > hi = m_seCorner ;
extent - > lo . z = MIN ( extent - > lo . z , m_nwCorner . z ) ;
extent - > lo . z = MIN ( extent - > lo . z , m_seCorner . z ) ;
extent - > lo . z = MIN ( extent - > lo . z , m_neZ ) ;
extent - > lo . z = MIN ( extent - > lo . z , m_swZ ) ;
extent - > hi . z = MAX ( extent - > hi . z , m_nwCorner . z ) ;
extent - > hi . z = MAX ( extent - > hi . z , m_seCorner . z ) ;
extent - > hi . z = MAX ( extent - > hi . z , m_neZ ) ;
extent - > hi . z = MAX ( extent - > hi . z , m_swZ ) ;
}
//--------------------------------------------------------------------------------------------------------------
// returns the closest node along the given edge to the given point
CNavNode * CNavArea : : FindClosestNode ( const Vector & pos , NavDirType dir ) const
{
if ( ! HasNodes ( ) )
return NULL ;
CUtlVector < CNavNode * > nodes ;
GetNodes ( dir , & nodes ) ;
CNavNode * bestNode = NULL ;
float bestDistanceSq = FLT_MAX ;
for ( int i = 0 ; i < nodes . Count ( ) ; + + i )
{
float distSq = pos . DistToSqr ( * nodes [ i ] - > GetPosition ( ) ) ;
if ( distSq < bestDistanceSq )
{
bestDistanceSq = distSq ;
bestNode = nodes [ i ] ;
}
}
return bestNode ;
}
//--------------------------------------------------------------------------------------------------------------
// build a vector of nodes along the given direction
void CNavArea : : GetNodes ( NavDirType dir , CUtlVector < CNavNode * > * nodes ) const
{
if ( ! nodes )
return ;
nodes - > RemoveAll ( ) ;
NavCornerType startCorner ;
NavCornerType endCorner ;
NavDirType traversalDirection ;
switch ( dir )
{
case NORTH :
startCorner = NORTH_WEST ;
endCorner = NORTH_EAST ;
traversalDirection = EAST ;
break ;
case SOUTH :
startCorner = SOUTH_WEST ;
endCorner = SOUTH_EAST ;
traversalDirection = EAST ;
break ;
case EAST :
startCorner = NORTH_EAST ;
endCorner = SOUTH_EAST ;
traversalDirection = SOUTH ;
break ;
case WEST :
startCorner = NORTH_WEST ;
endCorner = SOUTH_WEST ;
traversalDirection = SOUTH ;
break ;
default :
return ;
}
CNavNode * node ;
for ( node = m_node [ startCorner ] ; node & & node ! = m_node [ endCorner ] ; node = node - > GetConnectedNode ( traversalDirection ) )
{
nodes - > AddToTail ( node ) ;
}
if ( node & & node = = m_node [ endCorner ] )
{
nodes - > AddToTail ( node ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
class ForgetArea
{
public :
ForgetArea ( CNavArea * area )
{
m_area = area ;
}
bool operator ( ) ( CBasePlayer * player )
{
player - > OnNavAreaRemoved ( m_area ) ;
return true ;
}
bool operator ( ) ( CBaseCombatCharacter * player )
{
player - > OnNavAreaRemoved ( m_area ) ;
return true ;
}
CNavArea * m_area ;
} ;
//--------------------------------------------------------------------------------------------------------------
class AreaDestroyNotification
{
CNavArea * m_area ;
public :
AreaDestroyNotification ( CNavArea * area )
{
m_area = area ;
}
bool operator ( ) ( CNavLadder * ladder )
{
ladder - > OnDestroyNotify ( m_area ) ;
return true ;
}
bool operator ( ) ( CNavArea * area )
{
if ( area ! = m_area )
{
area - > OnDestroyNotify ( m_area ) ;
}
return true ;
}
} ;
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CNavArea : : ~ CNavArea ( )
{
// spot encounters aren't owned by anything else, so free them up here
m_spotEncounters . PurgeAndDeleteElements ( ) ;
// if we are resetting the system, don't bother cleaning up - all areas are being destroyed
if ( m_isReset )
return ;
// tell the other areas and ladders we are going away
AreaDestroyNotification notification ( this ) ;
TheNavMesh - > ForAllAreas ( notification ) ;
TheNavMesh - > ForAllLadders ( notification ) ;
// remove the area from the grid
TheNavMesh - > RemoveNavArea ( this ) ;
// make sure no players keep a pointer to this area
ForgetArea forget ( this ) ;
ForEachActor ( forget ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find elevator connections between areas
*/
void CNavArea : : ConnectElevators ( void )
{
m_elevator = NULL ;
m_attributeFlags & = ~ NAV_MESH_HAS_ELEVATOR ;
m_elevatorAreas . RemoveAll ( ) ;
# ifdef TERROR
// connect elevators
CFuncElevator * elevator = NULL ;
while ( ( elevator = ( CFuncElevator * ) gEntList . FindEntityByClassname ( elevator , " func_elevator " ) ) ! = NULL )
{
if ( elevator - > GetNumFloors ( ) < 2 )
{
// broken elevator
continue ;
}
Extent elevatorExtent ;
elevator - > CollisionProp ( ) - > WorldSpaceSurroundingBounds ( & elevatorExtent . lo , & elevatorExtent . hi ) ;
if ( IsOverlapping ( elevatorExtent ) )
{
// overlaps in 2D - check that this area is within the shaft of the elevator
const Vector & center = GetCenter ( ) ;
for ( int f = 0 ; f < elevator - > GetNumFloors ( ) ; + + f )
{
const FloorInfo * floor = elevator - > GetFloor ( f ) ;
const float tolerance = 30.0f ;
if ( center . z < = floor - > height + tolerance & & center . z > = floor - > height - tolerance )
{
if ( m_elevator )
{
Warning ( " Multiple elevators overlap navigation area #%d \n " , GetID ( ) ) ;
break ;
}
// this area is part of an elevator system
m_elevator = elevator ;
m_attributeFlags | = NAV_MESH_HAS_ELEVATOR ;
// find the largest area overlapping this elevator on each other floor
for ( int of = 0 ; of < elevator - > GetNumFloors ( ) ; + + of )
{
if ( of = = f )
{
// we are on this floor
continue ;
}
const FloorInfo * otherFloor = elevator - > GetFloor ( of ) ;
// find the largest area at this floor
CNavArea * floorArea = NULL ;
float floorAreaSize = 0.0f ;
FOR_EACH_VEC ( TheNavAreas , it )
{
CNavArea * area = TheNavAreas [ it ] ;
if ( area - > IsOverlapping ( elevatorExtent ) )
{
if ( area - > GetCenter ( ) . z < = otherFloor - > height + tolerance & & area - > GetCenter ( ) . z > = otherFloor - > height - tolerance )
{
float size = area - > GetSizeX ( ) * area - > GetSizeY ( ) ;
if ( size > floorAreaSize )
{
floorArea = area ;
floorAreaSize = size ;
}
}
}
}
if ( floorArea )
{
// add this area to the set of areas reachable via elevator
NavConnect con ;
con . area = floorArea ;
con . length = ( floorArea - > GetCenter ( ) - GetCenter ( ) ) . Length ( ) ;
m_elevatorAreas . AddToTail ( con ) ;
}
else
{
Warning ( " Floor %d ('%s') of elevator at ( %3.2f, %3.2f, %3.2f ) has no matching navigation areas \n " ,
of ,
otherFloor - > name . ToCStr ( ) ,
elevator - > GetAbsOrigin ( ) . x , elevator - > GetAbsOrigin ( ) . y , elevator - > GetAbsOrigin ( ) . z ) ;
}
}
// we found our floor
break ;
}
}
}
}
# endif // TERROR
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when map is initially loaded
*/
void CNavArea : : OnServerActivate ( void )
{
ConnectElevators ( ) ;
m_damagingTickCount = 0 ;
ClearAllNavCostEntities ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked for each area when the round restarts
*/
void CNavArea : : OnRoundRestart ( void )
{
// need to redo this here since func_elevators are deleted and recreated at round restart
ConnectElevators ( ) ;
m_damagingTickCount = 0 ;
ClearAllNavCostEntities ( ) ;
}
# ifdef DEBUG_AREA_PLAYERCOUNTS
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : IncrementPlayerCount ( int teamID , int entIndex )
{
ConColorMsg ( Color ( 128 , 255 , 128 , 255 ) , " %f: Adding ent %d (team %d) to area %d \n " , gpGlobals - > curtime , entIndex , teamID , GetID ( ) ) ;
teamID = teamID % MAX_NAV_TEAMS ;
Assert ( ! m_playerEntIndices [ teamID ] . HasElement ( entIndex ) ) ;
if ( ! m_playerEntIndices [ teamID ] . HasElement ( entIndex ) )
{
m_playerEntIndices [ teamID ] . AddToTail ( entIndex ) ;
}
if ( m_playerCount [ teamID ] = = 255 )
{
Warning ( " CNavArea::IncrementPlayerCount: Overflow \n " ) ;
return ;
}
+ + m_playerCount [ teamID ] ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : DecrementPlayerCount ( int teamID , int entIndex )
{
ConColorMsg ( Color ( 128 , 128 , 255 , 255 ) , " %f: Removing ent %d (team %d) from area %d \n " , gpGlobals - > curtime , entIndex , teamID , GetID ( ) ) ;
teamID = teamID % MAX_NAV_TEAMS ;
Assert ( m_playerEntIndices [ teamID ] . HasElement ( entIndex ) ) ;
m_playerEntIndices [ teamID ] . FindAndFastRemove ( entIndex ) ;
if ( m_playerCount [ teamID ] = = 0 )
{
Warning ( " CNavArea::IncrementPlayerCount: Underflow \n " ) ;
return ;
}
- - m_playerCount [ teamID ] ;
}
# endif // DEBUG_AREA_PLAYERCOUNTS
//--------------------------------------------------------------------------------------------------------------
/**
* This is invoked at the start of an incremental nav generation on pre - existing areas .
*/
void CNavArea : : ResetNodes ( void )
{
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
m_node [ i ] = NULL ;
}
}
//--------------------------------------------------------------------------------------------------------------
bool CNavArea : : HasNodes ( void ) const
{
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
if ( m_node [ i ] )
{
return true ;
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* This is invoked when an area is going away .
* Remove any references we have to it .
*/
void CNavArea : : OnDestroyNotify ( CNavArea * dead )
{
NavConnect con ;
con . area = dead ;
for ( int d = 0 ; d < NUM_DIRECTIONS ; + + d )
{
m_connect [ d ] . FindAndRemove ( con ) ;
m_incomingConnect [ d ] . FindAndRemove ( con ) ;
}
// remove all visibility info, since we're editing the mesh anyways
m_inheritVisibilityFrom . area = NULL ;
m_potentiallyVisibleAreas . RemoveAll ( ) ;
m_isInheritedFrom = false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* This is invoked when a ladder is going away .
* Remove any references we have to it .
*/
void CNavArea : : OnDestroyNotify ( CNavLadder * dead )
{
Disconnect ( dead ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Connect this area to given area in given direction
*/
void CNavArea : : ConnectTo ( CNavArea * area , NavDirType dir )
{
// don't allow self-referential connections
if ( area = = this )
return ;
// check if already connected
FOR_EACH_VEC ( m_connect [ dir ] , it )
{
if ( m_connect [ dir ] [ it ] . area = = area )
return ;
}
NavConnect con ;
con . area = area ;
con . length = ( area - > GetCenter ( ) - GetCenter ( ) ) . Length ( ) ;
m_connect [ dir ] . AddToTail ( con ) ;
m_incomingConnect [ dir ] . FindAndRemove ( con ) ;
NavDirType dirOpposite = OppositeDirection ( dir ) ;
con . area = this ;
if ( area - > m_connect [ dirOpposite ] . Find ( con ) = = area - > m_connect [ dirOpposite ] . InvalidIndex ( ) )
{
area - > AddIncomingConnection ( this , dirOpposite ) ;
}
//static char *dirName[] = { "NORTH", "EAST", "SOUTH", "WEST" };
//CONSOLE_ECHO( " Connected area #%d to #%d, %s\n", m_id, area->m_id, dirName[ dir ] );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Connect this area to given ladder
*/
void CNavArea : : ConnectTo ( CNavLadder * ladder )
{
float center = ( ladder - > m_top . z + ladder - > m_bottom . z ) * 0.5f ;
Disconnect ( ladder ) ; // just in case
if ( GetCenter ( ) . z > center )
{
AddLadderDown ( ladder ) ;
}
else
{
AddLadderUp ( ladder ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Disconnect this area from given area
*/
void CNavArea : : Disconnect ( CNavArea * area )
{
NavConnect connect ;
connect . area = area ;
for ( int i = 0 ; i < NUM_DIRECTIONS ; i + + )
{
NavDirType dir = ( NavDirType ) i ;
NavDirType dirOpposite = OppositeDirection ( dir ) ;
int index = m_connect [ dir ] . Find ( connect ) ;
if ( index ! = m_connect [ dir ] . InvalidIndex ( ) )
{
m_connect [ dir ] . Remove ( index ) ;
if ( area - > IsConnected ( this , dirOpposite ) )
{
AddIncomingConnection ( area , dir ) ;
}
else
{
connect . area = this ;
area - > m_incomingConnect [ dirOpposite ] . FindAndRemove ( connect ) ;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Disconnect this area from given ladder
*/
void CNavArea : : Disconnect ( CNavLadder * ladder )
{
NavLadderConnect con ;
con . ladder = ladder ;
for ( int i = 0 ; i < CNavLadder : : NUM_LADDER_DIRECTIONS ; + + i )
{
m_ladder [ i ] . FindAndRemove ( con ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : AddLadderUp ( CNavLadder * ladder )
{
Disconnect ( ladder ) ; // just in case
NavLadderConnect tmp ;
tmp . ladder = ladder ;
m_ladder [ CNavLadder : : LADDER_UP ] . AddToTail ( tmp ) ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : AddLadderDown ( CNavLadder * ladder )
{
Disconnect ( ladder ) ; // just in case
NavLadderConnect tmp ;
tmp . ladder = ladder ;
m_ladder [ CNavLadder : : LADDER_DOWN ] . AddToTail ( tmp ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Recompute internal data once nodes have been adjusted during merge
* Destroy adjArea .
*/
void CNavArea : : FinishMerge ( CNavArea * adjArea )
{
// update extent
m_nwCorner = * m_node [ NORTH_WEST ] - > GetPosition ( ) ;
m_seCorner = * m_node [ SOUTH_EAST ] - > GetPosition ( ) ;
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
m_neZ = m_node [ NORTH_EAST ] - > GetPosition ( ) - > z ;
m_swZ = m_node [ SOUTH_WEST ] - > GetPosition ( ) - > z ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
// reassign the adjacent area's internal nodes to the final area
adjArea - > AssignNodes ( this ) ;
// merge adjacency links - we gain all the connections that adjArea had
MergeAdjacentConnections ( adjArea ) ;
// remove subsumed adjacent area
TheNavAreas . FindAndRemove ( adjArea ) ;
TheNavMesh - > OnEditDestroyNotify ( adjArea ) ;
TheNavMesh - > DestroyArea ( adjArea ) ;
}
//--------------------------------------------------------------------------------------------------------------
class LadderConnectionReplacement
{
CNavArea * m_originalArea ;
CNavArea * m_replacementArea ;
public :
LadderConnectionReplacement ( CNavArea * originalArea , CNavArea * replacementArea )
{
m_originalArea = originalArea ;
m_replacementArea = replacementArea ;
}
bool operator ( ) ( CNavLadder * ladder )
{
if ( ladder - > m_topForwardArea = = m_originalArea )
ladder - > m_topForwardArea = m_replacementArea ;
if ( ladder - > m_topRightArea = = m_originalArea )
ladder - > m_topRightArea = m_replacementArea ;
if ( ladder - > m_topLeftArea = = m_originalArea )
ladder - > m_topLeftArea = m_replacementArea ;
if ( ladder - > m_topBehindArea = = m_originalArea )
ladder - > m_topBehindArea = m_replacementArea ;
if ( ladder - > m_bottomArea = = m_originalArea )
ladder - > m_bottomArea = m_replacementArea ;
return true ;
}
} ;
//--------------------------------------------------------------------------------------------------------------
/**
* For merging with " adjArea " - pick up all of " adjArea " s connections
*/
void CNavArea : : MergeAdjacentConnections ( CNavArea * adjArea )
{
// merge adjacency links - we gain all the connections that adjArea had
int dir ;
for ( dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
{
FOR_EACH_VEC ( adjArea - > m_connect [ dir ] , it )
{
NavConnect connect = adjArea - > m_connect [ dir ] [ it ] ;
if ( connect . area ! = adjArea & & connect . area ! = this )
ConnectTo ( connect . area , ( NavDirType ) dir ) ;
}
}
// remove any references from this area to the adjacent area, since it is now part of us
Disconnect ( adjArea ) ;
// Change other references to adjArea to refer instead to us
// We can't just replace existing connections, as several adjacent areas may have been merged into one,
// resulting in a large area adjacent to all of them ending up with multiple redunandant connections
// into the merged area, one for each of the adjacent subsumed smaller ones.
// If an area has a connection to the merged area, we must remove all references to adjArea, and add
// a single connection to us.
FOR_EACH_VEC ( TheNavAreas , it )
{
CNavArea * area = TheNavAreas [ it ] ;
if ( area = = this | | area = = adjArea )
continue ;
for ( dir = 0 ; dir < NUM_DIRECTIONS ; dir + + )
{
// check if there are any references to adjArea in this direction
bool connected = false ;
FOR_EACH_VEC ( area - > m_connect [ dir ] , cit )
{
NavConnect connect = area - > m_connect [ dir ] [ cit ] ;
if ( connect . area = = adjArea )
{
connected = true ;
break ;
}
}
if ( connected )
{
// remove all references to adjArea
area - > Disconnect ( adjArea ) ;
// remove all references to the new area
area - > Disconnect ( this ) ;
// add a single connection to the new area
area - > ConnectTo ( this , ( NavDirType ) dir ) ;
}
}
}
// We gain all ladder connections adjArea had
for ( dir = 0 ; dir < CNavLadder : : NUM_LADDER_DIRECTIONS ; + + dir )
{
FOR_EACH_VEC ( adjArea - > m_ladder [ dir ] , it )
{
ConnectTo ( adjArea - > m_ladder [ dir ] [ it ] . ladder ) ;
}
}
// All ladders that point to adjArea should point to us now
LadderConnectionReplacement replacement ( adjArea , this ) ;
TheNavMesh - > ForAllLadders ( replacement ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Assign internal nodes to the given area
* NOTE : " internal " nodes do not include the east or south border nodes
*/
void CNavArea : : AssignNodes ( CNavArea * area )
{
CNavNode * horizLast = m_node [ NORTH_EAST ] ;
for ( CNavNode * vertNode = m_node [ NORTH_WEST ] ; vertNode ! = m_node [ SOUTH_WEST ] ; vertNode = vertNode - > GetConnectedNode ( SOUTH ) )
{
for ( CNavNode * horizNode = vertNode ; horizNode ! = horizLast ; horizNode = horizNode - > GetConnectedNode ( EAST ) )
{
horizNode - > AssignArea ( area ) ;
}
horizLast = horizLast - > GetConnectedNode ( SOUTH ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
class SplitNotification
{
CNavArea * m_originalArea ;
CNavArea * m_alphaArea ;
CNavArea * m_betaArea ;
public :
SplitNotification ( CNavArea * originalArea , CNavArea * alphaArea , CNavArea * betaArea )
{
m_originalArea = originalArea ;
m_alphaArea = alphaArea ;
m_betaArea = betaArea ;
}
bool operator ( ) ( CNavLadder * ladder )
{
ladder - > OnSplit ( m_originalArea , m_alphaArea , m_betaArea ) ;
return true ;
}
} ;
//--------------------------------------------------------------------------------------------------------------
/**
* Split this area into two areas at the given edge .
* Preserve all adjacency connections .
* NOTE : This does not update node connections , only areas .
*/
bool CNavArea : : SplitEdit ( bool splitAlongX , float splitEdge , CNavArea * * outAlpha , CNavArea * * outBeta )
{
CNavArea * alpha = NULL ;
CNavArea * beta = NULL ;
if ( splitAlongX )
{
// +-----+->X
// | A |
// +-----+
// | B |
// +-----+
// |
// Y
// don't do split if at edge of area
if ( splitEdge < = m_nwCorner . y + 1.0f )
return false ;
if ( splitEdge > = m_seCorner . y - 1.0f )
return false ;
alpha = TheNavMesh - > CreateArea ( ) ;
alpha - > m_nwCorner = m_nwCorner ;
alpha - > m_seCorner . x = m_seCorner . x ;
alpha - > m_seCorner . y = splitEdge ;
alpha - > m_seCorner . z = GetZ ( alpha - > m_seCorner ) ;
beta = TheNavMesh - > CreateArea ( ) ;
beta - > m_nwCorner . x = m_nwCorner . x ;
beta - > m_nwCorner . y = splitEdge ;
beta - > m_nwCorner . z = GetZ ( beta - > m_nwCorner ) ;
beta - > m_seCorner = m_seCorner ;
alpha - > ConnectTo ( beta , SOUTH ) ;
beta - > ConnectTo ( alpha , NORTH ) ;
FinishSplitEdit ( alpha , SOUTH ) ;
FinishSplitEdit ( beta , NORTH ) ;
}
else
{
// +--+--+->X
// | | |
// | A|B |
// | | |
// +--+--+
// |
// Y
// don't do split if at edge of area
if ( splitEdge < = m_nwCorner . x + 1.0f )
return false ;
if ( splitEdge > = m_seCorner . x - 1.0f )
return false ;
alpha = TheNavMesh - > CreateArea ( ) ;
alpha - > m_nwCorner = m_nwCorner ;
alpha - > m_seCorner . x = splitEdge ;
alpha - > m_seCorner . y = m_seCorner . y ;
alpha - > m_seCorner . z = GetZ ( alpha - > m_seCorner ) ;
beta = TheNavMesh - > CreateArea ( ) ;
beta - > m_nwCorner . x = splitEdge ;
beta - > m_nwCorner . y = m_nwCorner . y ;
beta - > m_nwCorner . z = GetZ ( beta - > m_nwCorner ) ;
beta - > m_seCorner = m_seCorner ;
alpha - > ConnectTo ( beta , EAST ) ;
beta - > ConnectTo ( alpha , WEST ) ;
FinishSplitEdit ( alpha , EAST ) ;
FinishSplitEdit ( beta , WEST ) ;
}
if ( ! TheNavMesh - > IsGenerating ( ) & & nav_split_place_on_ground . GetBool ( ) )
{
alpha - > PlaceOnGround ( NUM_CORNERS ) ;
beta - > PlaceOnGround ( NUM_CORNERS ) ;
}
// For every ladder we pointed to, alpha or beta should point to it, based on
// their distance to the ladder
int dir ;
for ( dir = 0 ; dir < CNavLadder : : NUM_LADDER_DIRECTIONS ; + + dir )
{
FOR_EACH_VEC ( m_ladder [ dir ] , it )
{
CNavLadder * ladder = m_ladder [ dir ] [ it ] . ladder ;
Vector ladderPos = ladder - > m_top ; // doesn't matter if we choose top or bottom
float alphaDistance = alpha - > GetDistanceSquaredToPoint ( ladderPos ) ;
float betaDistance = beta - > GetDistanceSquaredToPoint ( ladderPos ) ;
if ( alphaDistance < betaDistance )
{
alpha - > ConnectTo ( ladder ) ;
}
else
{
beta - > ConnectTo ( ladder ) ;
}
}
}
// For every ladder that pointed to us, connect that ladder to the closer of alpha and beta
SplitNotification notify ( this , alpha , beta ) ;
TheNavMesh - > ForAllLadders ( notify ) ;
// return new areas
if ( outAlpha )
* outAlpha = alpha ;
if ( outBeta )
* outBeta = beta ;
TheNavMesh - > OnEditCreateNotify ( alpha ) ;
TheNavMesh - > OnEditCreateNotify ( beta ) ;
if ( TheNavMesh - > IsInSelectedSet ( this ) )
{
TheNavMesh - > AddToSelectedSet ( alpha ) ;
TheNavMesh - > AddToSelectedSet ( beta ) ;
}
// remove original area
TheNavMesh - > OnEditDestroyNotify ( this ) ;
TheNavAreas . FindAndRemove ( this ) ;
TheNavMesh - > RemoveFromSelectedSet ( this ) ;
TheNavMesh - > DestroyArea ( this ) ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given ladder is connected in given direction
* @ todo Formalize " asymmetric " flag on connections
*/
bool CNavArea : : IsConnected ( const CNavLadder * ladder , CNavLadder : : LadderDirectionType dir ) const
{
FOR_EACH_VEC ( m_ladder [ dir ] , it )
{
if ( ladder = = m_ladder [ dir ] [ it ] . ladder )
{
return true ;
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given area is connected in given direction
* if dir = = NUM_DIRECTIONS , check all directions ( direction is unknown )
* @ todo Formalize " asymmetric " flag on connections
*/
bool CNavArea : : IsConnected ( const CNavArea * area , NavDirType dir ) const
{
// we are connected to ourself
if ( area = = this )
return true ;
if ( dir = = NUM_DIRECTIONS )
{
// search all directions
for ( int d = 0 ; d < NUM_DIRECTIONS ; + + d )
{
FOR_EACH_VEC ( m_connect [ d ] , it )
{
if ( area = = m_connect [ d ] [ it ] . area )
return true ;
}
}
// check ladder connections
FOR_EACH_VEC ( m_ladder [ CNavLadder : : LADDER_UP ] , it )
{
CNavLadder * ladder = m_ladder [ CNavLadder : : LADDER_UP ] [ it ] . ladder ;
if ( ladder - > m_topBehindArea = = area | |
ladder - > m_topForwardArea = = area | |
ladder - > m_topLeftArea = = area | |
ladder - > m_topRightArea = = area )
return true ;
}
FOR_EACH_VEC ( m_ladder [ CNavLadder : : LADDER_DOWN ] , dit )
{
CNavLadder * ladder = m_ladder [ CNavLadder : : LADDER_DOWN ] [ dit ] . ladder ;
if ( ladder - > m_bottomArea = = area )
return true ;
}
}
else
{
// check specific direction
FOR_EACH_VEC ( m_connect [ dir ] , it )
{
if ( area = = m_connect [ dir ] [ it ] . area )
return true ;
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Compute change in actual ground height from this area to given area
*/
float CNavArea : : ComputeGroundHeightChange ( const CNavArea * area )
{
VPROF_BUDGET ( " CNavArea::ComputeHeightChange " , " NextBot " ) ;
Vector closeFrom , closeTo ;
area - > GetClosestPointOnArea ( GetCenter ( ) , & closeTo ) ;
GetClosestPointOnArea ( area - > GetCenter ( ) , & closeFrom ) ;
// find actual ground height at each point in case
// areas are below/above actual terrain
float toZ , fromZ ;
if ( TheNavMesh - > GetSimpleGroundHeight ( closeTo + Vector ( 0 , 0 , StepHeight ) , & toZ ) = = false )
{
return 0.0f ;
}
if ( TheNavMesh - > GetSimpleGroundHeight ( closeFrom + Vector ( 0 , 0 , StepHeight ) , & fromZ ) = = false )
{
return 0.0f ;
}
return toZ - fromZ ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* The area ' source ' is connected to us along our ' incomingEdgeDir ' edge
*/
void CNavArea : : AddIncomingConnection ( CNavArea * source , NavDirType incomingEdgeDir )
{
NavConnect con ;
con . area = source ;
if ( m_incomingConnect [ incomingEdgeDir ] . Find ( con ) = = m_incomingConnect [ incomingEdgeDir ] . InvalidIndex ( ) )
{
con . length = ( source - > GetCenter ( ) - GetCenter ( ) ) . Length ( ) ;
m_incomingConnect [ incomingEdgeDir ] . AddToTail ( con ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Given the portion of the original area , update its internal data
* The " ignoreEdge " direction defines the side of the original area that the new area does not include
*/
void CNavArea : : FinishSplitEdit ( CNavArea * newArea , NavDirType ignoreEdge )
{
newArea - > InheritAttributes ( this ) ;
newArea - > m_center . x = ( newArea - > m_nwCorner . x + newArea - > m_seCorner . x ) / 2.0f ;
newArea - > m_center . y = ( newArea - > m_nwCorner . y + newArea - > m_seCorner . y ) / 2.0f ;
newArea - > m_center . z = ( newArea - > m_nwCorner . z + newArea - > m_seCorner . z ) / 2.0f ;
newArea - > m_neZ = GetZ ( newArea - > m_seCorner . x , newArea - > m_nwCorner . y ) ;
newArea - > m_swZ = GetZ ( newArea - > m_nwCorner . x , newArea - > m_seCorner . y ) ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
newArea - > m_invDxCorners = 1.0f / ( newArea - > m_seCorner . x - newArea - > m_nwCorner . x ) ;
newArea - > m_invDyCorners = 1.0f / ( newArea - > m_seCorner . y - newArea - > m_nwCorner . y ) ;
}
else
{
newArea - > m_invDxCorners = newArea - > m_invDyCorners = 0 ;
}
// connect to adjacent areas
for ( int d = 0 ; d < NUM_DIRECTIONS ; + + d )
{
if ( d = = ignoreEdge )
continue ;
int count = GetAdjacentCount ( ( NavDirType ) d ) ;
for ( int a = 0 ; a < count ; + + a )
{
CNavArea * adj = GetAdjacentArea ( ( NavDirType ) d , a ) ;
switch ( d )
{
case NORTH :
case SOUTH :
if ( newArea - > IsOverlappingX ( adj ) )
{
newArea - > ConnectTo ( adj , ( NavDirType ) d ) ;
// add reciprocal connection if needed
if ( adj - > IsConnected ( this , OppositeDirection ( ( NavDirType ) d ) ) )
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
case EAST :
case WEST :
if ( newArea - > IsOverlappingY ( adj ) )
{
newArea - > ConnectTo ( adj , ( NavDirType ) d ) ;
// add reciprocal connection if needed
if ( adj - > IsConnected ( this , OppositeDirection ( ( NavDirType ) d ) ) )
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
}
for ( int a = 0 ; a < m_incomingConnect [ d ] . Count ( ) ; a + + )
{
CNavArea * adj = m_incomingConnect [ d ] [ a ] . area ;
switch ( d )
{
case NORTH :
case SOUTH :
if ( newArea - > IsOverlappingX ( adj ) )
{
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
case EAST :
case WEST :
if ( newArea - > IsOverlappingY ( adj ) )
{
adj - > ConnectTo ( newArea , OppositeDirection ( ( NavDirType ) d ) ) ;
}
break ;
}
}
}
}
TheNavAreas . AddToTail ( newArea ) ;
TheNavMesh - > AddNavArea ( newArea ) ;
// Assign nodes
if ( HasNodes ( ) )
{
// first give it all our nodes...
newArea - > m_node [ NORTH_WEST ] = m_node [ NORTH_WEST ] ;
newArea - > m_node [ NORTH_EAST ] = m_node [ NORTH_EAST ] ;
newArea - > m_node [ SOUTH_EAST ] = m_node [ SOUTH_EAST ] ;
newArea - > m_node [ SOUTH_WEST ] = m_node [ SOUTH_WEST ] ;
// ... then pull in one edge...
NavDirType dir = NUM_DIRECTIONS ;
NavCornerType corner [ 2 ] = { NUM_CORNERS , NUM_CORNERS } ;
switch ( ignoreEdge )
{
case NORTH :
dir = SOUTH ;
corner [ 0 ] = NORTH_WEST ;
corner [ 1 ] = NORTH_EAST ;
break ;
case SOUTH :
dir = NORTH ;
corner [ 0 ] = SOUTH_WEST ;
corner [ 1 ] = SOUTH_EAST ;
break ;
case EAST :
dir = WEST ;
corner [ 0 ] = NORTH_EAST ;
corner [ 1 ] = SOUTH_EAST ;
break ;
case WEST :
dir = EAST ;
corner [ 0 ] = NORTH_WEST ;
corner [ 1 ] = SOUTH_WEST ;
break ;
}
while ( ! newArea - > IsOverlapping ( * newArea - > m_node [ corner [ 0 ] ] - > GetPosition ( ) , GenerationStepSize / 2 ) )
{
for ( int i = 0 ; i < 2 ; + + i )
{
Assert ( newArea - > m_node [ corner [ i ] ] ) ;
Assert ( newArea - > m_node [ corner [ i ] ] - > GetConnectedNode ( dir ) ) ;
newArea - > m_node [ corner [ i ] ] = newArea - > m_node [ corner [ i ] ] - > GetConnectedNode ( dir ) ;
}
}
// assign internal nodes...
newArea - > AssignNodes ( newArea ) ;
// ... and grab the node heights for our corner heights.
newArea - > m_neZ = newArea - > m_node [ NORTH_EAST ] - > GetPosition ( ) - > z ;
newArea - > m_nwCorner . z = newArea - > m_node [ NORTH_WEST ] - > GetPosition ( ) - > z ;
newArea - > m_swZ = newArea - > m_node [ SOUTH_WEST ] - > GetPosition ( ) - > z ;
newArea - > m_seCorner . z = newArea - > m_node [ SOUTH_EAST ] - > GetPosition ( ) - > z ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a new area between this area and given area
*/
bool CNavArea : : SpliceEdit ( CNavArea * other )
{
CNavArea * newArea = NULL ;
Vector nw , ne , se , sw ;
if ( m_nwCorner . x > other - > m_seCorner . x )
{
// 'this' is east of 'other'
float top = MAX ( m_nwCorner . y , other - > m_nwCorner . y ) ;
float bottom = MIN ( m_seCorner . y , other - > m_seCorner . y ) ;
nw . x = other - > m_seCorner . x ;
nw . y = top ;
nw . z = other - > GetZ ( nw ) ;
se . x = m_nwCorner . x ;
se . y = bottom ;
se . z = GetZ ( se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = GetZ ( ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = other - > GetZ ( sw ) ;
newArea = TheNavMesh - > CreateArea ( ) ;
if ( newArea = = NULL )
{
Warning ( " SpliceEdit: Out of memory. \n " ) ;
return false ;
}
newArea - > Build ( nw , ne , se , sw ) ;
this - > ConnectTo ( newArea , WEST ) ;
newArea - > ConnectTo ( this , EAST ) ;
other - > ConnectTo ( newArea , EAST ) ;
newArea - > ConnectTo ( other , WEST ) ;
}
else if ( m_seCorner . x < other - > m_nwCorner . x )
{
// 'this' is west of 'other'
float top = MAX ( m_nwCorner . y , other - > m_nwCorner . y ) ;
float bottom = MIN ( m_seCorner . y , other - > m_seCorner . y ) ;
nw . x = m_seCorner . x ;
nw . y = top ;
nw . z = GetZ ( nw ) ;
se . x = other - > m_nwCorner . x ;
se . y = bottom ;
se . z = other - > GetZ ( se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = other - > GetZ ( ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = GetZ ( sw ) ;
newArea = TheNavMesh - > CreateArea ( ) ;
if ( newArea = = NULL )
{
Warning ( " SpliceEdit: Out of memory. \n " ) ;
return false ;
}
newArea - > Build ( nw , ne , se , sw ) ;
this - > ConnectTo ( newArea , EAST ) ;
newArea - > ConnectTo ( this , WEST ) ;
other - > ConnectTo ( newArea , WEST ) ;
newArea - > ConnectTo ( other , EAST ) ;
}
else // 'this' overlaps in X
{
if ( m_nwCorner . y > other - > m_seCorner . y )
{
// 'this' is south of 'other'
float left = MAX ( m_nwCorner . x , other - > m_nwCorner . x ) ;
float right = MIN ( m_seCorner . x , other - > m_seCorner . x ) ;
nw . x = left ;
nw . y = other - > m_seCorner . y ;
nw . z = other - > GetZ ( nw ) ;
se . x = right ;
se . y = m_nwCorner . y ;
se . z = GetZ ( se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = other - > GetZ ( ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = GetZ ( sw ) ;
newArea = TheNavMesh - > CreateArea ( ) ;
if ( newArea = = NULL )
{
Warning ( " SpliceEdit: Out of memory. \n " ) ;
return false ;
}
newArea - > Build ( nw , ne , se , sw ) ;
this - > ConnectTo ( newArea , NORTH ) ;
newArea - > ConnectTo ( this , SOUTH ) ;
other - > ConnectTo ( newArea , SOUTH ) ;
newArea - > ConnectTo ( other , NORTH ) ;
}
else if ( m_seCorner . y < other - > m_nwCorner . y )
{
// 'this' is north of 'other'
float left = MAX ( m_nwCorner . x , other - > m_nwCorner . x ) ;
float right = MIN ( m_seCorner . x , other - > m_seCorner . x ) ;
nw . x = left ;
nw . y = m_seCorner . y ;
nw . z = GetZ ( nw ) ;
se . x = right ;
se . y = other - > m_nwCorner . y ;
se . z = other - > GetZ ( se ) ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = GetZ ( ne ) ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = other - > GetZ ( sw ) ;
newArea = TheNavMesh - > CreateArea ( ) ;
if ( newArea = = NULL )
{
Warning ( " SpliceEdit: Out of memory. \n " ) ;
return false ;
}
newArea - > Build ( nw , ne , se , sw ) ;
this - > ConnectTo ( newArea , SOUTH ) ;
newArea - > ConnectTo ( this , NORTH ) ;
other - > ConnectTo ( newArea , NORTH ) ;
newArea - > ConnectTo ( other , SOUTH ) ;
}
else
{
// areas overlap
return false ;
}
}
newArea - > InheritAttributes ( this , other ) ;
TheNavAreas . AddToTail ( newArea ) ;
TheNavMesh - > AddNavArea ( newArea ) ;
TheNavMesh - > OnEditCreateNotify ( newArea ) ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Calculates a constant ID for an area at this location , for debugging
*/
void CNavArea : : CalcDebugID ( )
{
if ( m_debugid = = 0 )
{
// calculate a debug ID which will be constant for this nav area across generation runs
int coord [ 6 ] = { ( int ) m_nwCorner . x , ( int ) m_nwCorner . x , ( int ) m_nwCorner . z , ( int ) m_seCorner . x , ( int ) m_seCorner . y , ( int ) m_seCorner . z } ;
m_debugid = CRC32_ProcessSingleBuffer ( & coord , sizeof ( coord ) ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Merge this area and given adjacent area
*/
bool CNavArea : : MergeEdit ( CNavArea * adj )
{
// can only merge if attributes of both areas match
// check that these areas can be merged
const float tolerance = 1.0f ;
bool merge = false ;
if ( fabs ( m_nwCorner . x - adj - > m_nwCorner . x ) < tolerance & &
fabs ( m_seCorner . x - adj - > m_seCorner . x ) < tolerance )
merge = true ;
if ( fabs ( m_nwCorner . y - adj - > m_nwCorner . y ) < tolerance & &
fabs ( m_seCorner . y - adj - > m_seCorner . y ) < tolerance )
merge = true ;
if ( merge = = false )
return false ;
Vector originalNWCorner = m_nwCorner ;
Vector originalSECorner = m_seCorner ;
// update extent
if ( m_nwCorner . x > adj - > m_nwCorner . x | | m_nwCorner . y > adj - > m_nwCorner . y )
m_nwCorner = adj - > m_nwCorner ;
if ( m_seCorner . x < adj - > m_seCorner . x | | m_seCorner . y < adj - > m_seCorner . y )
m_seCorner = adj - > m_seCorner ;
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
if ( m_seCorner . x > originalSECorner . x | | m_nwCorner . y < originalNWCorner . y )
m_neZ = adj - > GetZ ( m_seCorner . x , m_nwCorner . y ) ;
else
m_neZ = GetZ ( m_seCorner . x , m_nwCorner . y ) ;
if ( m_nwCorner . x < originalNWCorner . x | | m_seCorner . y > originalSECorner . y )
m_swZ = adj - > GetZ ( m_nwCorner . x , m_seCorner . y ) ;
else
m_swZ = GetZ ( m_nwCorner . x , m_seCorner . y ) ;
// merge adjacency links - we gain all the connections that adjArea had
MergeAdjacentConnections ( adj ) ;
InheritAttributes ( adj ) ;
// remove subsumed adjacent area
TheNavAreas . FindAndRemove ( adj ) ;
TheNavMesh - > OnEditDestroyNotify ( adj ) ;
TheNavMesh - > DestroyArea ( adj ) ;
TheNavMesh - > OnEditCreateNotify ( this ) ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : InheritAttributes ( CNavArea * first , CNavArea * second )
{
if ( first & & second )
{
SetAttributes ( first - > GetAttributes ( ) | second - > GetAttributes ( ) ) ;
// if both areas have the same place, the new area inherits it
if ( first - > GetPlace ( ) = = second - > GetPlace ( ) )
{
SetPlace ( first - > GetPlace ( ) ) ;
}
else if ( first - > GetPlace ( ) = = UNDEFINED_PLACE )
{
SetPlace ( second - > GetPlace ( ) ) ;
}
else if ( second - > GetPlace ( ) = = UNDEFINED_PLACE )
{
SetPlace ( first - > GetPlace ( ) ) ;
}
else
{
// both have valid, but different places - pick on at random
if ( RandomInt ( 0 , 100 ) < 50 )
SetPlace ( first - > GetPlace ( ) ) ;
else
SetPlace ( second - > GetPlace ( ) ) ;
}
}
else if ( first )
{
SetAttributes ( GetAttributes ( ) | first - > GetAttributes ( ) ) ;
if ( GetPlace ( ) = = UNDEFINED_PLACE )
{
SetPlace ( first - > GetPlace ( ) ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void ApproachAreaAnalysisPrep ( void )
{
}
//--------------------------------------------------------------------------------------------------------------
void CleanupApproachAreaAnalysisPrep ( void )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Remove " analyzed " data from nav area
*/
void CNavArea : : Strip ( void )
{
m_spotEncounters . PurgeAndDeleteElements ( ) ; // this calls delete on each element
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if area is more or less square .
* This is used when merging to prevent long , thin , areas being created .
*/
bool CNavArea : : IsRoughlySquare ( void ) const
{
float aspect = GetSizeX ( ) / GetSizeY ( ) ;
const float maxAspect = 3.01 ;
const float minAspect = 1.0f / maxAspect ;
if ( aspect < minAspect | | aspect > maxAspect )
return false ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if ' pos ' is within 2 D extents of area .
*/
bool CNavArea : : IsOverlapping ( const Vector & pos , float tolerance ) const
{
if ( pos . x + tolerance > = m_nwCorner . x & & pos . x - tolerance < = m_seCorner . x & &
pos . y + tolerance > = m_nwCorner . y & & pos . y - tolerance < = m_seCorner . y )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if ' area ' overlaps our 2 D extents
*/
bool CNavArea : : IsOverlapping ( const CNavArea * area ) const
{
if ( area - > m_nwCorner . x < m_seCorner . x & & area - > m_seCorner . x > m_nwCorner . x & &
area - > m_nwCorner . y < m_seCorner . y & & area - > m_seCorner . y > m_nwCorner . y )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if ' extent ' overlaps our 2 D extents
*/
bool CNavArea : : IsOverlapping ( const Extent & extent ) const
{
return ( extent . lo . x < m_seCorner . x & & extent . hi . x > m_nwCorner . x & &
extent . lo . y < m_seCorner . y & & extent . hi . y > m_nwCorner . y ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if ' area ' overlaps our X extent
*/
bool CNavArea : : IsOverlappingX ( const CNavArea * area ) const
{
if ( area - > m_nwCorner . x < m_seCorner . x & & area - > m_seCorner . x > m_nwCorner . x )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if ' area ' overlaps our Y extent
*/
bool CNavArea : : IsOverlappingY ( const CNavArea * area ) const
{
if ( area - > m_nwCorner . y < m_seCorner . y & & area - > m_seCorner . y > m_nwCorner . y )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
class COverlapCheck
{
public :
COverlapCheck ( const CNavArea * me , const Vector & pos ) : m_pos ( pos )
{
m_me = me ;
m_myZ = me - > GetZ ( pos ) ;
}
bool operator ( ) ( CNavArea * area )
{
// skip self
if ( area = = m_me )
return true ;
// check 2D overlap
if ( ! area - > IsOverlapping ( m_pos ) )
return true ;
float theirZ = area - > GetZ ( m_pos ) ;
if ( theirZ > m_pos . z )
{
// they are above the point
return true ;
}
if ( theirZ > m_myZ )
{
// we are below an area that is beneath the given position
return false ;
}
return true ;
}
const CNavArea * m_me ;
float m_myZ ;
const Vector & m_pos ;
} ;
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given point is on or above this area , but no others
*/
bool CNavArea : : Contains ( const Vector & pos ) const
{
// check 2D overlap
if ( ! IsOverlapping ( pos ) )
return false ;
// the point overlaps us, check that it is above us, but not above any areas that overlap us
float myZ = GetZ ( pos ) ;
// if the nav area is above the given position, fail
// allow nav area to be as much as a step height above the given position
if ( myZ - StepHeight > pos . z )
return false ;
Extent areaExtent ;
GetExtent ( & areaExtent ) ;
COverlapCheck overlap ( this , pos ) ;
return TheNavMesh - > ForAllAreasOverlappingExtent ( overlap , areaExtent ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns true if area completely contains other area
*/
bool CNavArea : : Contains ( const CNavArea * area ) const
{
return ( ( m_nwCorner . x < = area - > m_nwCorner . x ) & & ( m_seCorner . x > = area - > m_seCorner . x ) & &
( m_nwCorner . y < = area - > m_nwCorner . y ) & & ( m_seCorner . y > = area - > m_seCorner . y ) & &
( m_nwCorner . z < = area - > m_nwCorner . z ) & & ( m_seCorner . z > = area - > m_seCorner . z ) ) ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : ComputeNormal ( Vector * normal , bool alternate ) const
{
if ( ! normal )
return ;
Vector u , v ;
if ( ! alternate )
{
u . x = m_seCorner . x - m_nwCorner . x ;
u . y = 0.0f ;
u . z = m_neZ - m_nwCorner . z ;
v . x = 0.0f ;
v . y = m_seCorner . y - m_nwCorner . y ;
v . z = m_swZ - m_nwCorner . z ;
}
else
{
u . x = m_nwCorner . x - m_seCorner . x ;
u . y = 0.0f ;
u . z = m_swZ - m_seCorner . z ;
v . x = 0.0f ;
v . y = m_nwCorner . y - m_seCorner . y ;
v . z = m_neZ - m_seCorner . z ;
}
* normal = CrossProduct ( u , v ) ;
normal - > NormalizeInPlace ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Removes all connections in directions to left and right of specified direction
*/
void CNavArea : : RemoveOrthogonalConnections ( NavDirType dir )
{
NavDirType dirToRemove [ 2 ] ;
dirToRemove [ 0 ] = DirectionLeft ( dir ) ;
dirToRemove [ 1 ] = DirectionRight ( dir ) ;
for ( int i = 0 ; i < 2 ; i + + )
{
dir = dirToRemove [ i ] ;
while ( GetAdjacentCount ( dir ) > 0 )
{
CNavArea * adj = GetAdjacentArea ( dir , 0 ) ;
Disconnect ( adj ) ;
adj - > Disconnect ( this ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if the area is approximately flat , using normals computed from opposite corners
*/
bool CNavArea : : IsFlat ( void ) const
{
Vector normal , otherNormal ;
ComputeNormal ( & normal ) ;
ComputeNormal ( & otherNormal , true ) ;
float tolerance = nav_coplanar_slope_limit . GetFloat ( ) ;
if ( ( m_node [ NORTH_WEST ] & & m_node [ NORTH_WEST ] - > IsOnDisplacement ( ) ) | |
( m_node [ NORTH_EAST ] & & m_node [ NORTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_EAST ] & & m_node [ SOUTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_WEST ] & & m_node [ SOUTH_WEST ] - > IsOnDisplacement ( ) ) )
{
tolerance = nav_coplanar_slope_limit_displacement . GetFloat ( ) ;
}
if ( DotProduct ( normal , otherNormal ) > tolerance )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this area and given area are approximately co - planar
*/
bool CNavArea : : IsCoplanar ( const CNavArea * area ) const
{
Vector u , v ;
bool isOnDisplacement = ( m_node [ NORTH_WEST ] & & m_node [ NORTH_WEST ] - > IsOnDisplacement ( ) ) | |
( m_node [ NORTH_EAST ] & & m_node [ NORTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_EAST ] & & m_node [ SOUTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_WEST ] & & m_node [ SOUTH_WEST ] - > IsOnDisplacement ( ) ) ;
if ( ! isOnDisplacement & & ! IsFlat ( ) )
return false ;
bool areaIsOnDisplacement = ( area - > m_node [ NORTH_WEST ] & & area - > m_node [ NORTH_WEST ] - > IsOnDisplacement ( ) ) | |
( area - > m_node [ NORTH_EAST ] & & area - > m_node [ NORTH_EAST ] - > IsOnDisplacement ( ) ) | |
( area - > m_node [ SOUTH_EAST ] & & area - > m_node [ SOUTH_EAST ] - > IsOnDisplacement ( ) ) | |
( area - > m_node [ SOUTH_WEST ] & & area - > m_node [ SOUTH_WEST ] - > IsOnDisplacement ( ) ) ;
if ( ! areaIsOnDisplacement & & ! area - > IsFlat ( ) )
return false ;
// compute our unit surface normal
Vector normal , otherNormal ;
ComputeNormal ( & normal ) ;
area - > ComputeNormal ( & otherNormal ) ;
// can only merge areas that are nearly planar, to ensure areas do not differ from underlying geometry much
float tolerance = nav_coplanar_slope_limit . GetFloat ( ) ;
if ( ( m_node [ NORTH_WEST ] & & m_node [ NORTH_WEST ] - > IsOnDisplacement ( ) ) | |
( m_node [ NORTH_EAST ] & & m_node [ NORTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_EAST ] & & m_node [ SOUTH_EAST ] - > IsOnDisplacement ( ) ) | |
( m_node [ SOUTH_WEST ] & & m_node [ SOUTH_WEST ] - > IsOnDisplacement ( ) ) )
{
tolerance = nav_coplanar_slope_limit_displacement . GetFloat ( ) ;
}
if ( DotProduct ( normal , otherNormal ) > tolerance )
return true ;
return false ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return Z of area at ( x , y ) of ' pos '
* Trilinear interpolation of Z values at quad edges .
* NOTE : pos - > z is not used .
*/
float CNavArea : : GetZ ( float x , float y ) const RESTRICT
{
// guard against division by zero due to degenerate areas
# ifdef _X360
// do the compare-against-zero on the integer unit to avoid a fcmp
// IEEE754 float positive zero is simply 0x00. There is also a
// floating-point negative zero (-0.0f == 0x80000000), but given
// how m_inv is computed earlier, that's not a possible value for
// it here, so we don't have to check for that.
//
// oddly, the compiler isn't smart enough to do this on its own
if ( * reinterpret_cast < const unsigned * > ( & m_invDxCorners ) = = 0 | |
* reinterpret_cast < const unsigned * > ( & m_invDyCorners ) = = 0 )
return m_neZ ;
# else
if ( m_invDxCorners = = 0.0f | | m_invDyCorners = = 0.0f )
return m_neZ ;
# endif
float u = ( x - m_nwCorner . x ) * m_invDxCorners ;
float v = ( y - m_nwCorner . y ) * m_invDyCorners ;
// clamp Z values to (x,y) volume
u = fsel ( u , u , 0 ) ; // u >= 0 ? u : 0
u = fsel ( u - 1.0f , 1.0f , u ) ; // u >= 1 ? 1 : u
v = fsel ( v , v , 0 ) ; // v >= 0 ? v : 0
v = fsel ( v - 1.0f , 1.0f , v ) ; // v >= 1 ? 1 : v
float northZ = m_nwCorner . z + u * ( m_neZ - m_nwCorner . z ) ;
float southZ = m_swZ + u * ( m_seCorner . z - m_swZ ) ;
return northZ + v * ( southZ - northZ ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return closest point to ' pos ' on ' area ' .
* Returned point is in ' close ' .
*/
void CNavArea : : GetClosestPointOnArea ( const Vector * RESTRICT pPos , Vector * close ) const RESTRICT
{
float x , y , z ;
// Using fsel rather than compares, as much faster on 360 [7/28/2008 tom]
x = fsel ( pPos - > x - m_nwCorner . x , pPos - > x , m_nwCorner . x ) ;
x = fsel ( x - m_seCorner . x , m_seCorner . x , x ) ;
y = fsel ( pPos - > y - m_nwCorner . y , pPos - > y , m_nwCorner . y ) ;
y = fsel ( y - m_seCorner . y , m_seCorner . y , y ) ;
z = GetZ ( x , y ) ;
close - > Init ( x , y , z ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return shortest distance squared between point and this area
*/
float CNavArea : : GetDistanceSquaredToPoint ( const Vector & pos ) const
{
if ( pos . x < m_nwCorner . x )
{
if ( pos . y < m_nwCorner . y )
{
// position is north-west of area
return ( m_nwCorner - pos ) . LengthSqr ( ) ;
}
else if ( pos . y > m_seCorner . y )
{
// position is south-west of area
Vector d ;
d . x = m_nwCorner . x - pos . x ;
d . y = m_seCorner . y - pos . y ;
d . z = m_swZ - pos . z ;
return d . LengthSqr ( ) ;
}
else
{
// position is west of area
float d = m_nwCorner . x - pos . x ;
return d * d ;
}
}
else if ( pos . x > m_seCorner . x )
{
if ( pos . y < m_nwCorner . y )
{
// position is north-east of area
Vector d ;
d . x = m_seCorner . x - pos . x ;
d . y = m_nwCorner . y - pos . y ;
d . z = m_neZ - pos . z ;
return d . LengthSqr ( ) ;
}
else if ( pos . y > m_seCorner . y )
{
// position is south-east of area
return ( m_seCorner - pos ) . LengthSqr ( ) ;
}
else
{
// position is east of area
float d = pos . x - m_seCorner . x ;
return d * d ;
}
}
else if ( pos . y < m_nwCorner . y )
{
// position is north of area
float d = m_nwCorner . y - pos . y ;
return d * d ;
}
else if ( pos . y > m_seCorner . y )
{
// position is south of area
float d = pos . y - m_seCorner . y ;
return d * d ;
}
else
{
// position is inside of 2D extent of area - find delta Z
float z = GetZ ( pos ) ;
float d = z - pos . z ;
return d * d ;
}
}
//--------------------------------------------------------------------------------------------------------------
CNavArea * CNavArea : : GetRandomAdjacentArea ( NavDirType dir ) const
{
int count = m_connect [ dir ] . Count ( ) ;
int which = RandomInt ( 0 , count - 1 ) ;
int i = 0 ;
FOR_EACH_VEC ( m_connect [ dir ] , it )
{
if ( i = = which )
return m_connect [ dir ] [ it ] . area ;
+ + i ;
}
return NULL ;
}
//--------------------------------------------------------------------------------------------------------------
// Build a vector of all adjacent areas
void CNavArea : : CollectAdjacentAreas ( CUtlVector < CNavArea * > * adjVector ) const
{
for ( int d = 0 ; d < NUM_DIRECTIONS ; + + d )
{
for ( int i = 0 ; i < m_connect [ d ] . Count ( ) ; + + i )
{
adjVector - > AddToTail ( m_connect [ d ] . Element ( i ) . area ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Compute " portal " between two adjacent areas .
* Return center of portal opening , and half - width defining sides of portal from center .
* NOTE : center - > z is unset .
*/
void CNavArea : : ComputePortal ( const CNavArea * to , NavDirType dir , Vector * center , float * halfWidth ) const
{
if ( dir = = NORTH | | dir = = SOUTH )
{
if ( dir = = NORTH )
{
center - > y = m_nwCorner . y ;
}
else
{
center - > y = m_seCorner . y ;
}
float left = MAX ( m_nwCorner . x , to - > m_nwCorner . x ) ;
float right = MIN ( m_seCorner . x , to - > m_seCorner . x ) ;
// clamp to our extent in case areas are disjoint
if ( left < m_nwCorner . x )
{
left = m_nwCorner . x ;
}
else if ( left > m_seCorner . x )
{
left = m_seCorner . x ;
}
if ( right < m_nwCorner . x )
{
right = m_nwCorner . x ;
}
else if ( right > m_seCorner . x )
{
right = m_seCorner . x ;
}
center - > x = ( left + right ) / 2.0f ;
* halfWidth = ( right - left ) / 2.0f ;
}
else // EAST or WEST
{
if ( dir = = WEST )
{
center - > x = m_nwCorner . x ;
}
else
{
center - > x = m_seCorner . x ;
}
float top = MAX ( m_nwCorner . y , to - > m_nwCorner . y ) ;
float bottom = MIN ( m_seCorner . y , to - > m_seCorner . y ) ;
// clamp to our extent in case areas are disjoint
if ( top < m_nwCorner . y )
{
top = m_nwCorner . y ;
}
else if ( top > m_seCorner . y )
{
top = m_seCorner . y ;
}
if ( bottom < m_nwCorner . y )
{
bottom = m_nwCorner . y ;
}
else if ( bottom > m_seCorner . y )
{
bottom = m_seCorner . y ;
}
center - > y = ( top + bottom ) / 2.0f ;
* halfWidth = ( bottom - top ) / 2.0f ;
}
center - > z = GetZ ( center - > x , center - > y ) ;
}
//--------------------------------------------------------------------------------------------------------------
// compute largest portal to adjacent area, returning direction
NavDirType CNavArea : : ComputeLargestPortal ( const CNavArea * to , Vector * center , float * halfWidth ) const
{
NavDirType bestDir = NUM_DIRECTIONS ;
Vector bestCenter ( vec3_origin ) ;
float bestHalfWidth = 0.0f ;
Vector centerDir = to - > GetCenter ( ) - GetCenter ( ) ;
for ( int i = 0 ; i < NUM_DIRECTIONS ; + + i )
{
NavDirType testDir = ( NavDirType ) i ;
Vector testCenter ;
float testHalfWidth ;
// Make sure we're not picking the opposite direction
switch ( testDir )
{
case NORTH : // -y
if ( centerDir . y > = 0.0f )
continue ;
break ;
case SOUTH : // +y
if ( centerDir . y < = 0.0f )
continue ;
break ;
case WEST : // -x
if ( centerDir . x > = 0.0f )
continue ;
break ;
case EAST : // +x
if ( centerDir . x < = 0.0f )
continue ;
break ;
}
ComputePortal ( to , testDir , & testCenter , & testHalfWidth ) ;
if ( testHalfWidth > bestHalfWidth )
{
bestDir = testDir ;
bestCenter = testCenter ;
bestHalfWidth = testHalfWidth ;
}
}
* center = bestCenter ;
* halfWidth = bestHalfWidth ;
return bestDir ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Compute closest point within the " portal " between to adjacent areas .
*/
void CNavArea : : ComputeClosestPointInPortal ( const CNavArea * to , NavDirType dir , const Vector & fromPos , Vector * closePos ) const
{
// const float margin = 0.0f; //GenerationStepSize/2.0f; // causes trouble with very small/narrow nav areas
const float margin = GenerationStepSize ;
if ( dir = = NORTH | | dir = = SOUTH )
{
if ( dir = = NORTH )
{
closePos - > y = m_nwCorner . y ;
}
else
{
closePos - > y = m_seCorner . y ;
}
float left = MAX ( m_nwCorner . x , to - > m_nwCorner . x ) ;
float right = MIN ( m_seCorner . x , to - > m_seCorner . x ) ;
// clamp to our extent in case areas are disjoint
// no good - need to push into to area for margins
/*
if ( left < m_nwCorner . x )
left = m_nwCorner . x ;
else if ( left > m_seCorner . x )
left = m_seCorner . x ;
if ( right < m_nwCorner . x )
right = m_nwCorner . x ;
else if ( right > m_seCorner . x )
right = m_seCorner . x ;
*/
// keep margin if against edge
/// @todo Need better check whether edge is outer edge or not - partial overlap is missed
float leftMargin = ( to - > IsEdge ( WEST ) ) ? ( left + margin ) : left ;
float rightMargin = ( to - > IsEdge ( EAST ) ) ? ( right - margin ) : right ;
// if area is narrow, margins may have crossed
if ( leftMargin > rightMargin )
{
// use midline
float mid = ( left + right ) / 2.0f ;
leftMargin = mid ;
rightMargin = mid ;
}
// limit x to within portal
if ( fromPos . x < leftMargin )
{
closePos - > x = leftMargin ;
}
else if ( fromPos . x > rightMargin )
{
closePos - > x = rightMargin ;
}
else
{
closePos - > x = fromPos . x ;
}
}
else // EAST or WEST
{
if ( dir = = WEST )
{
closePos - > x = m_nwCorner . x ;
}
else
{
closePos - > x = m_seCorner . x ;
}
float top = MAX ( m_nwCorner . y , to - > m_nwCorner . y ) ;
float bottom = MIN ( m_seCorner . y , to - > m_seCorner . y ) ;
// clamp to our extent in case areas are disjoint
// no good - need to push into to area for margins
/*
if ( top < m_nwCorner . y )
top = m_nwCorner . y ;
else if ( top > m_seCorner . y )
top = m_seCorner . y ;
if ( bottom < m_nwCorner . y )
bottom = m_nwCorner . y ;
else if ( bottom > m_seCorner . y )
bottom = m_seCorner . y ;
*/
// keep margin if against edge
float topMargin = ( to - > IsEdge ( NORTH ) ) ? ( top + margin ) : top ;
float bottomMargin = ( to - > IsEdge ( SOUTH ) ) ? ( bottom - margin ) : bottom ;
// if area is narrow, margins may have crossed
if ( topMargin > bottomMargin )
{
// use midline
float mid = ( top + bottom ) / 2.0f ;
topMargin = mid ;
bottomMargin = mid ;
}
// limit y to within portal
if ( fromPos . y < topMargin )
{
closePos - > y = topMargin ;
}
else if ( fromPos . y > bottomMargin )
{
closePos - > y = bottomMargin ;
}
else
{
closePos - > y = fromPos . y ;
}
}
closePos - > z = GetZ ( closePos - > x , closePos - > y ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if the given area and ' other ' share a colinear edge ( ie : no drop - down or step / jump / climb )
*/
bool CNavArea : : IsContiguous ( const CNavArea * other ) const
{
VPROF_BUDGET ( " CNavArea::IsContiguous " , " NextBot " ) ;
// find which side it is connected on
int dir ;
for ( dir = 0 ; dir < NUM_DIRECTIONS ; + + dir )
{
if ( IsConnected ( other , ( NavDirType ) dir ) )
break ;
}
if ( dir = = NUM_DIRECTIONS )
return false ;
Vector myEdge ;
float halfWidth ;
ComputePortal ( other , ( NavDirType ) dir , & myEdge , & halfWidth ) ;
Vector otherEdge ;
other - > ComputePortal ( this , OppositeDirection ( ( NavDirType ) dir ) , & otherEdge , & halfWidth ) ;
// must use stepheight because rough terrain can have gaps/cracks between adjacent nav areas
return ( myEdge - otherEdge ) . IsLengthLessThan ( StepHeight ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return height change between edges of adjacent nav areas ( not actual underlying ground )
*/
float CNavArea : : ComputeAdjacentConnectionHeightChange ( const CNavArea * destinationArea ) const
{
VPROF_BUDGET ( " CNavArea::ComputeAdjacentConnectionHeightChange " , " NextBot " ) ;
// find which side it is connected on
int dir ;
for ( dir = 0 ; dir < NUM_DIRECTIONS ; + + dir )
{
if ( IsConnected ( destinationArea , ( NavDirType ) dir ) )
break ;
}
if ( dir = = NUM_DIRECTIONS )
return FLT_MAX ;
Vector myEdge ;
float halfWidth ;
ComputePortal ( destinationArea , ( NavDirType ) dir , & myEdge , & halfWidth ) ;
Vector otherEdge ;
destinationArea - > ComputePortal ( this , OppositeDirection ( ( NavDirType ) dir ) , & otherEdge , & halfWidth ) ;
return otherEdge . z - myEdge . z ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if there are no bi - directional links on the given side
*/
bool CNavArea : : IsEdge ( NavDirType dir ) const
{
FOR_EACH_VEC ( m_connect [ dir ] , it )
{
const NavConnect connect = m_connect [ dir ] [ it ] ;
if ( connect . area - > IsConnected ( this , OppositeDirection ( dir ) ) )
return false ;
}
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return direction from this area to the given point
*/
NavDirType CNavArea : : ComputeDirection ( Vector * point ) const
{
if ( point - > x > = m_nwCorner . x & & point - > x < = m_seCorner . x )
{
if ( point - > y < m_nwCorner . y )
return NORTH ;
else if ( point - > y > m_seCorner . y )
return SOUTH ;
}
else if ( point - > y > = m_nwCorner . y & & point - > y < = m_seCorner . y )
{
if ( point - > x < m_nwCorner . x )
return WEST ;
else if ( point - > x > m_seCorner . x )
return EAST ;
}
// find closest direction
Vector to = * point - m_center ;
if ( fabs ( to . x ) > fabs ( to . y ) )
{
if ( to . x > 0.0f )
return EAST ;
return WEST ;
}
else
{
if ( to . y > 0.0f )
return SOUTH ;
return NORTH ;
}
return NUM_DIRECTIONS ;
}
//--------------------------------------------------------------------------------------------------------------
bool CNavArea : : GetCornerHotspot ( NavCornerType corner , Vector hotspot [ NUM_CORNERS ] ) const
{
Vector nw = GetCorner ( NORTH_WEST ) ;
Vector ne = GetCorner ( NORTH_EAST ) ;
Vector sw = GetCorner ( SOUTH_WEST ) ;
Vector se = GetCorner ( SOUTH_EAST ) ;
float size = 9.0f ;
size = MIN ( size , GetSizeX ( ) / 3 ) ; // make sure the hotspot doesn't extend outside small areas
size = MIN ( size , GetSizeY ( ) / 3 ) ;
switch ( corner )
{
case NORTH_WEST :
hotspot [ 0 ] = nw ;
hotspot [ 1 ] = hotspot [ 0 ] + Vector ( size , 0 , 0 ) ;
hotspot [ 2 ] = hotspot [ 0 ] + Vector ( size , size , 0 ) ;
hotspot [ 3 ] = hotspot [ 0 ] + Vector ( 0 , size , 0 ) ;
break ;
case NORTH_EAST :
hotspot [ 0 ] = ne ;
hotspot [ 1 ] = hotspot [ 0 ] + Vector ( - size , 0 , 0 ) ;
hotspot [ 2 ] = hotspot [ 0 ] + Vector ( - size , size , 0 ) ;
hotspot [ 3 ] = hotspot [ 0 ] + Vector ( 0 , size , 0 ) ;
break ;
case SOUTH_WEST :
hotspot [ 0 ] = sw ;
hotspot [ 1 ] = hotspot [ 0 ] + Vector ( size , 0 , 0 ) ;
hotspot [ 2 ] = hotspot [ 0 ] + Vector ( size , - size , 0 ) ;
hotspot [ 3 ] = hotspot [ 0 ] + Vector ( 0 , - size , 0 ) ;
break ;
case SOUTH_EAST :
hotspot [ 0 ] = se ;
hotspot [ 1 ] = hotspot [ 0 ] + Vector ( - size , 0 , 0 ) ;
hotspot [ 2 ] = hotspot [ 0 ] + Vector ( - size , - size , 0 ) ;
hotspot [ 3 ] = hotspot [ 0 ] + Vector ( 0 , - size , 0 ) ;
break ;
default :
return false ;
}
for ( int i = 1 ; i < NUM_CORNERS ; + + i )
{
hotspot [ i ] . z = GetZ ( hotspot [ i ] ) ;
}
Vector eyePos , eyeForward ;
TheNavMesh - > GetEditVectors ( & eyePos , & eyeForward ) ;
Ray_t ray ;
ray . Init ( eyePos , eyePos + 10000.0f * eyeForward , vec3_origin , vec3_origin ) ;
float dist = IntersectRayWithTriangle ( ray , hotspot [ 0 ] , hotspot [ 1 ] , hotspot [ 2 ] , false ) ;
if ( dist > 0 )
{
return true ;
}
dist = IntersectRayWithTriangle ( ray , hotspot [ 2 ] , hotspot [ 3 ] , hotspot [ 0 ] , false ) ;
if ( dist > 0 )
{
return true ;
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
NavCornerType CNavArea : : GetCornerUnderCursor ( void ) const
{
Vector eyePos , eyeForward ;
TheNavMesh - > GetEditVectors ( & eyePos , & eyeForward ) ;
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
Vector hotspot [ NUM_CORNERS ] ;
if ( GetCornerHotspot ( ( NavCornerType ) i , hotspot ) )
{
return ( NavCornerType ) i ;
}
}
return NUM_CORNERS ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Draw area for debugging
*/
void CNavArea : : Draw ( void ) const
{
NavEditColor color ;
bool useAttributeColors = true ;
const float DebugDuration = NDEBUG_PERSIST_TILL_NEXT_SERVER ;
if ( TheNavMesh - > IsEditMode ( CNavMesh : : PLACE_PAINTING ) )
{
useAttributeColors = false ;
if ( m_place = = UNDEFINED_PLACE )
{
color = NavNoPlaceColor ;
}
else if ( TheNavMesh - > GetNavPlace ( ) = = m_place )
{
color = NavSamePlaceColor ;
}
else
{
color = NavDifferentPlaceColor ;
}
}
else
{
// normal edit mode
if ( this = = TheNavMesh - > GetMarkedArea ( ) )
{
useAttributeColors = false ;
color = NavMarkedColor ;
}
else if ( this = = TheNavMesh - > GetSelectedArea ( ) )
{
color = NavSelectedColor ;
}
else
{
color = NavNormalColor ;
}
}
if ( IsDegenerate ( ) )
{
static IntervalTimer blink ;
static bool blinkOn = false ;
if ( blink . GetElapsedTime ( ) > 1.0f )
{
blink . Reset ( ) ;
blinkOn = ! blinkOn ;
}
useAttributeColors = false ;
if ( blinkOn )
color = NavDegenerateFirstColor ;
else
color = NavDegenerateSecondColor ;
NDebugOverlay : : Text ( GetCenter ( ) , UTIL_VarArgs ( " Degenerate area %d " , GetID ( ) ) , true , DebugDuration ) ;
}
Vector nw , ne , sw , se ;
nw = m_nwCorner ;
se = m_seCorner ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = m_neZ ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = m_swZ ;
if ( nav_show_light_intensity . GetBool ( ) )
{
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
Vector pos = GetCorner ( ( NavCornerType ) i ) ;
Vector end = pos ;
float lightIntensity = GetLightIntensity ( pos ) ;
end . z + = HumanHeight * lightIntensity ;
lightIntensity * = 255 ; // for color
NDebugOverlay : : Line ( end , pos , lightIntensity , lightIntensity , MAX ( 192 , lightIntensity ) , true , DebugDuration ) ;
}
}
int bgcolor [ 4 ] ;
if ( 4 = = sscanf ( nav_area_bgcolor . GetString ( ) , " %d %d %d %d " , & ( bgcolor [ 0 ] ) , & ( bgcolor [ 1 ] ) , & ( bgcolor [ 2 ] ) , & ( bgcolor [ 3 ] ) ) )
{
for ( int i = 0 ; i < 4 ; + + i )
bgcolor [ i ] = clamp ( bgcolor [ i ] , 0 , 255 ) ;
if ( bgcolor [ 3 ] > 0 )
{
const Vector offset ( 0 , 0 , 0.8f ) ;
NDebugOverlay : : Triangle ( nw + offset , se + offset , ne + offset , bgcolor [ 0 ] , bgcolor [ 1 ] , bgcolor [ 2 ] , bgcolor [ 3 ] , true , DebugDuration ) ;
NDebugOverlay : : Triangle ( se + offset , nw + offset , sw + offset , bgcolor [ 0 ] , bgcolor [ 1 ] , bgcolor [ 2 ] , bgcolor [ 3 ] , true , DebugDuration ) ;
}
}
const float inset = 0.2f ;
nw . x + = inset ;
nw . y + = inset ;
ne . x - = inset ;
ne . y + = inset ;
sw . x + = inset ;
sw . y - = inset ;
se . x - = inset ;
se . y - = inset ;
if ( GetAttributes ( ) & NAV_MESH_TRANSIENT )
{
NavDrawDashedLine ( nw , ne , color ) ;
NavDrawDashedLine ( ne , se , color ) ;
NavDrawDashedLine ( se , sw , color ) ;
NavDrawDashedLine ( sw , nw , color ) ;
}
else
{
NavDrawLine ( nw , ne , color ) ;
NavDrawLine ( ne , se , color ) ;
NavDrawLine ( se , sw , color ) ;
NavDrawLine ( sw , nw , color ) ;
}
if ( this = = TheNavMesh - > GetMarkedArea ( ) & & TheNavMesh - > m_markedCorner ! = NUM_CORNERS )
{
Vector p [ NUM_CORNERS ] ;
GetCornerHotspot ( TheNavMesh - > m_markedCorner , p ) ;
NavDrawLine ( p [ 1 ] , p [ 2 ] , NavMarkedColor ) ;
NavDrawLine ( p [ 2 ] , p [ 3 ] , NavMarkedColor ) ;
}
if ( this ! = TheNavMesh - > GetMarkedArea ( ) & & this = = TheNavMesh - > GetSelectedArea ( ) & & TheNavMesh - > IsEditMode ( CNavMesh : : NORMAL ) )
{
NavCornerType bestCorner = GetCornerUnderCursor ( ) ;
Vector p [ NUM_CORNERS ] ;
if ( GetCornerHotspot ( bestCorner , p ) )
{
NavDrawLine ( p [ 1 ] , p [ 2 ] , NavSelectedColor ) ;
NavDrawLine ( p [ 2 ] , p [ 3 ] , NavSelectedColor ) ;
}
}
if ( GetAttributes ( ) & NAV_MESH_CROUCH )
{
if ( useAttributeColors )
color = NavAttributeCrouchColor ;
NavDrawLine ( nw , se , color ) ;
}
if ( GetAttributes ( ) & NAV_MESH_JUMP )
{
if ( useAttributeColors )
color = NavAttributeJumpColor ;
if ( ! ( GetAttributes ( ) & NAV_MESH_CROUCH ) )
{
NavDrawLine ( nw , se , color ) ;
}
NavDrawLine ( ne , sw , color ) ;
}
if ( GetAttributes ( ) & NAV_MESH_PRECISE )
{
if ( useAttributeColors )
color = NavAttributePreciseColor ;
float size = 8.0f ;
Vector up ( m_center . x , m_center . y - size , m_center . z ) ;
Vector down ( m_center . x , m_center . y + size , m_center . z ) ;
NavDrawLine ( up , down , color ) ;
Vector left ( m_center . x - size , m_center . y , m_center . z ) ;
Vector right ( m_center . x + size , m_center . y , m_center . z ) ;
NavDrawLine ( left , right , color ) ;
}
if ( GetAttributes ( ) & NAV_MESH_NO_JUMP )
{
if ( useAttributeColors )
color = NavAttributeNoJumpColor ;
float size = 8.0f ;
Vector up ( m_center . x , m_center . y - size , m_center . z ) ;
Vector down ( m_center . x , m_center . y + size , m_center . z ) ;
Vector left ( m_center . x - size , m_center . y , m_center . z ) ;
Vector right ( m_center . x + size , m_center . y , m_center . z ) ;
NavDrawLine ( up , right , color ) ;
NavDrawLine ( right , down , color ) ;
NavDrawLine ( down , left , color ) ;
NavDrawLine ( left , up , color ) ;
}
if ( GetAttributes ( ) & NAV_MESH_STAIRS )
{
if ( useAttributeColors )
color = NavAttributeStairColor ;
float northZ = ( GetCorner ( NORTH_WEST ) . z + GetCorner ( NORTH_EAST ) . z ) / 2.0f ;
float southZ = ( GetCorner ( SOUTH_WEST ) . z + GetCorner ( SOUTH_EAST ) . z ) / 2.0f ;
float westZ = ( GetCorner ( NORTH_WEST ) . z + GetCorner ( SOUTH_WEST ) . z ) / 2.0f ;
float eastZ = ( GetCorner ( NORTH_EAST ) . z + GetCorner ( SOUTH_EAST ) . z ) / 2.0f ;
float deltaEastWest = abs ( westZ - eastZ ) ;
float deltaNorthSouth = abs ( northZ - southZ ) ;
float stepSize = StepHeight / 2.0f ;
float t ;
if ( deltaEastWest > deltaNorthSouth )
{
float inc = stepSize / GetSizeX ( ) ;
for ( t = 0.0f ; t < = 1.0f ; t + = inc )
{
float x = m_nwCorner . x + t * GetSizeX ( ) ;
NavDrawLine ( Vector ( x , m_nwCorner . y , GetZ ( x , m_nwCorner . y ) ) ,
Vector ( x , m_seCorner . y , GetZ ( x , m_seCorner . y ) ) ,
color ) ;
}
}
else
{
float inc = stepSize / GetSizeY ( ) ;
for ( t = 0.0f ; t < = 1.0f ; t + = inc )
{
float y = m_nwCorner . y + t * GetSizeY ( ) ;
NavDrawLine ( Vector ( m_nwCorner . x , y , GetZ ( m_nwCorner . x , y ) ) ,
Vector ( m_seCorner . x , y , GetZ ( m_seCorner . x , y ) ) ,
color ) ;
}
}
}
// Stop is represented by an octagon
if ( GetAttributes ( ) & NAV_MESH_STOP )
{
if ( useAttributeColors )
color = NavAttributeStopColor ;
float dist = 8.0f ;
float length = dist / 2.5f ;
Vector start , end ;
start = m_center + Vector ( dist , - length , 0 ) ;
end = m_center + Vector ( dist , length , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( dist , length , 0 ) ;
end = m_center + Vector ( length , dist , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( - dist , - length , 0 ) ;
end = m_center + Vector ( - dist , length , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( - dist , length , 0 ) ;
end = m_center + Vector ( - length , dist , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( - length , dist , 0 ) ;
end = m_center + Vector ( length , dist , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( - dist , - length , 0 ) ;
end = m_center + Vector ( - length , - dist , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( - length , - dist , 0 ) ;
end = m_center + Vector ( length , - dist , 0 ) ;
NavDrawLine ( start , end , color ) ;
start = m_center + Vector ( length , - dist , 0 ) ;
end = m_center + Vector ( dist , - length , 0 ) ;
NavDrawLine ( start , end , color ) ;
}
// Walk is represented by an arrow
if ( GetAttributes ( ) & NAV_MESH_WALK )
{
if ( useAttributeColors )
color = NavAttributeWalkColor ;
float size = 8.0f ;
NavDrawHorizontalArrow ( m_center + Vector ( - size , 0 , 0 ) , m_center + Vector ( size , 0 , 0 ) , 4 , color ) ;
}
// Walk is represented by a double arrow
if ( GetAttributes ( ) & NAV_MESH_RUN )
{
if ( useAttributeColors )
color = NavAttributeRunColor ;
float size = 8.0f ;
float dist = 4.0f ;
NavDrawHorizontalArrow ( m_center + Vector ( - size , dist , 0 ) , m_center + Vector ( size , dist , 0 ) , 4 , color ) ;
NavDrawHorizontalArrow ( m_center + Vector ( - size , - dist , 0 ) , m_center + Vector ( size , - dist , 0 ) , 4 , color ) ;
}
// Avoid is represented by an exclamation point
if ( GetAttributes ( ) & NAV_MESH_AVOID )
{
if ( useAttributeColors )
color = NavAttributeAvoidColor ;
float topHeight = 8.0f ;
float topWidth = 3.0f ;
float bottomHeight = 3.0f ;
float bottomWidth = 2.0f ;
NavDrawTriangle ( m_center , m_center + Vector ( - topWidth , topHeight , 0 ) , m_center + Vector ( + topWidth , topHeight , 0 ) , color ) ;
NavDrawTriangle ( m_center + Vector ( 0 , - bottomHeight , 0 ) , m_center + Vector ( - bottomWidth , - bottomHeight * 2 , 0 ) , m_center + Vector ( bottomWidth , - bottomHeight * 2 , 0 ) , color ) ;
}
if ( IsBlocked ( TEAM_ANY ) | | HasAvoidanceObstacle ( ) | | IsDamaging ( ) )
{
NavEditColor color = ( IsBlocked ( TEAM_ANY ) & & ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) ) ? NavBlockedByFuncNavBlockerColor : NavBlockedByDoorColor ;
const float blockedInset = 4.0f ;
nw . x + = blockedInset ;
nw . y + = blockedInset ;
ne . x - = blockedInset ;
ne . y + = blockedInset ;
sw . x + = blockedInset ;
sw . y - = blockedInset ;
se . x - = blockedInset ;
se . y - = blockedInset ;
NavDrawLine ( nw , ne , color ) ;
NavDrawLine ( ne , se , color ) ;
NavDrawLine ( se , sw , color ) ;
NavDrawLine ( sw , nw , color ) ;
}
}
//--------------------------------------------------------------------------------------------------------
/**
* Draw area as a filled rect of the given color
*/
void CNavArea : : DrawFilled ( int r , int g , int b , int a , float deltaT , bool noDepthTest , float margin ) const
{
Vector nw = GetCorner ( NORTH_WEST ) + Vector ( margin , margin , 0.0f ) ;
Vector ne = GetCorner ( NORTH_EAST ) + Vector ( - margin , margin , 0.0f ) ;
Vector sw = GetCorner ( SOUTH_WEST ) + Vector ( margin , - margin , 0.0f ) ;
Vector se = GetCorner ( SOUTH_EAST ) + Vector ( - margin , - margin , 0.0f ) ;
if ( a = = 0 )
{
NDebugOverlay : : Line ( nw , ne , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( nw , sw , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( sw , se , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( se , ne , r , g , b , true , deltaT ) ;
}
else
{
NDebugOverlay : : Triangle ( nw , se , ne , r , g , b , a , noDepthTest , deltaT ) ;
NDebugOverlay : : Triangle ( se , nw , sw , r , g , b , a , noDepthTest , deltaT ) ;
}
// backside
// NDebugOverlay::Triangle( nw, ne, se, r, g, b, a, noDepthTest, deltaT );
// NDebugOverlay::Triangle( se, sw, nw, r, g, b, a, noDepthTest, deltaT );
}
//--------------------------------------------------------------------------------------------------------
void CNavArea : : DrawSelectedSet ( const Vector & shift ) const
{
const float deltaT = NDEBUG_PERSIST_TILL_NEXT_SERVER ;
int r = s_selectedSetColor . r ( ) ;
int g = s_selectedSetColor . g ( ) ;
int b = s_selectedSetColor . b ( ) ;
int a = s_selectedSetColor . a ( ) ;
Vector nw = GetCorner ( NORTH_WEST ) + shift ;
Vector ne = GetCorner ( NORTH_EAST ) + shift ;
Vector sw = GetCorner ( SOUTH_WEST ) + shift ;
Vector se = GetCorner ( SOUTH_EAST ) + shift ;
NDebugOverlay : : Triangle ( nw , se , ne , r , g , b , a , true , deltaT ) ;
NDebugOverlay : : Triangle ( se , nw , sw , r , g , b , a , true , deltaT ) ;
r = s_selectedSetBorderColor . r ( ) ;
g = s_selectedSetBorderColor . g ( ) ;
b = s_selectedSetBorderColor . b ( ) ;
NDebugOverlay : : Line ( nw , ne , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( nw , sw , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( sw , se , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( se , ne , r , g , b , true , deltaT ) ;
}
//--------------------------------------------------------------------------------------------------------
void CNavArea : : DrawDragSelectionSet ( Color & dragSelectionSetColor ) const
{
const float deltaT = NDEBUG_PERSIST_TILL_NEXT_SERVER ;
int r = dragSelectionSetColor . r ( ) ;
int g = dragSelectionSetColor . g ( ) ;
int b = dragSelectionSetColor . b ( ) ;
int a = dragSelectionSetColor . a ( ) ;
Vector nw = GetCorner ( NORTH_WEST ) ;
Vector ne = GetCorner ( NORTH_EAST ) ;
Vector sw = GetCorner ( SOUTH_WEST ) ;
Vector se = GetCorner ( SOUTH_EAST ) ;
NDebugOverlay : : Triangle ( nw , se , ne , r , g , b , a , true , deltaT ) ;
NDebugOverlay : : Triangle ( se , nw , sw , r , g , b , a , true , deltaT ) ;
r = s_dragSelectionSetBorderColor . r ( ) ;
g = s_dragSelectionSetBorderColor . g ( ) ;
b = s_dragSelectionSetBorderColor . b ( ) ;
NDebugOverlay : : Line ( nw , ne , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( nw , sw , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( sw , se , r , g , b , true , deltaT ) ;
NDebugOverlay : : Line ( se , ne , r , g , b , true , deltaT ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Draw navigation areas and edit them
*/
void CNavArea : : DrawHidingSpots ( void ) const
{
const HidingSpotVector * hidingSpots = GetHidingSpots ( ) ;
FOR_EACH_VEC ( ( * hidingSpots ) , it )
{
const HidingSpot * spot = ( * hidingSpots ) [ it ] ;
NavEditColor color ;
if ( spot - > IsIdealSniperSpot ( ) )
{
color = NavIdealSniperColor ;
}
else if ( spot - > IsGoodSniperSpot ( ) )
{
color = NavGoodSniperColor ;
}
else if ( spot - > HasGoodCover ( ) )
{
color = NavGoodCoverColor ;
}
else
{
color = NavExposedColor ;
}
NavDrawLine ( spot - > GetPosition ( ) , spot - > GetPosition ( ) + Vector ( 0 , 0 , 50 ) , color ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Draw ourselves and adjacent areas
*/
void CNavArea : : DrawConnectedAreas ( void ) const
{
int i ;
CBasePlayer * player = UTIL_GetListenServerHost ( ) ;
if ( player = = NULL )
return ;
// draw self
if ( TheNavMesh - > IsEditMode ( CNavMesh : : PLACE_PAINTING ) )
{
Draw ( ) ;
}
else
{
Draw ( ) ;
DrawHidingSpots ( ) ;
}
// draw connected ladders
{
FOR_EACH_VEC ( m_ladder [ CNavLadder : : LADDER_UP ] , it )
{
CNavLadder * ladder = m_ladder [ CNavLadder : : LADDER_UP ] [ it ] . ladder ;
ladder - > DrawLadder ( ) ;
if ( ! ladder - > IsConnected ( this , CNavLadder : : LADDER_DOWN ) )
{
NavDrawLine ( m_center , ladder - > m_bottom + Vector ( 0 , 0 , GenerationStepSize ) , NavConnectedOneWayColor ) ;
}
}
}
{
FOR_EACH_VEC ( m_ladder [ CNavLadder : : LADDER_DOWN ] , it )
{
CNavLadder * ladder = m_ladder [ CNavLadder : : LADDER_DOWN ] [ it ] . ladder ;
ladder - > DrawLadder ( ) ;
if ( ! ladder - > IsConnected ( this , CNavLadder : : LADDER_UP ) )
{
NavDrawLine ( m_center , ladder - > m_top , NavConnectedOneWayColor ) ;
}
}
}
// draw connected areas
for ( i = 0 ; i < NUM_DIRECTIONS ; + + i )
{
NavDirType dir = ( NavDirType ) i ;
int count = GetAdjacentCount ( dir ) ;
for ( int a = 0 ; a < count ; + + a )
{
CNavArea * adj = GetAdjacentArea ( dir , a ) ;
adj - > Draw ( ) ;
if ( ! TheNavMesh - > IsEditMode ( CNavMesh : : PLACE_PAINTING ) )
{
adj - > DrawHidingSpots ( ) ;
Vector from , to ;
Vector hookPos ;
float halfWidth ;
float size = 5.0f ;
ComputePortal ( adj , dir , & hookPos , & halfWidth ) ;
switch ( dir )
{
case NORTH :
from = hookPos + Vector ( 0.0f , size , 0.0f ) ;
to = hookPos + Vector ( 0.0f , - size , 0.0f ) ;
break ;
case SOUTH :
from = hookPos + Vector ( 0.0f , - size , 0.0f ) ;
to = hookPos + Vector ( 0.0f , size , 0.0f ) ;
break ;
case EAST :
from = hookPos + Vector ( - size , 0.0f , 0.0f ) ;
to = hookPos + Vector ( + size , 0.0f , 0.0f ) ;
break ;
case WEST :
from = hookPos + Vector ( size , 0.0f , 0.0f ) ;
to = hookPos + Vector ( - size , 0.0f , 0.0f ) ;
break ;
}
from . z = GetZ ( from ) ;
to . z = adj - > GetZ ( to ) ;
Vector drawTo ;
adj - > GetClosestPointOnArea ( to , & drawTo ) ;
if ( nav_show_contiguous . GetBool ( ) )
{
if ( IsContiguous ( adj ) )
NavDrawLine ( from , drawTo , NavConnectedContiguous ) ;
else
NavDrawLine ( from , drawTo , NavConnectedNonContiguous ) ;
}
else
{
if ( adj - > IsConnected ( this , OppositeDirection ( dir ) ) )
NavDrawLine ( from , drawTo , NavConnectedTwoWaysColor ) ;
else
NavDrawLine ( from , drawTo , NavConnectedOneWayColor ) ;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add to open list in decreasing value order
*/
void CNavArea : : AddToOpenList ( void )
{
Assert ( ( m_openList & & m_openList - > m_prevOpen = = NULL ) | | m_openList = = NULL ) ;
if ( IsOpen ( ) )
{
// already on list
return ;
}
// mark as being on open list for quick check
m_openMarker = m_masterMarker ;
// if list is empty, add and return
if ( m_openList = = NULL )
{
m_openList = this ;
m_openListTail = this ;
this - > m_prevOpen = NULL ;
this - > m_nextOpen = NULL ;
return ;
}
// insert self in ascending cost order
// Since costs are positive, IEEE754 let's us compare as integers (see http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm)
CNavArea * area , * last = NULL ;
int thisCostBits = * reinterpret_cast < const int * > ( & m_totalCost ) ;
Assert ( m_totalCost > = 0.0f ) ;
for ( area = m_openList ; area ; area = area - > m_nextOpen )
{
Assert ( area - > GetTotalCost ( ) > = 0.0f ) ;
int thoseCostBits = * reinterpret_cast < const int * > ( & area - > m_totalCost ) ;
if ( thisCostBits < thoseCostBits )
{
break ;
}
last = area ;
}
if ( area )
{
// insert before this area
this - > m_prevOpen = area - > m_prevOpen ;
if ( this - > m_prevOpen )
{
this - > m_prevOpen - > m_nextOpen = this ;
}
else
{
m_openList = this ;
}
this - > m_nextOpen = area ;
area - > m_prevOpen = this ;
}
else
{
// append to end of list
last - > m_nextOpen = this ;
this - > m_prevOpen = last ;
this - > m_nextOpen = NULL ;
m_openListTail = this ;
}
Assert ( ( m_openList & & m_openList - > m_prevOpen = = NULL ) | | m_openList = = NULL ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add to tail of the open list
*/
void CNavArea : : AddToOpenListTail ( void )
{
Assert ( ( m_openList & & m_openList - > m_prevOpen = = NULL ) | | m_openList = = NULL ) ;
if ( IsOpen ( ) )
{
// already on list
return ;
}
// mark as being on open list for quick check
m_openMarker = m_masterMarker ;
// if list is empty, add and return
if ( m_openList = = NULL )
{
m_openList = this ;
m_openListTail = this ;
this - > m_prevOpen = NULL ;
this - > m_nextOpen = NULL ;
Assert ( ( m_openList & & m_openList - > m_prevOpen = = NULL ) | | m_openList = = NULL ) ;
return ;
}
// append to end of list
m_openListTail - > m_nextOpen = this ;
this - > m_prevOpen = m_openListTail ;
this - > m_nextOpen = NULL ;
m_openListTail = this ;
Assert ( ( m_openList & & m_openList - > m_prevOpen = = NULL ) | | m_openList = = NULL ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* A smaller value has been found , update this area on the open list
* @ todo " bubbling " does unnecessary work , since the order of all other nodes will be unchanged - only this node is altered
*/
void CNavArea : : UpdateOnOpenList ( void )
{
// since value can only decrease, bubble this area up from current spot
while ( m_prevOpen & & this - > GetTotalCost ( ) < m_prevOpen - > GetTotalCost ( ) )
{
// swap position with predecessor
CNavArea * other = m_prevOpen ;
CNavArea * before = other - > m_prevOpen ;
CNavArea * after = this - > m_nextOpen ;
this - > m_nextOpen = other ;
this - > m_prevOpen = before ;
other - > m_prevOpen = this ;
other - > m_nextOpen = after ;
if ( before )
{
before - > m_nextOpen = this ;
}
else
{
m_openList = this ;
}
if ( after )
{
after - > m_prevOpen = other ;
}
else
{
m_openListTail = this ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : RemoveFromOpenList ( void )
{
if ( m_openMarker = = 0 )
{
// not on the list
return ;
}
if ( m_prevOpen )
{
m_prevOpen - > m_nextOpen = m_nextOpen ;
}
else
{
m_openList = m_nextOpen ;
}
if ( m_nextOpen )
{
m_nextOpen - > m_prevOpen = m_prevOpen ;
}
else
{
m_openListTail = m_prevOpen ;
}
// zero is an invalid marker
m_openMarker = 0 ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Clears the open and closed lists for a new search
*/
void CNavArea : : ClearSearchLists ( void )
{
// effectively clears all open list pointers and closed flags
CNavArea : : MakeNewMarker ( ) ;
m_openList = NULL ;
m_openListTail = NULL ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : SetCorner ( NavCornerType corner , const Vector & newPosition )
{
switch ( corner )
{
case NORTH_WEST :
m_nwCorner = newPosition ;
break ;
case NORTH_EAST :
m_seCorner . x = newPosition . x ;
m_nwCorner . y = newPosition . y ;
m_neZ = newPosition . z ;
break ;
case SOUTH_WEST :
m_nwCorner . x = newPosition . x ;
m_seCorner . y = newPosition . y ;
m_swZ = newPosition . z ;
break ;
case SOUTH_EAST :
m_seCorner = newPosition ;
break ;
default :
{
Vector oldPosition = GetCenter ( ) ;
Vector delta = newPosition - oldPosition ;
m_nwCorner + = delta ;
m_seCorner + = delta ;
m_neZ + = delta . z ;
m_swZ + = delta . z ;
}
}
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
CalcDebugID ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns true if an existing hiding spot is too close to given position
*/
bool CNavArea : : IsHidingSpotCollision ( const Vector & pos ) const
{
const float collisionRange = 30.0f ;
FOR_EACH_VEC ( m_hidingSpots , it )
{
const HidingSpot * spot = m_hidingSpots [ it ] ;
if ( ( spot - > GetPosition ( ) - pos ) . IsLengthLessThan ( collisionRange ) )
return true ;
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
bool IsHidingSpotInCover ( const Vector & spot )
{
int coverCount = 0 ;
trace_t result ;
Vector from = spot ;
from . z + = HalfHumanHeight ;
Vector to ;
// if we are crouched underneath something, that counts as good cover
to = from + Vector ( 0 , 0 , 20.0f ) ;
UTIL_TraceLine ( from , to , MASK_NPCSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f )
return true ;
const float coverRange = 100.0f ;
const float inc = M_PI / 8.0f ;
for ( float angle = 0.0f ; angle < 2.0f * M_PI ; angle + = inc )
{
to = from + Vector ( coverRange * ( float ) cos ( angle ) , coverRange * ( float ) sin ( angle ) , HalfHumanHeight ) ;
UTIL_TraceLine ( from , to , MASK_NPCSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
// if traceline hit something, it hit "cover"
if ( result . fraction ! = 1.0f )
+ + coverCount ;
}
// if more than half of the circle has no cover, the spot is not "in cover"
const int halfCover = 8 ;
if ( coverCount < halfCover )
return false ;
return true ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Finds the hiding spot position in a corner ' s area . If the typical inset is off the nav area ( small
* hand - constructed areas ) , it tries to fit the position inside the area .
*/
static Vector FindPositionInArea ( CNavArea * area , NavCornerType corner )
{
int multX = 1 , multY = 1 ;
switch ( corner )
{
case NORTH_WEST :
break ;
case NORTH_EAST :
multX = - 1 ;
break ;
case SOUTH_WEST :
multY = - 1 ;
break ;
case SOUTH_EAST :
multX = - 1 ;
multY = - 1 ;
break ;
}
const float offset = 12.5f ;
Vector cornerPos = area - > GetCorner ( corner ) ;
// Try the basic inset
Vector pos = cornerPos + Vector ( offset * multX , offset * multY , 0.0f ) ;
if ( ! area - > IsOverlapping ( pos ) )
{
// Try pulling the Y offset to the area's center
pos = cornerPos + Vector ( offset * multX , area - > GetSizeY ( ) * 0.5f * multY , 0.0f ) ;
if ( ! area - > IsOverlapping ( pos ) )
{
// Try pulling the X offset to the area's center
pos = cornerPos + Vector ( area - > GetSizeX ( ) * 0.5f * multX , offset * multY , 0.0f ) ;
if ( ! area - > IsOverlapping ( pos ) )
{
// Try pulling the X and Y offsets to the area's center
pos = cornerPos + Vector ( area - > GetSizeX ( ) * 0.5f * multX , area - > GetSizeY ( ) * 0.5f * multY , 0.0f ) ;
if ( ! area - > IsOverlapping ( pos ) )
{
AssertMsg ( false , UTIL_VarArgs ( " A Hiding Spot can't be placed on its area at (%.0f %.0f %.0f) " , cornerPos . x , cornerPos . y , cornerPos . z ) ) ;
// Just pull the position to a small offset
pos = cornerPos + Vector ( 1.0f * multX , 1.0f * multY , 0.0f ) ;
if ( ! area - > IsOverlapping ( pos ) )
{
// Nothing is working (degenerate area?), so just put it directly on the corner
pos = cornerPos ;
}
}
}
}
}
return pos ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Analyze local area neighborhood to find " hiding spots " for this area
*/
void CNavArea : : ComputeHidingSpots ( void )
{
struct
{
float lo , hi ;
}
extent ;
m_hidingSpots . PurgeAndDeleteElements ( ) ;
// "jump areas" cannot have hiding spots
if ( GetAttributes ( ) & NAV_MESH_JUMP )
return ;
// "don't hide areas" cannot have hiding spots
if ( GetAttributes ( ) & NAV_MESH_DONT_HIDE )
return ;
int cornerCount [ NUM_CORNERS ] ;
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
cornerCount [ i ] = 0 ;
const float cornerSize = 20.0f ;
// for each direction, find extents of adjacent areas along the wall
for ( int d = 0 ; d < NUM_DIRECTIONS ; + + d )
{
extent . lo = 999999.9f ;
extent . hi = - 999999.9f ;
bool isHoriz = ( d = = NORTH | | d = = SOUTH ) ? true : false ;
FOR_EACH_VEC ( m_connect [ d ] , it )
{
NavConnect connect = m_connect [ d ] [ it ] ;
// if connection is only one-way, it's a "jump down" connection (ie: a discontinuity that may mean cover)
// ignore it
if ( connect . area - > IsConnected ( this , OppositeDirection ( static_cast < NavDirType > ( d ) ) ) = = false )
continue ;
// ignore jump areas
if ( connect . area - > GetAttributes ( ) & NAV_MESH_JUMP )
continue ;
if ( isHoriz )
{
if ( connect . area - > m_nwCorner . x < extent . lo )
extent . lo = connect . area - > m_nwCorner . x ;
if ( connect . area - > m_seCorner . x > extent . hi )
extent . hi = connect . area - > m_seCorner . x ;
}
else
{
if ( connect . area - > m_nwCorner . y < extent . lo )
extent . lo = connect . area - > m_nwCorner . y ;
if ( connect . area - > m_seCorner . y > extent . hi )
extent . hi = connect . area - > m_seCorner . y ;
}
}
switch ( d )
{
case NORTH :
if ( extent . lo - m_nwCorner . x > = cornerSize )
+ + cornerCount [ NORTH_WEST ] ;
if ( m_seCorner . x - extent . hi > = cornerSize )
+ + cornerCount [ NORTH_EAST ] ;
break ;
case SOUTH :
if ( extent . lo - m_nwCorner . x > = cornerSize )
+ + cornerCount [ SOUTH_WEST ] ;
if ( m_seCorner . x - extent . hi > = cornerSize )
+ + cornerCount [ SOUTH_EAST ] ;
break ;
case EAST :
if ( extent . lo - m_nwCorner . y > = cornerSize )
+ + cornerCount [ NORTH_EAST ] ;
if ( m_seCorner . y - extent . hi > = cornerSize )
+ + cornerCount [ SOUTH_EAST ] ;
break ;
case WEST :
if ( extent . lo - m_nwCorner . y > = cornerSize )
+ + cornerCount [ NORTH_WEST ] ;
if ( m_seCorner . y - extent . hi > = cornerSize )
+ + cornerCount [ SOUTH_WEST ] ;
break ;
}
}
for ( int c = 0 ; c < NUM_CORNERS ; + + c )
{
// if a corner count is 2, then it really is a corner (walls on both sides)
if ( cornerCount [ c ] = = 2 )
{
Vector pos = FindPositionInArea ( this , ( NavCornerType ) c ) ;
if ( ! c | | ! IsHidingSpotCollision ( pos ) )
{
HidingSpot * spot = TheNavMesh - > CreateHidingSpot ( ) ;
spot - > SetPosition ( pos ) ;
spot - > SetFlags ( IsHidingSpotInCover ( pos ) ? HidingSpot : : IN_COVER : HidingSpot : : EXPOSED ) ;
m_hidingSpots . AddToTail ( spot ) ;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine how much walkable area we can see from the spot , and how far away we can see .
*/
void ClassifySniperSpot ( HidingSpot * spot )
{
Vector eye = spot - > GetPosition ( ) ;
CNavArea * hidingArea = TheNavMesh - > GetNavArea ( spot - > GetPosition ( ) ) ;
if ( hidingArea & & ( hidingArea - > GetAttributes ( ) & NAV_MESH_STAND ) )
{
// we will be standing at this hiding spot
eye . z + = HumanEyeHeight ;
}
else
{
// we are crouching when at this hiding spot
eye . z + = HumanCrouchEyeHeight ;
}
Vector walkable ;
trace_t result ;
Extent sniperExtent ;
float farthestRangeSq = 0.0f ;
const float minSniperRangeSq = 1000.0f * 1000.0f ;
bool found = false ;
// to make compiler stop warning me
sniperExtent . lo = Vector ( 0.0f , 0.0f , 0.0f ) ;
sniperExtent . hi = Vector ( 0.0f , 0.0f , 0.0f ) ;
Extent areaExtent ;
FOR_EACH_VEC ( TheNavAreas , it )
{
CNavArea * area = TheNavAreas [ it ] ;
area - > GetExtent ( & areaExtent ) ;
// scan this area
for ( walkable . y = areaExtent . lo . y + GenerationStepSize / 2.0f ; walkable . y < areaExtent . hi . y ; walkable . y + = GenerationStepSize )
{
for ( walkable . x = areaExtent . lo . x + GenerationStepSize / 2.0f ; walkable . x < areaExtent . hi . x ; walkable . x + = GenerationStepSize )
{
walkable . z = area - > GetZ ( walkable ) + HalfHumanHeight ;
// check line of sight
UTIL_TraceLine ( eye , walkable , CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_PLAYERCLIP , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction = = 1.0f & & ! result . startsolid )
{
// can see this spot
// keep track of how far we can see
float rangeSq = ( eye - walkable ) . LengthSqr ( ) ;
if ( rangeSq > farthestRangeSq )
{
farthestRangeSq = rangeSq ;
if ( rangeSq > = minSniperRangeSq )
{
// this is a sniper spot
// determine how good of a sniper spot it is by keeping track of the snipable area
if ( found )
{
if ( walkable . x < sniperExtent . lo . x )
sniperExtent . lo . x = walkable . x ;
if ( walkable . x > sniperExtent . hi . x )
sniperExtent . hi . x = walkable . x ;
if ( walkable . y < sniperExtent . lo . y )
sniperExtent . lo . y = walkable . y ;
if ( walkable . y > sniperExtent . hi . y )
sniperExtent . hi . y = walkable . y ;
}
else
{
sniperExtent . lo = walkable ;
sniperExtent . hi = walkable ;
found = true ;
}
}
}
}
}
}
}
if ( found )
{
// if we can see a large snipable area, it is an "ideal" spot
float snipableArea = sniperExtent . Area ( ) ;
const float minIdealSniperArea = 200.0f * 200.0f ;
const float longSniperRangeSq = 1500.0f * 1500.0f ;
if ( snipableArea > = minIdealSniperArea | | farthestRangeSq > = longSniperRangeSq )
spot - > m_flags | = HidingSpot : : IDEAL_SNIPER_SPOT ;
else
spot - > m_flags | = HidingSpot : : GOOD_SNIPER_SPOT ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Analyze local area neighborhood to find " sniper spots " for this area
*/
void CNavArea : : ComputeSniperSpots ( void )
{
if ( nav_quicksave . GetBool ( ) )
return ;
FOR_EACH_VEC ( m_hidingSpots , it )
{
HidingSpot * spot = m_hidingSpots [ it ] ;
ClassifySniperSpot ( spot ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Given the areas we are moving between , return the spots we will encounter
*/
SpotEncounter * CNavArea : : GetSpotEncounter ( const CNavArea * from , const CNavArea * to )
{
if ( from & & to )
{
SpotEncounter * e ;
FOR_EACH_VEC ( m_spotEncounters , it )
{
e = m_spotEncounters [ it ] ;
if ( e - > from . area = = from & & e - > to . area = = to )
return e ;
}
}
return NULL ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Add spot encounter data when moving from area to area
*/
void CNavArea : : AddSpotEncounters ( const CNavArea * from , NavDirType fromDir , const CNavArea * to , NavDirType toDir )
{
SpotEncounter * e = new SpotEncounter ;
e - > from . area = const_cast < CNavArea * > ( from ) ;
e - > fromDir = fromDir ;
e - > to . area = const_cast < CNavArea * > ( to ) ;
e - > toDir = toDir ;
float halfWidth ;
ComputePortal ( to , toDir , & e - > path . to , & halfWidth ) ;
ComputePortal ( from , fromDir , & e - > path . from , & halfWidth ) ;
const float eyeHeight = HumanEyeHeight ;
e - > path . from . z = from - > GetZ ( e - > path . from ) + eyeHeight ;
e - > path . to . z = to - > GetZ ( e - > path . to ) + eyeHeight ;
// step along ray and track which spots can be seen
Vector dir = e - > path . to - e - > path . from ;
float length = dir . NormalizeInPlace ( ) ;
// create unique marker to flag used spots
HidingSpot : : ChangeMasterMarker ( ) ;
const float stepSize = 25.0f ; // 50
const float seeSpotRange = 2000.0f ; // 3000
trace_t result ;
Vector eye , delta ;
HidingSpot * spot ;
SpotOrder spotOrder ;
// step along path thru this area
bool done = false ;
for ( float along = 0.0f ; ! done ; along + = stepSize )
{
// make sure we check the endpoint of the path segment
if ( along > = length )
{
along = length ;
done = true ;
}
// move the eyepoint along the path segment
eye = e - > path . from + along * dir ;
// check each hiding spot for visibility
FOR_EACH_VEC ( TheHidingSpots , it )
{
spot = TheHidingSpots [ it ] ;
// only look at spots with cover (others are out in the open and easily seen)
if ( ! spot - > HasGoodCover ( ) )
continue ;
if ( spot - > IsMarked ( ) )
continue ;
const Vector & spotPos = spot - > GetPosition ( ) ;
delta . x = spotPos . x - eye . x ;
delta . y = spotPos . y - eye . y ;
delta . z = ( spotPos . z + eyeHeight ) - eye . z ;
// check if in range
if ( delta . IsLengthGreaterThan ( seeSpotRange ) )
continue ;
// check if we have LOS
// BOTPORT: ignore glass here
UTIL_TraceLine ( eye , Vector ( spotPos . x , spotPos . y , spotPos . z + HalfHumanHeight ) , MASK_NPCSOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & result ) ;
if ( result . fraction ! = 1.0f )
continue ;
// if spot is in front of us along our path, ignore it
delta . NormalizeInPlace ( ) ;
float dot = DotProduct ( dir , delta ) ;
if ( dot < 0.7071f & & dot > - 0.7071f )
{
// we only want to keep spots that BECOME visible as we walk past them
// therefore, skip ALL visible spots at the start of the path segment
if ( along > 0.0f )
{
// add spot to encounter
spotOrder . spot = spot ;
spotOrder . t = along / length ;
e - > spots . AddToTail ( spotOrder ) ;
}
}
// mark spot as encountered
spot - > Mark ( ) ;
}
}
// add encounter to list
m_spotEncounters . AddToTail ( e ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Compute " spot encounter " data . This is an ordered list of spots to look at
* for each possible path thru a nav area .
*/
void CNavArea : : ComputeSpotEncounters ( void )
{
m_spotEncounters . RemoveAll ( ) ;
if ( nav_quicksave . GetBool ( ) )
return ;
// for each adjacent area
for ( int fromDir = 0 ; fromDir < NUM_DIRECTIONS ; + + fromDir )
{
FOR_EACH_VEC ( m_connect [ fromDir ] , it )
{
NavConnect * fromCon = & ( m_connect [ fromDir ] [ it ] ) ;
// compute encounter data for path to each adjacent area
for ( int toDir = 0 ; toDir < NUM_DIRECTIONS ; + + toDir )
{
FOR_EACH_VEC ( m_connect [ toDir ] , ot )
{
NavConnect * toCon = & ( m_connect [ toDir ] [ ot ] ) ;
if ( toCon = = fromCon )
continue ;
// just do our direction, as we'll loop around for other direction
AddSpotEncounters ( fromCon - > area , ( NavDirType ) fromDir , toCon - > area , ( NavDirType ) toDir ) ;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Decay the danger values
*/
void CNavArea : : DecayDanger ( void )
{
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
float deltaT = gpGlobals - > curtime - m_dangerTimestamp [ i ] ;
float decayAmount = GetDangerDecayRate ( ) * deltaT ;
m_danger [ i ] - = decayAmount ;
if ( m_danger [ i ] < 0.0f )
m_danger [ i ] = 0.0f ;
// update timestamp
m_dangerTimestamp [ i ] = gpGlobals - > curtime ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Increase the danger of this area for the given team
*/
void CNavArea : : IncreaseDanger ( int teamID , float amount )
{
// before we add the new value, decay what's there
DecayDanger ( ) ;
int teamIdx = teamID % MAX_NAV_TEAMS ;
m_danger [ teamIdx ] + = amount ;
m_dangerTimestamp [ teamIdx ] = gpGlobals - > curtime ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the danger of this area ( decays over time )
*/
float CNavArea : : GetDanger ( int teamID )
{
DecayDanger ( ) ;
int teamIdx = teamID % MAX_NAV_TEAMS ;
return m_danger [ teamIdx ] ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns a 0. .1 light intensity for the given point
*/
float CNavArea : : GetLightIntensity ( const Vector & pos ) const
{
Vector testPos ;
testPos . x = clamp ( pos . x , m_nwCorner . x , m_seCorner . x ) ;
testPos . y = clamp ( pos . y , m_nwCorner . y , m_seCorner . y ) ;
testPos . z = pos . z ;
float dX = ( testPos . x - m_nwCorner . x ) / ( m_seCorner . x - m_nwCorner . x ) ;
float dY = ( testPos . y - m_nwCorner . y ) / ( m_seCorner . y - m_nwCorner . y ) ;
float northLight = m_lightIntensity [ NORTH_WEST ] * ( 1 - dX ) + m_lightIntensity [ NORTH_EAST ] * dX ;
float southLight = m_lightIntensity [ SOUTH_WEST ] * ( 1 - dX ) + m_lightIntensity [ SOUTH_EAST ] * dX ;
float light = northLight * ( 1 - dY ) + southLight * dY ;
return light ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns a 0. .1 light intensity for the given point
*/
float CNavArea : : GetLightIntensity ( float x , float y ) const
{
return GetLightIntensity ( Vector ( x , y , 0 ) ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns a 0. .1 light intensity averaged over the whole area
*/
float CNavArea : : GetLightIntensity ( void ) const
{
float light = m_lightIntensity [ NORTH_WEST ] ;
light + = m_lightIntensity [ NORTH_EAST ] ;
light + = m_lightIntensity [ SOUTH_WEST ] ;
light + = m_lightIntensity [ SOUTH_EAST ] ;
return light / 4.0f ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Compute light intensity at corners and center ( requires client via listenserver )
*/
bool CNavArea : : ComputeLighting ( void )
{
if ( engine - > IsDedicatedServer ( ) )
{
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
m_lightIntensity [ i ] = 1.0f ;
}
return true ;
}
// Calculate light at the corners
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
Vector pos = FindPositionInArea ( this , ( NavCornerType ) i ) ;
pos . z = GetZ ( pos ) + HalfHumanHeight - StepHeight ; // players light from their centers, and we light from slightly below that, to allow for low ceilings
float height ;
if ( TheNavMesh - > GetGroundHeight ( pos , & height ) )
{
pos . z = height + HalfHumanHeight - StepHeight ; // players light from their centers, and we light from slightly below that, to allow for low ceilings
}
Vector light ( 0 , 0 , 0 ) ;
// FIXMEL4DTOMAINMERGE
//if ( !engine->GetLightForPointListenServerOnly( pos, false, &light ) )
//{
//NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 255, 0, 0, false, 100.0f );
// return false;
//}
Vector ambientColor ;
// FIXMEL4DTOMAINMERGE
//if ( !GetTerrainAmbientLightAtPoint( pos, &ambientColor ) )
{
//NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 255, 127, 0, false, 100.0f );
return false ;
}
//NDebugOverlay::Line( pos, pos + Vector( 0, 0, -100 ), 0, 255, 127, false, 100.0f );
float ambientIntensity = ambientColor . x + ambientColor . y + ambientColor . z ;
float lightIntensity = light . x + light . y + light . z ;
lightIntensity = clamp ( lightIntensity , 0.f , 1.f ) ; // sum can go well over 1.0, but it's the lower region we care about. if it's bright, we don't need to know *how* bright.
lightIntensity = MAX ( lightIntensity , ambientIntensity ) ;
m_lightIntensity [ i ] = lightIntensity ;
}
return true ;
}
//--------------------------------------------------------------------------------------------------------------
CON_COMMAND_F ( nav_update_lighting , " Recomputes lighting values " , FCVAR_CHEAT )
{
int numComputed = 0 ;
if ( args . ArgC ( ) = = 2 )
{
int areaID = atoi ( args [ 1 ] ) ;
CNavArea * area = TheNavMesh - > GetNavAreaByID ( areaID ) ;
if ( area )
{
if ( area - > ComputeLighting ( ) )
{
+ + numComputed ;
}
}
}
else
{
FOR_EACH_VEC ( TheNavAreas , index )
{
CNavArea * area = TheNavAreas [ index ] ;
if ( area - > ComputeLighting ( ) )
{
+ + numComputed ;
}
}
}
DevMsg ( " Computed lighting for %d/%d areas \n " , numComputed , TheNavAreas . Count ( ) ) ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Raise / lower a corner
*/
void CNavArea : : RaiseCorner ( NavCornerType corner , int amount , bool raiseAdjacentCorners )
{
if ( corner = = NUM_CORNERS )
{
RaiseCorner ( NORTH_WEST , amount , raiseAdjacentCorners ) ;
RaiseCorner ( NORTH_EAST , amount , raiseAdjacentCorners ) ;
RaiseCorner ( SOUTH_WEST , amount , raiseAdjacentCorners ) ;
RaiseCorner ( SOUTH_EAST , amount , raiseAdjacentCorners ) ;
return ;
}
// Move the corner
switch ( corner )
{
case NORTH_WEST :
m_nwCorner . z + = amount ;
break ;
case NORTH_EAST :
m_neZ + = amount ;
break ;
case SOUTH_WEST :
m_swZ + = amount ;
break ;
case SOUTH_EAST :
m_seCorner . z + = amount ;
break ;
}
// Recompute the center
m_center . x = ( m_nwCorner . x + m_seCorner . x ) / 2.0f ;
m_center . y = ( m_nwCorner . y + m_seCorner . y ) / 2.0f ;
m_center . z = ( m_nwCorner . z + m_seCorner . z ) / 2.0f ;
if ( ( m_seCorner . x - m_nwCorner . x ) > 0.0f & & ( m_seCorner . y - m_nwCorner . y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner . x - m_nwCorner . x ) ;
m_invDyCorners = 1.0f / ( m_seCorner . y - m_nwCorner . y ) ;
}
else
{
m_invDxCorners = m_invDyCorners = 0 ;
}
if ( ! raiseAdjacentCorners | | nav_corner_adjust_adjacent . GetFloat ( ) < = 0.0f )
{
return ;
}
// Find nearby areas that share the corner
CNavArea : : MakeNewMarker ( ) ;
Mark ( ) ;
const float tolerance = nav_corner_adjust_adjacent . GetFloat ( ) ;
Vector cornerPos = GetCorner ( corner ) ;
cornerPos . z - = amount ; // use the pre-adjustment corner for adjacency checks
int gridX = TheNavMesh - > WorldToGridX ( cornerPos . x ) ;
int gridY = TheNavMesh - > WorldToGridY ( cornerPos . y ) ;
const int shift = 1 ; // try a 3x3 set of grids in case we're on the edge
for ( int x = gridX - shift ; x < = gridX + shift ; + + x )
{
if ( x < 0 | | x > = TheNavMesh - > m_gridSizeX )
continue ;
for ( int y = gridY - shift ; y < = gridY + shift ; + + y )
{
if ( y < 0 | | y > = TheNavMesh - > m_gridSizeY )
continue ;
NavAreaVector * areas = & TheNavMesh - > m_grid [ x + y * TheNavMesh - > m_gridSizeX ] ;
// find closest area in this cell
FOR_EACH_VEC ( ( * areas ) , it )
{
CNavArea * area = ( * areas ) [ it ] ;
// skip if we've already visited this area
if ( area - > IsMarked ( ) )
continue ;
area - > Mark ( ) ;
Vector areaPos ;
for ( int i = 0 ; i < NUM_CORNERS ; + + i )
{
areaPos = area - > GetCorner ( NavCornerType ( i ) ) ;
if ( areaPos . DistTo ( cornerPos ) < tolerance )
{
float heightDiff = ( cornerPos . z + amount ) - areaPos . z ;
area - > RaiseCorner ( NavCornerType ( i ) , heightDiff , false ) ;
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* FindGroundZFromPoint walks from the start position to the end position in GenerationStepSize increments ,
* checking the ground height along the way .
*/
float FindGroundZFromPoint ( const Vector & end , const Vector & start )
{
Vector step ( 0 , 0 , StepHeight ) ;
if ( fabs ( end . x - start . x ) > fabs ( end . y - start . y ) )
{
step . x = GenerationStepSize ;
if ( end . x < start . x )
{
step . x = - step . x ;
}
}
else
{
step . y = GenerationStepSize ;
if ( end . y < start . y )
{
step . y = - step . y ;
}
}
// step towards our end point
Vector point = start ;
float z ;
while ( point . AsVector2D ( ) . DistTo ( end . AsVector2D ( ) ) > GenerationStepSize )
{
point = point + step ;
z = point . z ;
if ( TheNavMesh - > GetGroundHeight ( point , & z ) )
{
point . z = z ;
}
else
{
point . z - = step . z ;
}
}
// now do the exact one once we're within GenerationStepSize of it
z = point . z + step . z ;
point = end ;
point . z = z ;
if ( TheNavMesh - > GetGroundHeight ( point , & z ) )
{
point . z = z ;
}
else
{
point . z - = step . z ;
}
return point . z ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Finds the Z value for a corner given two other corner points . This walks along the edges of the nav area
* in GenerationStepSize increments , to increase accuracy .
*/
float FindGroundZ ( const Vector & original , const Vector & corner1 , const Vector & corner2 )
{
float first = FindGroundZFromPoint ( original , corner1 ) ;
float second = FindGroundZFromPoint ( original , corner2 ) ;
if ( fabs ( first - second ) > StepHeight )
{
// approaching the point from the two directions didn't agree. Take the one closest to the original z.
if ( fabs ( original . z - first ) > fabs ( original . z - second ) )
{
return second ;
}
else
{
return first ;
}
}
return first ;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Places a corner ( or all corners if corner = = NUM_CORNERS ) on the ground
*/
void CNavArea : : PlaceOnGround ( NavCornerType corner , float inset )
{
trace_t result ;
Vector from , to ;
Vector nw = m_nwCorner + Vector ( inset , inset , 0 ) ;
Vector se = m_seCorner + Vector ( - inset , - inset , 0 ) ;
Vector ne , sw ;
ne . x = se . x ;
ne . y = nw . y ;
ne . z = m_neZ ;
sw . x = nw . x ;
sw . y = se . y ;
sw . z = m_swZ ;
if ( corner = = NORTH_WEST | | corner = = NUM_CORNERS )
{
float newZ = FindGroundZ ( nw , ne , sw ) ;
RaiseCorner ( NORTH_WEST , newZ - nw . z ) ;
}
if ( corner = = NORTH_EAST | | corner = = NUM_CORNERS )
{
float newZ = FindGroundZ ( ne , nw , se ) ;
RaiseCorner ( NORTH_EAST , newZ - ne . z ) ;
}
if ( corner = = SOUTH_WEST | | corner = = NUM_CORNERS )
{
float newZ = FindGroundZ ( sw , nw , se ) ;
RaiseCorner ( SOUTH_WEST , newZ - sw . z ) ;
}
if ( corner = = SOUTH_EAST | | corner = = NUM_CORNERS )
{
float newZ = FindGroundZ ( se , ne , sw ) ;
RaiseCorner ( SOUTH_EAST , newZ - se . z ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Shift the nav area
*/
void CNavArea : : Shift ( const Vector & shift )
{
m_nwCorner + = shift ;
m_seCorner + = shift ;
m_center + = shift ;
}
//--------------------------------------------------------------------------------------------------------------
static void CommandNavUpdateBlocked ( void )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
if ( TheNavMesh - > GetMarkedArea ( ) )
{
CNavArea * area = TheNavMesh - > GetMarkedArea ( ) ;
area - > UpdateBlocked ( true ) ;
if ( area - > IsBlocked ( TEAM_ANY ) )
{
DevMsg ( " Area #%d %s is blocked \n " , area - > GetID ( ) , VecToString ( area - > GetCenter ( ) + Vector ( 0 , 0 , HalfHumanHeight ) ) ) ;
}
}
else
{
float start = Plat_FloatTime ( ) ;
CNavArea * blockedArea = NULL ;
FOR_EACH_VEC ( TheNavAreas , nit )
{
CNavArea * area = TheNavAreas [ nit ] ;
area - > UpdateBlocked ( true ) ;
if ( area - > IsBlocked ( TEAM_ANY ) )
{
DevMsg ( " Area #%d %s is blocked \n " , area - > GetID ( ) , VecToString ( area - > GetCenter ( ) + Vector ( 0 , 0 , HalfHumanHeight ) ) ) ;
if ( ! blockedArea )
{
blockedArea = area ;
}
}
}
float end = Plat_FloatTime ( ) ;
float time = ( end - start ) * 1000.0f ;
DevMsg ( " nav_update_blocked took %2.2f ms \n " , time ) ;
if ( blockedArea )
{
CBasePlayer * player = UTIL_GetListenServerHost ( ) ;
if ( player )
{
if ( ( player - > IsDead ( ) | | player - > IsObserver ( ) ) & & player - > GetObserverMode ( ) = = OBS_MODE_ROAMING )
{
Vector origin = blockedArea - > GetCenter ( ) + Vector ( 0 , 0 , 0.75f * HumanHeight ) ;
UTIL_SetOrigin ( player , origin ) ;
}
}
}
}
}
static ConCommand nav_update_blocked ( " nav_update_blocked " , CommandNavUpdateBlocked , " Updates the blocked/unblocked status for every nav area. " , FCVAR_GAMEDLL ) ;
//--------------------------------------------------------------------------------------------------------
bool CNavArea : : IsBlocked ( int teamID , bool ignoreNavBlockers ) const
{
if ( ignoreNavBlockers & & ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) )
{
return false ;
}
# ifdef TERROR
if ( ( teamID = = TEAM_SURVIVOR ) & & ( m_attributeFlags & CNavArea : : NAV_PLAYERCLIP ) )
return true ;
# endif
if ( teamID = = TEAM_ANY )
{
bool isBlocked = false ;
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
isBlocked | = m_isBlocked [ i ] ;
}
return isBlocked ;
}
int teamIdx = teamID % MAX_NAV_TEAMS ;
return m_isBlocked [ teamIdx ] ;
}
//--------------------------------------------------------------------------------------------------------
void CNavArea : : MarkAsBlocked ( int teamID , CBaseEntity * blocker , bool bGenerateEvent )
{
if ( blocker & & blocker - > ClassMatches ( " func_nav_blocker " ) )
{
m_attributeFlags | = NAV_MESH_NAV_BLOCKER ;
}
bool wasBlocked = false ;
if ( teamID = = TEAM_ANY )
{
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
wasBlocked | = m_isBlocked [ i ] ;
m_isBlocked [ i ] = true ;
}
}
else
{
int teamIdx = teamID % MAX_NAV_TEAMS ;
wasBlocked | = m_isBlocked [ teamIdx ] ;
m_isBlocked [ teamIdx ] = true ;
}
if ( ! wasBlocked )
{
if ( bGenerateEvent )
{
IGameEvent * event = gameeventmanager - > CreateEvent ( " nav_blocked " ) ;
if ( event )
{
event - > SetInt ( " area " , m_id ) ;
event - > SetInt ( " blocked " , 1 ) ;
gameeventmanager - > FireEvent ( event ) ;
}
}
if ( nav_debug_blocked . GetBool ( ) )
{
if ( blocker )
{
ConColorMsg ( Color ( 0 , 255 , 128 , 255 ) , " %s %d blocked area %d \n " , blocker - > GetDebugName ( ) , blocker - > entindex ( ) , GetID ( ) ) ;
}
else
{
ConColorMsg ( Color ( 0 , 255 , 128 , 255 ) , " non-entity blocked area %d \n " , GetID ( ) ) ;
}
}
TheNavMesh - > OnAreaBlocked ( this ) ;
}
else
{
if ( nav_debug_blocked . GetBool ( ) )
{
if ( blocker )
{
ConColorMsg ( Color ( 0 , 255 , 128 , 255 ) , " DUPE: %s %d blocked area %d \n " , blocker - > GetDebugName ( ) , blocker - > entindex ( ) , GetID ( ) ) ;
}
else
{
ConColorMsg ( Color ( 0 , 255 , 128 , 255 ) , " DUPE: non-entity blocked area %d \n " , GetID ( ) ) ;
}
}
}
}
//--------------------------------------------------------------------------------------------------------
// checks if any func_nav_blockers are still blocking the area
void CNavArea : : UpdateBlockedFromNavBlockers ( void )
{
VPROF ( " CNavArea::UpdateBlockedFromNavBlockers " ) ;
Extent bounds ;
GetExtent ( & bounds ) ;
// Save off old values, reset to not blocked state
m_attributeFlags & = ~ NAV_MESH_NAV_BLOCKER ;
bool oldBlocked [ MAX_NAV_TEAMS ] ;
bool wasBlocked = false ;
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
oldBlocked [ i ] = m_isBlocked [ i ] ;
wasBlocked = wasBlocked | | m_isBlocked [ i ] ;
m_isBlocked [ i ] = false ;
}
bool isBlocked = CFuncNavBlocker : : CalculateBlocked ( m_isBlocked , bounds . lo , bounds . hi ) ;
if ( isBlocked )
{
m_attributeFlags | = NAV_MESH_NAV_BLOCKER ;
}
// If we're unblocked, fire a nav_blocked event.
if ( wasBlocked ! = isBlocked )
{
IGameEvent * event = gameeventmanager - > CreateEvent ( " nav_blocked " ) ;
if ( event )
{
event - > SetInt ( " area " , m_id ) ;
event - > SetInt ( " blocked " , isBlocked ) ;
gameeventmanager - > FireEvent ( event ) ;
}
if ( isBlocked )
{
if ( nav_debug_blocked . GetBool ( ) )
{
ConColorMsg ( Color ( 0 , 255 , 128 , 255 ) , " area %d is blocked by a nav blocker \n " , GetID ( ) ) ;
}
TheNavMesh - > OnAreaBlocked ( this ) ;
}
else
{
if ( nav_debug_blocked . GetBool ( ) )
{
ConColorMsg ( Color ( 0 , 128 , 255 , 255 ) , " area %d is unblocked by a nav blocker \n " , GetID ( ) ) ;
}
TheNavMesh - > OnAreaUnblocked ( this ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : UnblockArea ( int teamID )
{
bool wasBlocked = IsBlocked ( teamID ) ;
if ( teamID = = TEAM_ANY )
{
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
m_isBlocked [ i ] = false ;
}
}
else
{
int teamIdx = teamID % MAX_NAV_TEAMS ;
m_isBlocked [ teamIdx ] = false ;
}
if ( wasBlocked )
{
IGameEvent * event = gameeventmanager - > CreateEvent ( " nav_blocked " ) ;
if ( event )
{
event - > SetInt ( " area " , m_id ) ;
event - > SetInt ( " blocked " , false ) ;
gameeventmanager - > FireEvent ( event ) ;
}
if ( nav_debug_blocked . GetBool ( ) )
{
ConColorMsg ( Color ( 255 , 0 , 128 , 255 ) , " area %d is unblocked by UnblockArea \n " , GetID ( ) ) ;
}
TheNavMesh - > OnAreaUnblocked ( this ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Updates the ( un ) blocked status of the nav area
* The semantics of this method have gotten very muddled - needs refactoring ( MSB 5 / 7 / 09 )
*/
void CNavArea : : UpdateBlocked ( bool force , int teamID )
{
VPROF ( " CNavArea::UpdateBlocked " ) ;
if ( ! force & & ! m_blockedTimer . IsElapsed ( ) )
{
return ;
}
const float MaxBlockedCheckInterval = 5 ;
float interval = m_blockedTimer . GetCountdownDuration ( ) + 1 ;
if ( interval > MaxBlockedCheckInterval )
{
interval = MaxBlockedCheckInterval ;
}
m_blockedTimer . Start ( interval ) ;
if ( ( m_attributeFlags & NAV_MESH_NAV_BLOCKER ) )
{
if ( force )
{
UpdateBlockedFromNavBlockers ( ) ;
}
return ;
}
Vector origin = GetCenter ( ) ;
origin . z + = HalfHumanHeight ;
const float sizeX = MAX ( 1 , MIN ( GetSizeX ( ) / 2 - 5 , HalfHumanWidth ) ) ;
const float sizeY = MAX ( 1 , MIN ( GetSizeY ( ) / 2 - 5 , HalfHumanWidth ) ) ;
Extent bounds ;
bounds . lo . Init ( - sizeX , - sizeY , 0 ) ;
bounds . hi . Init ( sizeX , sizeY , VEC_DUCK_HULL_MAX . z - HalfHumanHeight ) ;
bool wasBlocked = IsBlocked ( TEAM_ANY ) ;
// See if spot is valid
# ifdef TERROR
// don't unblock func_doors
CTraceFilterWalkableEntities filter ( NULL , COLLISION_GROUP_PLAYER_MOVEMENT , WALK_THRU_PROP_DOORS | WALK_THRU_BREAKABLES ) ;
# else
CTraceFilterWalkableEntities filter ( NULL , COLLISION_GROUP_PLAYER_MOVEMENT , WALK_THRU_DOORS | WALK_THRU_BREAKABLES ) ;
# endif
trace_t tr ;
{
VPROF ( " CNavArea::UpdateBlocked-Trace " ) ;
UTIL_TraceHull (
origin ,
origin ,
bounds . lo ,
bounds . hi ,
MASK_NPCSOLID_BRUSHONLY ,
& filter ,
& tr ) ;
}
if ( ! tr . startsolid )
{
// unblock ourself
# ifdef TERROR
extern ConVar DebugZombieBreakables ;
if ( DebugZombieBreakables . GetBool ( ) )
# else
if ( false )
# endif
{
NDebugOverlay : : Box ( origin , bounds . lo , bounds . hi , 0 , 255 , 0 , 10 , 5.0f ) ;
}
else
{
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
m_isBlocked [ i ] = false ;
}
}
}
else if ( force )
{
if ( teamID = = TEAM_ANY )
{
for ( int i = 0 ; i < MAX_NAV_TEAMS ; + + i )
{
m_isBlocked [ i ] = true ;
}
}
else
{
int teamIdx = teamID % MAX_NAV_TEAMS ;
m_isBlocked [ teamIdx ] = true ;
}
}
bool isBlocked = IsBlocked ( TEAM_ANY ) ;
if ( wasBlocked ! = isBlocked )
{
VPROF ( " CNavArea::UpdateBlocked-Event " ) ;
IGameEvent * event = gameeventmanager - > CreateEvent ( " nav_blocked " ) ;
if ( event )
{
event - > SetInt ( " area " , m_id ) ;
event - > SetInt ( " blocked " , isBlocked ) ;
gameeventmanager - > FireEvent ( event ) ;
}
if ( isBlocked )
{
TheNavMesh - > OnAreaBlocked ( this ) ;
}
else
{
TheNavMesh - > OnAreaUnblocked ( this ) ;
}
}
if ( TheNavMesh - > GetMarkedArea ( ) = = this )
{
if ( IsBlocked ( teamID ) )
{
NDebugOverlay : : Box ( origin , bounds . lo , bounds . hi , 255 , 0 , 0 , 64 , 3.0f ) ;
}
else
{
NDebugOverlay : : Box ( origin , bounds . lo , bounds . hi , 0 , 255 , 0 , 64 , 3.0f ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Checks if there is a floor under the nav area , in case a breakable floor is gone
*/
void CNavArea : : CheckFloor ( CBaseEntity * ignore )
{
if ( IsBlocked ( TEAM_ANY ) )
return ;
Vector origin = GetCenter ( ) ;
origin . z - = JumpCrouchHeight ;
const float size = GenerationStepSize * 0.5f ;
Vector mins = Vector ( - size , - size , 0 ) ;
Vector maxs = Vector ( size , size , JumpCrouchHeight + 10.0f ) ;
// See if spot is valid
trace_t tr ;
UTIL_TraceHull (
origin ,
origin ,
mins ,
maxs ,
MASK_NPCSOLID_BRUSHONLY ,
ignore ,
COLLISION_GROUP_PLAYER_MOVEMENT ,
& tr ) ;
// If the center is open space, we're effectively blocked
if ( ! tr . startsolid )
{
MarkAsBlocked ( TEAM_ANY , NULL ) ;
}
/*
if ( IsBlocked ( TEAM_ANY ) )
{
NDebugOverlay : : Box ( origin , mins , maxs , 255 , 0 , 0 , 64 , 3.0f ) ;
}
else
{
NDebugOverlay : : Box ( origin , mins , maxs , 0 , 255 , 0 , 64 , 3.0f ) ;
}
*/
}
//--------------------------------------------------------------------------------------------------------
void CNavArea : : MarkObstacleToAvoid ( float obstructionHeight )
{
if ( m_avoidanceObstacleHeight < obstructionHeight )
{
if ( m_avoidanceObstacleHeight = = 0 )
{
TheNavMesh - > OnAvoidanceObstacleEnteredArea ( this ) ;
}
m_avoidanceObstacleHeight = obstructionHeight ;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Updates the ( un ) obstructed status of the nav area
*/
void CNavArea : : UpdateAvoidanceObstacles ( void )
{
if ( ! m_avoidanceObstacleTimer . IsElapsed ( ) )
{
return ;
}
const float MaxBlockedCheckInterval = 5 ;
float interval = m_blockedTimer . GetCountdownDuration ( ) + 1 ;
if ( interval > MaxBlockedCheckInterval )
{
interval = MaxBlockedCheckInterval ;
}
m_avoidanceObstacleTimer . Start ( interval ) ;
Vector mins = m_nwCorner ;
Vector maxs = m_seCorner ;
mins . z = MIN ( m_nwCorner . z , m_seCorner . z ) ;
maxs . z = MAX ( m_nwCorner . z , m_seCorner . z ) + HumanCrouchHeight ;
float obstructionHeight = 0.0f ;
for ( int i = 0 ; i < TheNavMesh - > GetObstructions ( ) . Count ( ) ; + + i )
{
INavAvoidanceObstacle * obstruction = TheNavMesh - > GetObstructions ( ) [ i ] ;
CBaseEntity * obstructingEntity = obstruction - > GetObstructingEntity ( ) ;
if ( ! obstructingEntity )
continue ;
// check if the aabb intersects the search aabb.
Vector vecSurroundMins , vecSurroundMaxs ;
obstructingEntity - > CollisionProp ( ) - > WorldSpaceSurroundingBounds ( & vecSurroundMins , & vecSurroundMaxs ) ;
if ( ! IsBoxIntersectingBox ( mins , maxs , vecSurroundMins , vecSurroundMaxs ) )
continue ;
if ( ! obstruction - > CanObstructNavAreas ( ) )
continue ;
float propHeight = obstruction - > GetNavObstructionHeight ( ) ;
obstructionHeight = MAX ( obstructionHeight , propHeight ) ;
}
m_avoidanceObstacleHeight = obstructionHeight ;
if ( m_avoidanceObstacleHeight = = 0.0f )
{
TheNavMesh - > OnAvoidanceObstacleLeftArea ( this ) ;
}
}
//--------------------------------------------------------------------------------------------------------------
// Clear set of func_nav_cost entities that affect this area
void CNavArea : : ClearAllNavCostEntities ( void )
{
RemoveAttributes ( NAV_MESH_FUNC_COST ) ;
m_funcNavCostVector . RemoveAll ( ) ;
}
//--------------------------------------------------------------------------------------------------------------
// Add the given func_nav_cost entity to the cost of this area
void CNavArea : : AddFuncNavCostEntity ( CFuncNavCost * cost )
{
SetAttributes ( NAV_MESH_FUNC_COST ) ;
m_funcNavCostVector . AddToTail ( cost ) ;
}
//--------------------------------------------------------------------------------------------------------------
// Return the cost multiplier of this area's func_nav_cost entities for the given actor
float CNavArea : : ComputeFuncNavCost ( CBaseCombatCharacter * who ) const
{
float funcCost = 1.0f ;
for ( int i = 0 ; i < m_funcNavCostVector . Count ( ) ; + + i )
{
if ( m_funcNavCostVector [ i ] ! = NULL )
{
funcCost * = m_funcNavCostVector [ i ] - > GetCostMultiplier ( who ) ;
}
}
return funcCost ;
}
//--------------------------------------------------------------------------------------------------------------
bool CNavArea : : HasFuncNavAvoid ( void ) const
{
for ( int i = 0 ; i < m_funcNavCostVector . Count ( ) ; + + i )
{
CFuncNavAvoid * avoid = dynamic_cast < CFuncNavAvoid * > ( m_funcNavCostVector [ i ] . Get ( ) ) ;
if ( avoid )
{
return true ;
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
bool CNavArea : : HasFuncNavPrefer ( void ) const
{
for ( int i = 0 ; i < m_funcNavCostVector . Count ( ) ; + + i )
{
CFuncNavPrefer * prefer = dynamic_cast < CFuncNavPrefer * > ( m_funcNavCostVector [ i ] . Get ( ) ) ;
if ( prefer )
{
return true ;
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------------
void CNavArea : : CheckWaterLevel ( void )
{
Vector pos ( GetCenter ( ) ) ;
if ( ! TheNavMesh - > GetGroundHeight ( pos , & pos . z ) )
{
m_isUnderwater = false ;
return ;
}
pos . z + = 1 ;
m_isUnderwater = ( enginetrace - > GetPointContents ( pos ) & MASK_WATER ) ! = 0 ;
}
//--------------------------------------------------------------------------------------------------------------
static void CommandNavCheckFloor ( void )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
if ( TheNavMesh - > GetMarkedArea ( ) )
{
CNavArea * area = TheNavMesh - > GetMarkedArea ( ) ;
area - > CheckFloor ( NULL ) ;
if ( area - > IsBlocked ( TEAM_ANY ) )
{
DevMsg ( " Area #%d %s is blocked \n " , area - > GetID ( ) , VecToString ( area - > GetCenter ( ) + Vector ( 0 , 0 , HalfHumanHeight ) ) ) ;
}
}
else
{
float start = Plat_FloatTime ( ) ;
FOR_EACH_VEC ( TheNavAreas , nit )
{
CNavArea * area = TheNavAreas [ nit ] ;
area - > CheckFloor ( NULL ) ;
if ( area - > IsBlocked ( TEAM_ANY ) )
{
DevMsg ( " Area #%d %s is blocked \n " , area - > GetID ( ) , VecToString ( area - > GetCenter ( ) + Vector ( 0 , 0 , HalfHumanHeight ) ) ) ;
}
}
float end = Plat_FloatTime ( ) ;
float time = ( end - start ) * 1000.0f ;
DevMsg ( " nav_check_floor took %2.2f ms \n " , time ) ;
}
}
static ConCommand nav_check_floor ( " nav_check_floor " , CommandNavCheckFloor , " Updates the blocked/unblocked status for every nav area. " , FCVAR_GAMEDLL ) ;
//--------------------------------------------------------------------------------------------------------------
bool SelectOverlappingAreas : : operator ( ) ( CNavArea * area )
{
CNavArea * overlappingArea = NULL ;
CNavLadder * overlappingLadder = NULL ;
Vector nw = area - > GetCorner ( NORTH_WEST ) ;
Vector se = area - > GetCorner ( SOUTH_EAST ) ;
Vector start = nw ;
start . x + = GenerationStepSize / 2 ;
start . y + = GenerationStepSize / 2 ;
while ( start . x < se . x )
{
start . y = nw . y + GenerationStepSize / 2 ;
while ( start . y < se . y )
{
start . z = area - > GetZ ( start . x , start . y ) ;
Vector end = start ;
start . z - = StepHeight ;
end . z + = HalfHumanHeight ;
if ( TheNavMesh - > FindNavAreaOrLadderAlongRay ( start , end , & overlappingArea , & overlappingLadder , area ) )
{
if ( overlappingArea )
{
TheNavMesh - > AddToSelectedSet ( overlappingArea ) ;
TheNavMesh - > AddToSelectedSet ( area ) ;
}
}
start . y + = GenerationStepSize ;
}
start . x + = GenerationStepSize ;
}
return true ;
}
//--------------------------------------------------------------------------------------------------------------
static void CommandNavSelectOverlapping ( void )
{
if ( ! UTIL_IsCommandIssuedByServerAdmin ( ) )
return ;
TheNavMesh - > ClearSelectedSet ( ) ;
SelectOverlappingAreas overlapCheck ;
TheNavMesh - > ForAllAreas ( overlapCheck ) ;
Msg ( " %d overlapping areas selected \n " , TheNavMesh - > GetSelecteSetSize ( ) ) ;
}
static ConCommand nav_select_overlapping ( " nav_select_overlapping " , CommandNavSelectOverlapping , " Selects nav areas that are overlapping others. " , FCVAR_GAMEDLL ) ;
//--------------------------------------------------------------------------------------------------------
static byte m_PVS [ PAD_NUMBER ( MAX_MAP_CLUSTERS , 8 ) / 8 ] ;
static int m_nPVSSize ; // PVS size in bytes
CUtlHash < NavVisPair_t , CVisPairHashFuncs , CVisPairHashFuncs > * g_pNavVisPairHash ;
# define MASK_NAV_VISION (MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE)
//--------------------------------------------------------------------------------------------------------
/**
* Set PVS to only include the Potentially Visible Set as seen from anywhere
* within this nav area
*/
void CNavArea : : SetupPVS ( void ) const
{
m_nPVSSize = sizeof ( m_PVS ) ;
engine - > ResetPVS ( m_PVS , m_nPVSSize ) ;
const float margin = GenerationStepSize / 2.0f ;
Vector eye ( 0 , 0 , 0.75f * HumanHeight ) ;
// step across area checking visibility to given area
Vector shift ( eye ) ;
for ( shift . y = margin ; shift . y < = GetSizeY ( ) - margin ; shift . y + = GenerationStepSize )
{
for ( shift . x = margin ; shift . x < = GetSizeX ( ) - margin ; shift . x + = GenerationStepSize )
{
// Optimization:
// If we are already POTENTIALLY_VISIBLE, and no longer COMPLETELY_VISIBLE, there's
// no way for vis to change again.
Vector testPos ( GetCorner ( NORTH_WEST ) + shift ) ;
testPos . z = GetZ ( testPos ) + eye . z ;
engine - > AddOriginToPVS ( testPos ) ;
}
}
}
//--------------------------------------------------------------------------------------------------------
/**
* Return true if this area is within the current PVS
*/
bool CNavArea : : IsInPVS ( void ) const
{
Vector eye ( 0 , 0 , 0.75f * HumanHeight ) ;
Extent areaExtent ;
areaExtent . lo = GetCenter ( ) + eye ;
areaExtent . hi = areaExtent . lo ;
areaExtent . Encompass ( GetCorner ( NORTH_WEST ) + eye ) ;
areaExtent . Encompass ( GetCorner ( NORTH_EAST ) + eye ) ;
areaExtent . Encompass ( GetCorner ( SOUTH_WEST ) + eye ) ;
areaExtent . Encompass ( GetCorner ( SOUTH_EAST ) + eye ) ;
return engine - > CheckBoxInPVS ( areaExtent . lo , areaExtent . hi , m_PVS , m_nPVSSize ) ;
}
//--------------------------------------------------------------------------------------------------------
/**
* Do actual line - of - sight traces to determine if any part of given area is visible from this area
*/
CNavArea : : VisibilityType CNavArea : : ComputeVisibility ( const CNavArea * area , bool isPVSValid , bool bCheckPVS , bool * pOutsidePVS ) const
{
float distanceSq = area - > GetCenter ( ) . DistToSqr ( GetCenter ( ) ) ;
if ( nav_max_view_distance . GetFloat ( ) > 0.00001f )
{
// limit range of visibility check
if ( distanceSq > Sqr ( nav_max_view_distance . GetFloat ( ) ) )
{
// too far to be visible
return NOT_VISIBLE ;
}
}
if ( ! isPVSValid )
{
SetupPVS ( ) ;
}
Vector eye ( 0 , 0 , 0.75f * HumanHeight ) ;
if ( bCheckPVS )
{
Extent areaExtent ;
areaExtent . lo = areaExtent . hi = area - > GetCenter ( ) + eye ;
areaExtent . Encompass ( area - > GetCorner ( NORTH_WEST ) + eye ) ;
areaExtent . Encompass ( area - > GetCorner ( NORTH_EAST ) + eye ) ;
areaExtent . Encompass ( area - > GetCorner ( SOUTH_WEST ) + eye ) ;
areaExtent . Encompass ( area - > GetCorner ( SOUTH_EAST ) + eye ) ;
if ( ! engine - > CheckBoxInPVS ( areaExtent . lo , areaExtent . hi , m_PVS , m_nPVSSize ) )
{
if ( pOutsidePVS )
* pOutsidePVS = true ;
return NOT_VISIBLE ;
}
if ( pOutsidePVS )
* pOutsidePVS = false ;
}
//------------------------------------
Vector vThisNW = GetCorner ( NORTH_WEST ) + eye ;
Vector vThisNE = GetCorner ( NORTH_EAST ) + eye ;
Vector vThisSW = GetCorner ( SOUTH_WEST ) + eye ;
Vector vThisSE = GetCorner ( SOUTH_EAST ) + eye ;
Vector vThisCenter = GetCenter ( ) + eye ;
Vector vTraceMins ( vThisNW ) ;
Vector vTraceMaxs ( vThisSE ) ;
vTraceMins . z = MIN ( MIN ( MIN ( vThisNW . z , vThisNE . z ) , vThisSE . z ) , vThisSW . z ) ;
vTraceMaxs . z = MAX ( MAX ( MAX ( vThisNW . z , vThisNE . z ) , vThisSE . z ) , vThisSW . z ) + 0.1 ;
vTraceMins - = vThisCenter ;
vTraceMaxs - = vThisCenter ;
Vector vOtherMins ( area - > GetCorner ( NORTH_WEST ) ) ;
Vector vOtherMaxs ( area - > GetCorner ( SOUTH_EAST ) ) ;
Vector vTarget ;
CalcClosestPointOnAABB ( vOtherMins , vOtherMaxs , vThisCenter , vTarget ) ;
vTarget . z = area - > GetZ ( vTarget ) + eye . z ;
trace_t tr ;
CTraceFilterNoNPCsOrPlayer traceFilter ( NULL , COLLISION_GROUP_NONE ) ;
UTIL_TraceHull ( vThisCenter , vTarget , vTraceMins , vTraceMaxs , MASK_NAV_VISION , & traceFilter , & tr ) ;
if ( tr . fraction = = 1.0 | | ( tr . endpos . x > vOtherMins . x & & tr . endpos . x < vOtherMaxs . x & & tr . endpos . y > vOtherMins . y & & tr . endpos . y < vOtherMaxs . y ) )
{
return COMPLETELY_VISIBLE ; // Counter-intuitive: the way this function was written, "COMPLETELY_VISIBLE" actually means "I am completely visible to the other"
}
//------------------------------------
// check line of sight between areas
unsigned char vis = COMPLETELY_VISIBLE ;
const float margin = GenerationStepSize / 2.0f ;
Vector shift ( 0 , 0 , 0.75f * HumanHeight ) ;
// always check center to catch very small areas
if ( area - > IsPartiallyVisible ( GetCenter ( ) + eye ) )
{
vis | = POTENTIALLY_VISIBLE ;
}
else
{
vis & = ~ COMPLETELY_VISIBLE ;
}
Vector eyeToCenter ( GetCenter ( ) - area - > GetCenter ( ) ) ;
eyeToCenter . NormalizeInPlace ( ) ;
float angleTolerance = nav_potentially_visible_dot_tolerance . GetFloat ( ) ; // if corner-to-eye angles are this close to center-to-eye angles, assume the same result and skip the trace
// step across area checking visibility to given area
for ( shift . y = margin ; shift . y < = GetSizeY ( ) - margin ; shift . y + = GenerationStepSize )
{
for ( shift . x = margin ; shift . x < = GetSizeX ( ) - margin ; shift . x + = GenerationStepSize )
{
// Optimization:
// If we are already POTENTIALLY_VISIBLE, and no longer COMPLETELY_VISIBLE, there's
// no way for vis to change again.
if ( vis = = POTENTIALLY_VISIBLE )
return POTENTIALLY_VISIBLE ;
Vector testPos ( GetCorner ( NORTH_WEST ) + shift ) ;
testPos . z = GetZ ( testPos ) + eye . z ;
// Optimization - treat long-distance traces that are effectively collinear as the same
if ( distanceSq > Sqr ( 1000 ) )
{
Vector eyeToCorner ( testPos - ( GetCenter ( ) + eye ) ) ;
eyeToCorner . NormalizeInPlace ( ) ;
if ( eyeToCorner . Dot ( eyeToCenter ) > = angleTolerance )
{
continue ;
}
}
if ( area - > IsPartiallyVisible ( testPos ) )
{
vis | = POTENTIALLY_VISIBLE ;
}
else
{
vis & = ~ COMPLETELY_VISIBLE ;
}
}
}
return ( VisibilityType ) vis ;
}
//--------------------------------------------------------------------------------------------------------
/**
* Return a list of the delta between our visibility list and the given adjacent area
*/
const CNavArea : : CAreaBindInfoArray & CNavArea : : ComputeVisibilityDelta ( const CNavArea * other ) const
{
static CAreaBindInfoArray delta ;
delta . RemoveAll ( ) ;
// do not delta from a delta - if 'other' is already inheriting, use its inherited source directly
if ( other - > m_inheritVisibilityFrom . area ! = NULL )
{
Assert ( false & & " Visibility inheriting from inherited area " ) ;
delta = m_potentiallyVisibleAreas ;
return delta ;
}
// add any visible areas in my list that are not in 'others' list into the delta
int i , j ;
for ( i = 0 ; i < m_potentiallyVisibleAreas . Count ( ) ; + + i )
{
if ( m_potentiallyVisibleAreas [ i ] . area )
{
// is my visible area also in adjacent area's vis list
for ( j = 0 ; j < other - > m_potentiallyVisibleAreas . Count ( ) ; + + j )
{
if ( m_potentiallyVisibleAreas [ i ] . area = = other - > m_potentiallyVisibleAreas [ j ] . area & &
m_potentiallyVisibleAreas [ i ] . attributes = = other - > m_potentiallyVisibleAreas [ j ] . attributes )
{
// mutually identically visible
break ;
}
}
if ( j = = other - > m_potentiallyVisibleAreas . Count ( ) )
{
// my vis area not in adjacent area's vis list or has different visibility attributes - add to delta
delta . AddToTail ( m_potentiallyVisibleAreas [ i ] ) ;
}
}
}
// add explicit NOT_VISIBLE references to areas in 'others' list that are NOT in mine
for ( j = 0 ; j < other - > m_potentiallyVisibleAreas . Count ( ) ; + + j )
{
if ( other - > m_potentiallyVisibleAreas [ j ] . area )
{
for ( i = 0 ; i < m_potentiallyVisibleAreas . Count ( ) ; + + i )
{
if ( m_potentiallyVisibleAreas [ i ] . area = = other - > m_potentiallyVisibleAreas [ j ] . area )
{
// area in both lists - already handled in delta above
break ;
}
}
if ( i = = m_potentiallyVisibleAreas . Count ( ) )
{
// 'other' has area in their list that we don't - mark it explicitly NOT_VISIBLE
AreaBindInfo info ;
info . area = other - > m_potentiallyVisibleAreas [ j ] . area ;
info . attributes = NOT_VISIBLE ;
delta . AddToTail ( info ) ;
}
}
}
return delta ;
}
//--------------------------------------------------------------------------------------------------------
void CNavArea : : ResetPotentiallyVisibleAreas ( )
{
m_potentiallyVisibleAreas . RemoveAll ( ) ;
}
//--------------------------------------------------------------------------------------------------------
/**
* Determine visibility between areas .
* Compute full list of all areas visible for each area . This list will be compressed into deltas
* in the PostCustomAnalysis ( ) step .
*/
CNavArea * g_pCurVisArea ;
CTSListWithFreeList < CNavArea : : AreaBindInfo > g_ComputedVis ;
void CNavArea : : ComputeVisToArea ( CNavArea * & pOtherArea )
{
CNavArea * area = assert_cast < CNavArea * > ( pOtherArea ) ;
VisibilityType visThisToOther = ( area = = g_pCurVisArea ) ? COMPLETELY_VISIBLE : NOT_VISIBLE ;
VisibilityType visOtherToThis = NOT_VISIBLE ;
if ( area ! = g_pCurVisArea )
{
bool bOutsidePVS ;
visOtherToThis = g_pCurVisArea - > ComputeVisibility ( area , true , true , & bOutsidePVS ) ; // TODO: Hacky right now. Compute visibility for the "complete" case actually returns how completely visible the area is to the other. Should fix it to be more clear [1/30/2009 tom]
if ( ! bOutsidePVS & & ( visOtherToThis | | ( g_pCurVisArea - > GetCenter ( ) - area - > GetCenter ( ) ) . LengthSqr ( ) < Sqr ( nav_max_view_distance . GetFloat ( ) ) ) )
{
visThisToOther = area - > ComputeVisibility ( g_pCurVisArea , true , false ) ;
}
if ( ! visOtherToThis & & visThisToOther )
{
visOtherToThis = POTENTIALLY_VISIBLE ;
}
if ( ! visThisToOther & & visOtherToThis )
{
visThisToOther = POTENTIALLY_VISIBLE ;
}
}
CNavArea : : AreaBindInfo info ;
if ( visThisToOther ! = NOT_VISIBLE )
{
info . area = area ;
info . attributes = visThisToOther ;
g_ComputedVis . PushItem ( info ) ;
}
if ( visOtherToThis ! = NOT_VISIBLE )
{
info . area = g_pCurVisArea ;
info . attributes = visOtherToThis ;
area - > m_potentiallyVisibleAreas . AddToTail ( info ) ;
}
}
//--------------------------------------------------------------------------------------------------------
/**
* Determine visibility from this area to all potentially / completely visible areas in the mesh
*/
void CNavArea : : ComputeVisibilityToMesh ( void )
{
m_inheritVisibilityFrom . area = NULL ;
m_isInheritedFrom = false ;
// collect all possible nav areas that could be visible from this area
NavAreaCollector collector ;
float radius = nav_max_view_distance . GetFloat ( ) ;
if ( radius = = 0.0f )
{
radius = DEF_NAV_VIEW_DISTANCE ;
}
collector . m_area . EnsureCapacity ( 1000 ) ;
TheNavMesh - > ForAllAreasInRadius ( collector , GetCenter ( ) , radius ) ;
NavVisPair_t visPair ;
UtlHashHandle_t hHash ;
// First eliminate the ones already calculated
for ( int i = collector . m_area . Count ( ) - 1 ; i > = 0 ; - - i )
{
visPair . SetPair ( this , collector . m_area [ i ] ) ;
hHash = g_pNavVisPairHash - > Find ( visPair ) ;
if ( hHash ! = g_pNavVisPairHash - > InvalidHandle ( ) )
{
collector . m_area . FastRemove ( i ) ;
}
}
SetupPVS ( ) ;
g_pCurVisArea = this ;
ParallelProcess ( " CNavArea::ComputeVisibilityToMesh " , collector . m_area . Base ( ) , collector . m_area . Count ( ) , & ComputeVisToArea ) ;
m_potentiallyVisibleAreas . EnsureCapacity ( g_ComputedVis . Count ( ) ) ;
while ( g_ComputedVis . Count ( ) )
{
g_ComputedVis . PopItem ( & m_potentiallyVisibleAreas [ m_potentiallyVisibleAreas . AddToTail ( ) ] ) ;
}
FOR_EACH_VEC ( collector . m_area , it )
{
visPair . SetPair ( this , ( CNavArea * ) collector . m_area [ it ] ) ;
Assert ( g_pNavVisPairHash - > Find ( visPair ) = = g_pNavVisPairHash - > InvalidHandle ( ) ) ;
g_pNavVisPairHash - > Insert ( visPair ) ;
}
}
//--------------------------------------------------------------------------------------------------------
/**
* The center and all four corners must ALL be visible
*/
bool CNavArea : : IsEntirelyVisible ( const Vector & eye , CBaseEntity * ignore ) const
{
Vector corner ;
trace_t result ;
CTraceFilterNoNPCsOrPlayer traceFilter ( ignore , COLLISION_GROUP_NONE ) ;
const float offset = 0.75f * HumanHeight ;
// check center
UTIL_TraceLine ( eye , GetCenter ( ) + Vector ( 0 , 0 , offset ) , MASK_NAV_VISION , & traceFilter , & result ) ;
if ( result . fraction < 1.0f )
{
return false ;
}
for ( int c = 0 ; c < NUM_CORNERS ; + + c )
{
corner = GetCorner ( ( NavCornerType ) c ) ;
UTIL_TraceLine ( eye , corner + Vector ( 0 , 0 , offset ) , MASK_NAV_VISION , & traceFilter , & result ) ;
if ( result . fraction < 1.0f )
{
return false ;
}
}
// all points are visible
return true ;
}
//--------------------------------------------------------------------------------------------------------
/**
* The center or any of the four corners may be visible
*/
bool CNavArea : : IsPartiallyVisible ( const Vector & eye , CBaseEntity * ignore ) const
{
Vector corner ;
trace_t result ;
CTraceFilterNoNPCsOrPlayer traceFilter ( ignore , COLLISION_GROUP_NONE ) ;
const float offset = 0.75f * HumanHeight ;
// check center
UTIL_TraceLine ( eye , GetCenter ( ) + Vector ( 0 , 0 , offset ) , MASK_NAV_VISION , & traceFilter , & result ) ;
if ( result . fraction > = 1.0f )
{
return true ;
}
Vector eyeToCenter ( GetCenter ( ) + Vector ( 0 , 0 , offset ) - eye ) ;
eyeToCenter . NormalizeInPlace ( ) ;
float angleTolerance = nav_potentially_visible_dot_tolerance . GetFloat ( ) ; // if corner-to-eye angles are this close to center-to-eye angles, assume the same result and skip the trace
for ( int c = 0 ; c < NUM_CORNERS ; + + c )
{
corner = GetCorner ( ( NavCornerType ) c ) + Vector ( 0 , 0 , offset ) ;
// Optimization - treat traces that are effectively collinear as the same
Vector eyeToCorner ( corner - eye ) ;
eyeToCorner . NormalizeInPlace ( ) ;
if ( eyeToCorner . Dot ( eyeToCenter ) > = angleTolerance )
{
continue ;
}
UTIL_TraceLine ( eye , corner + Vector ( 0 , 0 , offset ) , MASK_NAV_VISION , & traceFilter , & result ) ;
if ( result . fraction > = 1.0f )
{
return true ;
}
}
// nothing is visible
return false ;
}
//--------------------------------------------------------------------------------------------------------
bool CNavArea : : IsPotentiallyVisible ( const CNavArea * viewedArea ) const
{
VPROF_BUDGET ( " CNavArea::IsPotentiallyVisible " , " NextBot " ) ;
if ( viewedArea = = NULL )
{
return false ;
}
// can always see ourselves
if ( viewedArea = = this )
{
return true ;
}
// normal visibility check
for ( int i = 0 ; i < m_potentiallyVisibleAreas . Count ( ) ; + + i )
{
if ( m_potentiallyVisibleAreas [ i ] . area = = viewedArea )
{
// Found area in our list. We might be a delta from another list,
// and NOT_VISIBLE overrides that list.
return ( m_potentiallyVisibleAreas [ i ] . attributes ! = NOT_VISIBLE ) ;
}
}
// viewedArea is not in our visibility list, check inherited set
if ( m_inheritVisibilityFrom . area )
{
CAreaBindInfoArray & inherited = m_inheritVisibilityFrom . area - > m_potentiallyVisibleAreas ;
for ( int i = 0 ; i < inherited . Count ( ) ; + + i )
{
if ( inherited [ i ] . area = = viewedArea )
{
return ( inherited [ i ] . attributes ! = NOT_VISIBLE ) ;
}
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------
bool CNavArea : : IsCompletelyVisible ( const CNavArea * viewedArea ) const
{
VPROF_BUDGET ( " CNavArea::IsCompletelyVisible " , " NextBot " ) ;
if ( viewedArea = = NULL )
{
return false ;
}
// can always see ourselves
if ( viewedArea = = this )
{
return true ;
}
// normal visibility check
for ( int i = 0 ; i < m_potentiallyVisibleAreas . Count ( ) ; + + i )
{
if ( m_potentiallyVisibleAreas [ i ] . area = = viewedArea )
{
// our list is definitive - viewedArea is in our list, but is not completely visible
return ( m_potentiallyVisibleAreas [ i ] . attributes & COMPLETELY_VISIBLE ) ? true : false ;
}
}
// viewedArea is not in our visibility list, check inherited set
if ( m_inheritVisibilityFrom . area )
{
CAreaBindInfoArray & inherited = m_inheritVisibilityFrom . area - > m_potentiallyVisibleAreas ;
for ( int i = 0 ; i < inherited . Count ( ) ; + + i )
{
if ( inherited [ i ] . area = = viewedArea )
{
return ( inherited [ i ] . attributes & COMPLETELY_VISIBLE ) ? true : false ;
}
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------
/**
* Return true if any portion of this area is visible to anyone on the given team
*/
bool CNavArea : : IsPotentiallyVisibleToTeam ( int teamIndex ) const
{
VPROF_BUDGET ( " CNavArea::IsPotentiallyVisibleToTeam " , " NextBot " ) ;
CTeam * team = GetGlobalTeam ( teamIndex ) ;
for ( int i = 0 ; i < team - > GetNumPlayers ( ) ; + + i )
{
if ( team - > GetPlayer ( i ) - > IsAlive ( ) )
{
CNavArea * from = ( CNavArea * ) team - > GetPlayer ( i ) - > GetLastKnownArea ( ) ;
if ( from & & from - > IsPotentiallyVisible ( this ) )
{
return true ;
}
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------
/**
* Return true if given area is completely visible from somewhere in this area by someone on the team ( very fast )
*/
bool CNavArea : : IsCompletelyVisibleToTeam ( int teamIndex ) const
{
VPROF_BUDGET ( " CNavArea::IsCompletelyVisibleToTeam " , " NextBot " ) ;
CTeam * team = GetGlobalTeam ( teamIndex ) ;
for ( int i = 0 ; i < team - > GetNumPlayers ( ) ; + + i )
{
if ( team - > GetPlayer ( i ) - > IsAlive ( ) )
{
CNavArea * from = ( CNavArea * ) team - > GetPlayer ( i ) - > GetLastKnownArea ( ) ;
if ( from & & from - > IsCompletelyVisible ( this ) )
{
return true ;
}
}
}
return false ;
}
//--------------------------------------------------------------------------------------------------------
Vector CNavArea : : GetRandomPoint ( void ) const
{
Extent extent ;
GetExtent ( & extent ) ;
Vector spot ;
spot . x = RandomFloat ( extent . lo . x , extent . hi . x ) ;
spot . y = RandomFloat ( extent . lo . y , extent . hi . y ) ;
spot . z = GetZ ( spot . x , spot . y ) ;
return spot ;
}