// NextBotVisionInterface.cpp
// Implementation of common vision system
// Author: Michael Booth, May 2006
//========= Copyright Valve Corporation, All rights reserved. ============//

#include "cbase.h"

#include "nav.h"
#include "functorutils.h"

#include "NextBot.h"
#include "NextBotVisionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotUtil.h"

#ifdef TERROR
#include "querycache.h"
#endif

#include "tier0/vprof.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
ConVar nb_debug_known_entities( "nb_debug_known_entities", "0", FCVAR_CHEAT, "Show the 'known entities' for the bot that is the current spectator target" );


//------------------------------------------------------------------------------------------
IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
{ 
	Reset();
}


//------------------------------------------------------------------------------------------
/**
 * Reset to initial state
 */
void IVision::Reset( void )
{
	INextBotComponent::Reset();

	m_knownEntityVector.RemoveAll();
	m_lastVisionUpdateTimestamp = 0.0f;
	m_primaryThreat = NULL;

	m_FOV = GetDefaultFieldOfView();
	m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
	
	for( int i=0; i<MAX_TEAMS; ++i )
	{
		m_notVisibleTimer[i].Invalidate();
	}
}


//------------------------------------------------------------------------------------------
/**
 * Ask the current behavior to select the most dangerous threat from
 * our set of currently known entities
 * TODO: Find a semantically better place for this to live.
 */
const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
{
	if ( m_knownEntityVector.Count() == 0 )
		return NULL;

	const CKnownEntity *threat = NULL;
	int i;

	// find the first valid entity
	for( i=0; i<m_knownEntityVector.Count(); ++i )
	{
		const CKnownEntity &firstThreat = m_knownEntityVector[i];

		// check in case status changes between updates
		if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
		{
			if ( !onlyVisibleThreats || firstThreat.IsVisibleRecently() )
			{
				threat = &firstThreat;
				break;
			}
		}
	}

	if ( threat == NULL )
	{
		m_primaryThreat = NULL;
		return NULL;
	}

	for( ++i; i<m_knownEntityVector.Count(); ++i )
	{
		const CKnownEntity &newThreat = m_knownEntityVector[i];

		// check in case status changes between updates
		if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
		{
			if ( !onlyVisibleThreats || newThreat.IsVisibleRecently() )
			{
				threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
			}
		}
	}

	// cache off threat
	m_primaryThreat = threat ? threat->GetEntity() : NULL;

	return threat;
}


//------------------------------------------------------------------------------------------
/**
 * Return the closest recognized entity
 */
const CKnownEntity *IVision::GetClosestKnown( int team ) const
{
	const Vector &myPos = GetBot()->GetPosition();
	
	const CKnownEntity *close = NULL;
	float closeRange = 999999999.9f;
	
	for( int i=0; i < m_knownEntityVector.Count(); ++i )
	{
		const CKnownEntity &known = m_knownEntityVector[i];

		if ( !known.IsObsolete() && IsAwareOf( known ) )
		{
			if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
			{
				Vector to = known.GetLastKnownPosition() - myPos;
				float rangeSq = to.LengthSqr();
				
				if ( rangeSq < closeRange )
				{
					close = &known;
					closeRange = rangeSq;
				}
			}
		}
	}
	
	return close;
}


//------------------------------------------------------------------------------------------
/**
 * Return the closest recognized entity that passes the given filter
 */
const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
{
	const Vector &myPos = GetBot()->GetPosition();

	const CKnownEntity *close = NULL;
	float closeRange = 999999999.9f;

	for( int i=0; i < m_knownEntityVector.Count(); ++i )
	{
		const CKnownEntity &known = m_knownEntityVector[i];

		if ( !known.IsObsolete() && IsAwareOf( known ) )
		{
			if ( filter.IsAllowed( known.GetEntity() ) )
			{
				Vector to = known.GetLastKnownPosition() - myPos;
				float rangeSq = to.LengthSqr();

				if ( rangeSq < closeRange )
				{
					close = &known;
					closeRange = rangeSq;
				}
			}
		}
	}

	return close;	
}


