You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5879 lines
151 KiB
5879 lines
151 KiB
//========= 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 ¢er = 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 2D 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 2D 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 2D 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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|