diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/nav_area.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/nav_area.cpp')
| -rw-r--r-- | game/server/nav_area.cpp | 5880 |
1 files changed, 5880 insertions, 0 deletions
diff --git a/game/server/nav_area.cpp b/game/server/nav_area.cpp new file mode 100644 index 0000000..656f675 --- /dev/null +++ b/game/server/nav_area.cpp @@ -0,0 +1,5880 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// nav_area.cpp +// AI Navigation areas +// Author: Michael S. Booth ([email protected]), 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; + m_costSoFar = 0.0f; + m_pathLengthSoFar = 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(); + + m_nVisTestCounter = (uint32)-1; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * 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 + CNavArea *area, *last = NULL; + for( area = m_openList; area; area = area->m_nextOpen ) + { + if ( GetTotalCost() < area->GetTotalCost() ) + { + 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, "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 ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + 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, const 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, const 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; +} + + + + + + + + + + + + + + + + |