//------------------------------------------------------------------------------------------
/**
 * Given an entity, return our known version of it (or NULL if we don't know of it)
 */
const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
{
	if ( entity == NULL )
		return NULL;

	for( int i=0; i < m_knownEntityVector.Count(); ++i )
	{
		const CKnownEntity &known = m_knownEntityVector[i];

		if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() && !known.IsObsolete() )
		{
			return &known;
		}
	}

	return NULL;
}


//------------------------------------------------------------------------------------------
/**
 * Introduce a known entity into the system. Its position is assumed to be known
 * and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
 * of known entities by being told about them, hearing them, etc.
 */
void IVision::AddKnownEntity( CBaseEntity *entity )
{
	if ( entity == NULL || entity->IsWorld() )
	{
		// the world is not an entity we can deal with
		return;
	}

	CKnownEntity known( entity );

	// only add it if we don't already know of it
	if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
	{
		m_knownEntityVector.AddToTail( known );
	}
}


//------------------------------------------------------------------------------------------
// Remove the given entity from our awareness (whether we know if it or not)
// Useful if we've moved to where we last saw the entity, but it's not there any longer.
void IVision::ForgetEntity( CBaseEntity *forgetMe )
{
	if ( !forgetMe )
		return;

	FOR_EACH_VEC( m_knownEntityVector, it )
	{
		const CKnownEntity &known = m_knownEntityVector[ it ];

		if ( known.GetEntity() && known.GetEntity()->entindex() == forgetMe->entindex() )
		{
			m_knownEntityVector.FastRemove( it );
			return;
		}
	}
}


//------------------------------------------------------------------------------------------
void IVision::ForgetAllKnownEntities( void )
{
	m_knownEntityVector.RemoveAll();
}


//------------------------------------------------------------------------------------------
/**
 * Return the number of entity on the given team known to us closer than rangeLimit
 */
int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
{
	int count = 0;

	FOR_EACH_VEC( m_knownEntityVector, it )
	{
		const CKnownEntity &known = m_knownEntityVector[ it ];

		if ( !known.IsObsolete() && IsAwareOf( known ) )
		{
			if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
			{
				if ( !onlyVisible || known.IsVisibleRecently() )
				{
					if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
					{
						++count;
					}
				}
			}
		}
	}

	return count;
}


//------------------------------------------------------------------------------------------
class PopulateVisibleVector
{
public:
	PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
	{
		m_potentiallyVisible = potentiallyVisible;
	}

	bool operator() ( CBaseEntity *actor )
	{
		m_potentiallyVisible->AddToTail( actor );
		return true;
	}

	CUtlVector< CBaseEntity * > *m_potentiallyVisible;
};


//------------------------------------------------------------------------------------------
/**
 * Populate "potentiallyVisible" with the set of all entities we could potentially see. 
 * Entities in this set will be tested for visibility/recognition in IVision::Update()
 */
void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
{
	potentiallyVisible->RemoveAll();

	// by default, only consider players and other bots as potentially visible
	PopulateVisibleVector populate( potentiallyVisible );
	ForEachActor( populate );
}


//------------------------------------------------------------------------------------------
class CollectVisible
{
public:
	CollectVisible( IVision *vision )
	{
		m_vision = vision;
	}
	
	bool operator() ( CBaseEntity *entity )
	{
		if ( entity &&
			 !m_vision->IsIgnored( entity ) &&
			 entity->IsAlive() &&
			 entity != m_vision->GetBot()->GetEntity() &&
			 m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
		{
			m_recognized.AddToTail( entity );	
		}
			
		return true;
	}
	
	bool Contains( CBaseEntity *entity ) const
	{
		for( int i=0; i < m_recognized.Count(); ++i )
		{
			if ( entity->entindex() == m_recognized[ i ]->entindex() )
			{
				return true;
			}
		}
		return false;
	}
	
	IVision *m_vision;
	CUtlVector< CBaseEntity * > m_recognized;
};


//------------------------------------------------------------------------------------------
void IVision::UpdateKnownEntities( void )
{
	VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );

	// construct set of potentially visible objects
	CUtlVector< CBaseEntity * > potentiallyVisible;
	CollectPotentiallyVisibleEntities( &potentiallyVisible );

	// collect set of visible and recognized entities at this moment
	CollectVisible visibleNow( this );
	FOR_EACH_VEC( potentiallyVisible, pit )
	{
		VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );

		if ( visibleNow( potentiallyVisible[ pit ] ) == false )
			break;
	}
	
	// update known set with new data
	{	VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );

		int i;
		for( i=0; i < m_knownEntityVector.Count(); ++i )
		{
			CKnownEntity &known = m_knownEntityVector[i];

			// clear out obsolete knowledge
			if ( known.GetEntity() == NULL || known.IsObsolete() )
			{
				m_knownEntityVector.Remove( i );
				--i;
				continue;
			}
			
			if ( visibleNow.Contains( known.GetEntity() ) )
			{
				// this visible entity was already known (but perhaps not visible until now)
				known.UpdatePosition();
				known.UpdateVisibilityStatus( true );

				// has our reaction time just elapsed?
				if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
					 m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
				{
					if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
					{
						ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n", 
										gpGlobals->curtime,
										GetBot()->GetDebugIdentifier(),
										known.GetEntity()->GetClassname(),
										known.GetEntity()->entindex() );

						NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
					}

					GetBot()->OnSight( known.GetEntity() );
				}
		
				// restart 'not seen' timer
				m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
			}
			else // known entity is not currently visible
			{
				if ( known.IsVisibleInFOVNow() )
				{
					// previously known and visible entity is now no longer visible
					known.UpdateVisibilityStatus( false );

					// lost sight of this entity
					if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
					{
						ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n", 
										gpGlobals->curtime,
										GetBot()->GetDebugIdentifier(),
										known.GetEntity()->GetClassname(),
										known.GetEntity()->entindex() );
					}

					GetBot()->OnLostSight( known.GetEntity() );
				}

				if ( !known.HasLastKnownPositionBeenSeen() )
				{
					// can we see the entity's last know position?
					if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
					{
						known.MarkLastKnownPositionAsSeen();
					}
				}
			}
		}
	}
		
	// check for new recognizes that were not in the known set
	{	VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );

		int i, j;
		for( i=0; i < visibleNow.m_recognized.Count(); ++i )
		{	
			for( j=0; j < m_knownEntityVector.Count(); ++j )
			{
				if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
				{
					break;
				}
			}
			
			if ( j == m_knownEntityVector.Count() )
			{
				// recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
				CKnownEntity known( visibleNow.m_recognized[i] );
				known.UpdatePosition();
				known.UpdateVisibilityStatus( true );
				m_knownEntityVector.AddToTail( known );
			}
		}
	}

	// debugging
	if ( nb_debug_known_entities.GetBool() )
	{
		CBasePlayer *watcher = UTIL_GetListenServerHost();
		if ( watcher )
		{
			CBaseEntity *subject = watcher->GetObserverTarget();

			if ( subject && GetBot()->IsSelf( subject ) )
			{
				CUtlVector< CKnownEntity > knownVector;
				CollectKnownEntities( &knownVector );

				for( int i=0; i < knownVector.Count(); ++i )
				{
					CKnownEntity &known = knownVector[i];

					if ( GetBot()->IsFriend( known.GetEntity() ) )
					{
						if ( IsAwareOf( known ) )
						{
							if ( known.IsVisibleInFOVNow() )
								NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
							else
								NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
						}
						else
						{
							NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 0, 100, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
						}
					}
					else
					{
						if ( IsAwareOf( known ) )
						{
							if ( known.IsVisibleInFOVNow() )
								NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
							else
								NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
						}
						else
						{
							NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 1.0f, 100, 0, 0, 128, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
						}
					}
				}
			}
		}
	}
}


//------------------------------------------------------------------------------------------
/**
 * Update internal state
 */
void IVision::Update( void )
{
	VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );

/* This adds significantly to bot's reaction times
	// throttle update rate
	if ( !m_scanTimer.IsElapsed() )
	{
		return;
	}

	m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
*/

	if ( nb_blind.GetBool() )
	{
		m_knownEntityVector.RemoveAll();
		return;
	}

	UpdateKnownEntities();

	m_lastVisionUpdateTimestamp = gpGlobals->curtime;
}


//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
{
	VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );

	if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
	{
		return false;
	}

	
	if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
	{
		// lost in the fog
		return false;
	}

	if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
	{
		return false;
	}

	CBaseCombatCharacter *combat = subject->MyCombatCharacterPointer();
	if ( combat )
	{
		CNavArea *subjectArea = combat->GetLastKnownArea();
		CNavArea *myArea = GetBot()->GetEntity()->GetLastKnownArea();
		if ( myArea && subjectArea )
		{
			if ( !myArea->IsPotentiallyVisible( subjectArea ) )
			{
				// subject is not potentially visible, skip the expensive raycast
				return false;
			}
		}
	}

	// do actual line-of-sight trace
	if ( !IsLineOfSightClearToEntity( subject ) )
	{
		return false;
	}

	return IsVisibleEntityNoticed( subject );
}


//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
{
	VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );

	
	if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
	{
		return false;
	}

	if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
	{
		// lost in the fog
		return false;
	}

	if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
	{
		return false;
	}

	// do actual line-of-sight trace	
	return IsLineOfSightClear( pos );
}


//------------------------------------------------------------------------------------------
/**
 * Angle given in degrees
 */
void IVision::SetFieldOfView( float horizAngle )
{
	m_FOV = horizAngle;
	m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
}


//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( const Vector &pos ) const
{
#ifdef CHECK_OLD_CODE_AGAINST_NEW
	bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
	Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
	to.NormalizeInPlace();
	
	float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
	
	if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
	{
		Assert(0);
		bool bCheck2 =
			PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );

	}

	return ( cosDiff > m_cosHalfFOV );
#else
	return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
#endif
	
	return true;
}


//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
{
	/// @todo check more points
	if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
	{
		return true;
	}

	return IsInFieldOfView( subject->EyePosition() );
}


//------------------------------------------------------------------------------------------
/**
 * Return true if the ray to the given point is unobstructed
 */
bool IVision::IsLineOfSightClear( const Vector &pos ) const
{
	VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
	VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );

	trace_t result;
	NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
	
	UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
	
	return ( result.fraction >= 1.0f && !result.startsolid );
}


//------------------------------------------------------------------------------------------
bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
{
#ifdef TERROR
	// TODO: Integration querycache & its dependencies

	VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
	VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );

	bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
														subject, EOFFSET_MODE_WORLDSPACE_CENTER,
														subject, COLLISION_GROUP_NONE,
														MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );

#ifdef USE_NON_CACHE_QUERY
	trace_t result;
	NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
	
	UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
	Assert( result.DidHit() != bClear );
	if ( subject->IsPlayer() && ! bClear )
	{
		UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
		bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
													   EOFFSET_MODE_EYEPOSITION,
													   subject, EOFFSET_MODE_EYEPOSITION,
													   subject, COLLISION_GROUP_NONE,
													   MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, 
													   IgnoreActorsTraceFilterFunction, 1.0 );

		// this WILL assert - the query interface happens at a different time, and has hysteresis.
		Assert( result.DidHit() != bClear );
	}
#endif

	return bClear;

#else

	// TODO: Use plain-old traces until querycache/etc gets integrated
	VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );

	trace_t result;
	NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );

	UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
	if ( result.DidHit() )
	{
		UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );

		if ( result.DidHit() )
		{
			UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
		}
	}

	if ( visibleSpot )
	{
		*visibleSpot = result.endpos;
	}

	return ( result.fraction >= 1.0f && !result.startsolid );

#endif
}


//------------------------------------------------------------------------------------------
/**
 * Are we looking directly at the given position
 */
bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
{
	Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
	to.NormalizeInPlace();

	Vector forward;
	AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );

	return DotProduct( to, forward ) > cosTolerance;
}


//------------------------------------------------------------------------------------------
/**
 * Are we looking directly at the given actor
 */
bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
{
	return IsLookingAt( actor->EyePosition(), cosTolerance );
}