|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "trains.h"
#include "ai_trackpather.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define TRACKPATHER_DEBUG_LEADING 1
#define TRACKPATHER_DEBUG_PATH 2
#define TRACKPATHER_DEBUG_TRACKS 3
ConVar g_debug_trackpather( "g_debug_trackpather", "0", FCVAR_CHEAT );
//------------------------------------------------------------------------------
BEGIN_DATADESC( CAI_TrackPather ) DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecGoalOrientation, FIELD_VECTOR ),
DEFINE_FIELD( m_pCurrentPathTarget, FIELD_CLASSPTR ), DEFINE_FIELD( m_pDestPathTarget, FIELD_CLASSPTR ), DEFINE_FIELD( m_pLastPathTarget, FIELD_CLASSPTR ), DEFINE_FIELD( m_pTargetNearestPath, FIELD_CLASSPTR ), DEFINE_FIELD( m_strCurrentPathName, FIELD_STRING ), DEFINE_FIELD( m_strDestPathName, FIELD_STRING ), DEFINE_FIELD( m_strLastPathName, FIELD_STRING ), DEFINE_FIELD( m_strTargetNearestPathName, FIELD_STRING ), DEFINE_FIELD( m_vecLastGoalCheckPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_flEnemyPathUpdateTime, FIELD_TIME ), DEFINE_FIELD( m_bForcedMove, FIELD_BOOLEAN ), DEFINE_FIELD( m_bPatrolling, FIELD_BOOLEAN ), DEFINE_FIELD( m_bPatrolBreakable, FIELD_BOOLEAN ), DEFINE_FIELD( m_bLeading, FIELD_BOOLEAN ),
// Derived class pathing data
DEFINE_FIELD( m_flTargetDistanceThreshold, FIELD_FLOAT ), DEFINE_FIELD( m_flAvoidDistance, FIELD_FLOAT ),
DEFINE_FIELD( m_flTargetTolerance, FIELD_FLOAT ), DEFINE_FIELD( m_vecSegmentStartPoint, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecSegmentStartSplinePoint, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_bMovingForward, FIELD_BOOLEAN ), DEFINE_FIELD( m_bChooseFarthestPoint, FIELD_BOOLEAN ), DEFINE_FIELD( m_flFarthestPathDist, FIELD_FLOAT ), DEFINE_FIELD( m_flPathMaxSpeed, FIELD_FLOAT ), DEFINE_FIELD( m_flTargetDistFromPath, FIELD_FLOAT ), DEFINE_FIELD( m_flLeadDistance, FIELD_FLOAT ), DEFINE_FIELD( m_vecTargetPathDir, FIELD_VECTOR ), DEFINE_FIELD( m_vecTargetPathPoint, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_nPauseState, FIELD_INTEGER ),
// Inputs
DEFINE_INPUTFUNC( FIELD_STRING, "SetTrack", InputSetTrack ), DEFINE_INPUTFUNC( FIELD_STRING, "FlyToSpecificTrackViaPath", InputFlyToPathTrack ), DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrol", InputStartPatrol ), DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrol", InputStopPatrol ), DEFINE_INPUTFUNC( FIELD_VOID, "StartBreakableMovement", InputStartBreakableMovement ), DEFINE_INPUTFUNC( FIELD_VOID, "StopBreakableMovement", InputStopBreakableMovement ), DEFINE_INPUTFUNC( FIELD_VOID, "ChooseFarthestPathPoint", InputChooseFarthestPathPoint ), DEFINE_INPUTFUNC( FIELD_VOID, "ChooseNearestPathPoint", InputChooseNearestPathPoint ), DEFINE_INPUTFUNC( FIELD_INTEGER,"InputStartLeading", InputStartLeading ), DEFINE_INPUTFUNC( FIELD_VOID, "InputStopLeading", InputStopLeading ),
// Obsolete, for backwards compatibility
DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolBreakable", InputStartPatrolBreakable ), DEFINE_INPUTFUNC( FIELD_STRING, "FlyToPathTrack", InputFlyToPathTrack ), END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Initialize pathing data
//-----------------------------------------------------------------------------
void CAI_TrackPather::InitPathingData( float flTrackArrivalTolerance, float flTargetDistance, float flAvoidDistance ) { m_flTargetTolerance = flTrackArrivalTolerance; m_flTargetDistanceThreshold = flTargetDistance; m_flAvoidDistance = flAvoidDistance;
m_pCurrentPathTarget = NULL; m_pDestPathTarget = NULL; m_pLastPathTarget = NULL; m_pTargetNearestPath = NULL; m_bLeading = false;
m_flEnemyPathUpdateTime = gpGlobals->curtime; m_bForcedMove = false; m_bPatrolling = false; m_bPatrolBreakable = false; m_flLeadDistance = 0.0f; m_bMovingForward = true; m_vecSegmentStartPoint = m_vecSegmentStartSplinePoint = m_vecDesiredPosition = GetAbsOrigin(); m_bChooseFarthestPoint = true; m_flFarthestPathDist = 1e10; m_flPathMaxSpeed = 0; m_nPauseState = PAUSE_NO_PAUSE; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::OnRestore( void ) { BaseClass::OnRestore();
// Restore current path
if ( m_strCurrentPathName != NULL_STRING ) { m_pCurrentPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strCurrentPathName ); } else { m_pCurrentPathTarget = NULL; }
// Restore destination path
if ( m_strDestPathName != NULL_STRING ) { m_pDestPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strDestPathName ); } else { m_pDestPathTarget = NULL; }
// Restore last path
if ( m_strLastPathName != NULL_STRING ) { m_pLastPathTarget = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strLastPathName ); } else { m_pLastPathTarget = NULL; }
// Restore target nearest path
if ( m_strTargetNearestPathName != NULL_STRING ) { m_pTargetNearestPath = (CPathTrack *) gEntList.FindEntityByName( NULL, m_strTargetNearestPathName ); } else { m_pTargetNearestPath = NULL; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::OnSave( IEntitySaveUtils *pUtils ) { BaseClass::OnSave( pUtils );
// Stash all the paths into strings for restoration later
m_strCurrentPathName = ( m_pCurrentPathTarget != NULL ) ? m_pCurrentPathTarget->GetEntityName() : NULL_STRING; m_strDestPathName = ( m_pDestPathTarget != NULL ) ? m_pDestPathTarget->GetEntityName() : NULL_STRING; m_strLastPathName = ( m_pLastPathTarget != NULL ) ? m_pLastPathTarget->GetEntityName() : NULL_STRING; m_strTargetNearestPathName = ( m_pTargetNearestPath != NULL ) ? m_pTargetNearestPath->GetEntityName() : NULL_STRING; }
//-----------------------------------------------------------------------------
// Leading distance
//-----------------------------------------------------------------------------
void CAI_TrackPather::EnableLeading( bool bEnable ) { bool bWasLeading = m_bLeading; m_bLeading = bEnable; if ( m_bLeading ) { m_bPatrolling = false; } else if ( bWasLeading ) { // Going from leading to not leading. Refresh the desired position
// to prevent us from hovering around our old, no longer valid lead position.
if ( m_pCurrentPathTarget ) { SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); } } }
void CAI_TrackPather::SetLeadingDistance( float flLeadDistance ) { m_flLeadDistance = flLeadDistance; }
float CAI_TrackPather::GetLeadingDistance( ) const { return m_flLeadDistance; }
//-----------------------------------------------------------------------------
// Returns the next path along our current path
//-----------------------------------------------------------------------------
inline CPathTrack *CAI_TrackPather::NextAlongCurrentPath( CPathTrack *pPath ) const { return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetNext() : pPath->GetPrevious() ); }
inline CPathTrack *CAI_TrackPather::PreviousAlongCurrentPath( CPathTrack *pPath ) const { return CPathTrack::ValidPath( m_bMovingForward ? pPath->GetPrevious() : pPath->GetNext() ); }
inline CPathTrack *CAI_TrackPather::AdjustForMovementDirection( CPathTrack *pPath ) const { if ( !m_bMovingForward && CPathTrack::ValidPath( pPath->GetPrevious( ) ) ) { pPath = CPathTrack::ValidPath( pPath->GetPrevious() ); } return pPath; }
//-----------------------------------------------------------------------------
// Enemy visibility check
//-----------------------------------------------------------------------------
CBaseEntity *CAI_TrackPather::FindTrackBlocker( const Vector &vecViewPoint, const Vector &vecTargetPos ) { trace_t tr; AI_TraceHull( vecViewPoint, vecTargetPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); return (tr.fraction != 1.0f) ? tr.m_pEnt : NULL; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &targetPos -
// Output : CBaseEntity
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::BestPointOnPath( CPathTrack *pPath, const Vector &targetPos, float flAvoidRadius, bool visible, bool bFarthestPoint ) { // Find the node nearest to the destination path target if a path is not specified
if ( pPath == NULL ) { pPath = m_pDestPathTarget; }
// If the path node we're trying to use is not valid, then we're done.
if ( CPathTrack::ValidPath( pPath ) == NULL ) { //FIXME: Implement
Assert(0); return NULL; }
// Our target may be in a vehicle
CBaseEntity *pVehicle = NULL; CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); if ( pTargetEnt != NULL ) { CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) { pVehicle = pCCTarget->GetVehicleEntity(); } }
// Faster math...
flAvoidRadius *= flAvoidRadius;
// Find the nearest node to the target (going forward)
CPathTrack *pNearestPath = NULL; float flNearestDist = bFarthestPoint ? 0 : 999999999; float flPathDist;
float flFarthestDistSqr = ( m_flFarthestPathDist - 2.0f * m_flTargetDistanceThreshold ); flFarthestDistSqr *= flFarthestDistSqr;
// NOTE: Gotta do it this crazy way because paths can be one-way.
for ( int i = 0; i < 2; ++i ) { int loopCheck = 0; CPathTrack *pTravPath = pPath; CPathTrack *pNextPath;
BEGIN_PATH_TRACK_ITERATION(); for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) break;
pTravPath->Visit();
pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext();
// Find the distance between this test point and our goal point
flPathDist = ( pTravPath->GetAbsOrigin() - targetPos ).LengthSqr();
// See if it's closer and it's also not within our avoidance radius
if ( bFarthestPoint ) { if ( ( flPathDist <= flNearestDist ) && ( flNearestDist <= flFarthestDistSqr ) ) continue; } else { if ( flPathDist >= flNearestDist ) continue; }
// Don't choose points that are within the avoid radius
if ( flAvoidRadius && ( pTravPath->GetAbsOrigin() - targetPos ).Length2DSqr() <= flAvoidRadius ) continue;
if ( visible ) { // If it has to be visible, run those checks
CBaseEntity *pBlocker = FindTrackBlocker( pTravPath->GetAbsOrigin(), targetPos );
// Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle
bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || ( pVehicle && ( pVehicle == pBlocker ) );
// If we hit something, and it wasn't the target or his vehicle, then no dice
// If we hit the target and forced move was set, *still* no dice
if ( (pBlocker != NULL) && ( !bHitTarget || m_bForcedMove ) ) continue; }
pNearestPath = pTravPath; flNearestDist = flPathDist; } }
return pNearestPath; }
//-----------------------------------------------------------------------------
// Compute a point n units along a path
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::ComputeLeadingPointAlongPath( const Vector &vecStartPoint, CPathTrack *pFirstTrack, float flDistance, Vector *pTarget ) { bool bMovingForward = (flDistance > 0.0f); flDistance = fabs(flDistance);
CPathTrack *pTravPath = pFirstTrack; if ( (!bMovingForward) && pFirstTrack->GetPrevious() ) { pTravPath = pFirstTrack->GetPrevious(); }
*pTarget = vecStartPoint; CPathTrack *pNextPath;
// No circular loop checking needed; eventually, it'll run out of distance
for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) { pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious();
// Find the distance between this test point and our goal point
float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() );
// Find the distance between this test point and our goal point
if ( flPathDist <= flDistance ) { flDistance -= flPathDist; *pTarget = pTravPath->GetAbsOrigin(); if ( !CPathTrack::ValidPath(pNextPath) ) return bMovingForward ? pTravPath : pTravPath->GetNext();
continue; } ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); return bMovingForward ? pTravPath : pTravPath->GetNext(); }
return NULL; }
//-----------------------------------------------------------------------------
// Compute the distance to a particular point on the path
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceAlongPathToPoint( CPathTrack *pStartTrack, CPathTrack *pDestTrack, const Vector &vecDestPosition, bool bMovingForward ) { float flTotalDist = 0.0f;
Vector vecPoint; ClosestPointToCurrentPath( &vecPoint );
CPathTrack *pTravPath = pStartTrack; CPathTrack *pNextPath, *pTestPath; BEGIN_PATH_TRACK_ITERATION(); for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) break;
// Mark it as being visited.
pTravPath->Visit();
pNextPath = bMovingForward ? pTravPath->GetNext() : pTravPath->GetPrevious(); pTestPath = pTravPath; Assert( pTestPath );
if ( pTravPath == pDestTrack ) { Vector vecDelta; Vector vecPathDelta; VectorSubtract( vecDestPosition, vecPoint, vecDelta ); ComputePathDirection( pTravPath, &vecPathDelta ); float flDot = DotProduct( vecDelta, vecPathDelta ); flTotalDist += (flDot > 0.0f ? 1.0f : -1.0f) * vecDelta.Length2D(); break; }
// NOTE: This would be made more accurate if we did the path direction check here too.
// The starting vecPoint is sometimes *not* within the bounds of the line segment.
// Find the distance between this test point and our goal point
flTotalDist += (bMovingForward ? 1.0f : -1.0f) * vecPoint.AsVector2D().DistTo( pTestPath->GetAbsOrigin().AsVector2D() ); vecPoint = pTestPath->GetAbsOrigin(); }
return flTotalDist; }
//------------------------------------------------------------------------------
// Track debugging info
//------------------------------------------------------------------------------
void CAI_TrackPather::VisualizeDebugInfo( const Vector &vecNearestPoint, const Vector &vecTarget ) { if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_PATH ) { NDebugOverlay::Line( m_vecSegmentStartPoint, vecTarget, 0, 0, 255, true, 0.1f ); NDebugOverlay::Cross3D( vecNearestPoint, -Vector(16,16,16), Vector(16,16,16), 255, 0, 0, true, 0.1f ); NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 0, 255, 0, true, 0.1f ); NDebugOverlay::Cross3D( m_vecDesiredPosition, -Vector(16,16,16), Vector(16,16,16), 0, 0, 255, true, 0.1f ); NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(16,16,16), Vector(16,16,16), 255, 255, 255, true, 0.1f );
if ( m_pTargetNearestPath ) { NDebugOverlay::Cross3D( m_pTargetNearestPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 0, 255, true, 0.1f ); } }
if ( g_debug_trackpather.GetInt() == TRACKPATHER_DEBUG_TRACKS ) { if ( m_pCurrentPathTarget ) { CPathTrack *pPathTrack = m_pCurrentPathTarget; for ( ; CPathTrack::ValidPath( pPathTrack ); pPathTrack = pPathTrack->GetNext() ) { NDebugOverlay::Box( pPathTrack->GetAbsOrigin(), -Vector(2,2,2), Vector(2,2,2), 0,255, 0, 8, 0.1 ); if ( CPathTrack::ValidPath( pPathTrack->GetNext() ) ) { NDebugOverlay::Line( pPathTrack->GetAbsOrigin(), pPathTrack->GetNext()->GetAbsOrigin(), 0,255,0, true, 0.1 ); }
if ( pPathTrack->GetNext() == m_pCurrentPathTarget ) break; } } } }
//------------------------------------------------------------------------------
// Does this path track have LOS to the target?
//------------------------------------------------------------------------------
bool CAI_TrackPather::HasLOSToTarget( CPathTrack *pTrack ) { CBaseEntity *pTargetEnt = GetTrackPatherTargetEnt(); if ( !pTargetEnt ) return true;
Vector targetPos; if ( !GetTrackPatherTarget( &targetPos ) ) return true;
// Translate driver into vehicle for testing
CBaseEntity *pVehicle = NULL; CBaseCombatCharacter *pCCTarget = pTargetEnt->MyCombatCharacterPointer(); if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) { pVehicle = pCCTarget->GetVehicleEntity(); }
// If it has to be visible, run those checks
CBaseEntity *pBlocker = FindTrackBlocker( pTrack->GetAbsOrigin(), targetPos );
// Check to see if we've hit the target, or the player's vehicle if it's a player in a vehicle
bool bHitTarget = ( pTargetEnt && ( pTargetEnt == pBlocker ) ) || ( pVehicle && ( pVehicle == pBlocker ) );
return (pBlocker == NULL) || bHitTarget; }
//------------------------------------------------------------------------------
// Moves to the track
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateCurrentTarget() { // Find the point along the line that we're closest to.
const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); Vector vecPoint; float t = ClosestPointToCurrentPath( &vecPoint ); if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) goto visualizeDebugInfo;
// Forced move is gone as soon as we've reached the first point on our path
if ( m_bLeading ) { m_bForcedMove = false; }
// Trip our "path_track reached" output
if ( m_pCurrentPathTarget != m_pLastPathTarget ) { // Get the path's specified max speed
m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed;
variant_t emptyVariant; m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); m_pLastPathTarget = m_pCurrentPathTarget; }
if ( m_nPauseState == PAUSED_AT_POSITION ) return;
if ( m_nPauseState == PAUSE_AT_NEXT_LOS_POSITION ) { if ( HasLOSToTarget(m_pCurrentPathTarget) ) { m_nPauseState = PAUSED_AT_POSITION; return; } }
// Update our dest path target, if appropriate...
if ( m_pCurrentPathTarget == m_pDestPathTarget ) { m_bForcedMove = false; SelectNewDestTarget(); }
// Did SelectNewDestTarget give us a new point to move to?
if ( m_pCurrentPathTarget != m_pDestPathTarget ) { // Update to the next path, if there is one...
m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); if ( !m_pCurrentPathTarget ) { m_pCurrentPathTarget = m_pLastPathTarget; } } else { // We're at rest (no patrolling behavior), which means we're moving forward now.
m_bMovingForward = true; }
SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); m_vecSegmentStartSplinePoint = m_vecSegmentStartPoint; m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin();
visualizeDebugInfo: VisualizeDebugInfo( vecPoint, vecTarget ); }
//-----------------------------------------------------------------------------
//
// NOTE: All code below is used exclusively for leading/trailing behavior
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Compute the distance to the leading position
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceToLeadingPosition() { return ComputeDistanceAlongPathToPoint( m_pCurrentPathTarget, m_pDestPathTarget, GetDesiredPosition(), m_bMovingForward ); }
//-----------------------------------------------------------------------------
// Compute the distance to the *target* position
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputeDistanceToTargetPosition() { Assert( m_pTargetNearestPath );
CPathTrack *pDest = m_bMovingForward ? m_pTargetNearestPath.Get() : m_pTargetNearestPath->GetPrevious(); if ( !pDest ) { pDest = m_pTargetNearestPath; } bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest );
CPathTrack *pStart = m_pCurrentPathTarget; if ( bMovingForward != m_bMovingForward ) { if (bMovingForward) { if ( pStart->GetNext() ) { pStart = pStart->GetNext(); } if ( pDest->GetNext() ) { pDest = pDest->GetNext(); } } else { if ( pStart->GetPrevious() ) { pStart = pStart->GetPrevious(); } if ( pDest->GetPrevious() ) { pDest = pDest->GetPrevious(); } } }
return ComputeDistanceAlongPathToPoint( pStart, pDest, m_vecTargetPathPoint, bMovingForward ); }
//-----------------------------------------------------------------------------
// Compute a path direction
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePathDirection( CPathTrack *pPath, Vector *pVecPathDir ) { if ( pPath->GetPrevious() ) { VectorSubtract( pPath->GetAbsOrigin(), pPath->GetPrevious()->GetAbsOrigin(), *pVecPathDir ); } else { if ( pPath->GetNext() ) { VectorSubtract( pPath->GetNext()->GetAbsOrigin(), pPath->GetAbsOrigin(), *pVecPathDir ); } else { pVecPathDir->Init( 1, 0, 0 ); } } VectorNormalize( *pVecPathDir ); }
//-----------------------------------------------------------------------------
// What's the current path direction?
//-----------------------------------------------------------------------------
void CAI_TrackPather::CurrentPathDirection( Vector *pVecPathDir ) { if ( m_pCurrentPathTarget ) { ComputePathDirection( m_pCurrentPathTarget, pVecPathDir ); } else { pVecPathDir->Init( 0, 0, 1 ); } }
//-----------------------------------------------------------------------------
// Compute a point n units along the current path from our current position
// (but don't pass the desired target point)
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePointAlongCurrentPath( float flDistance, float flPerpDist, Vector *pTarget ) { Vector vecPathDir; Vector vecStartPoint; ClosestPointToCurrentPath( &vecStartPoint ); *pTarget = vecStartPoint;
if ( flDistance != 0.0f ) { Vector vecPrevPoint = vecStartPoint; CPathTrack *pTravPath = m_pCurrentPathTarget; CPathTrack *pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = NextAlongCurrentPath( pTravPath ) ) { if ( pTravPath == pAdjustedDest ) { ComputePathDirection( pTravPath, &vecPathDir );
float flPathDist = pTarget->DistTo( GetDesiredPosition() ); if ( flDistance > flPathDist ) { *pTarget = GetDesiredPosition(); } else { ComputeClosestPoint( *pTarget, flDistance, GetDesiredPosition(), pTarget ); } break; }
// Find the distance between this test point and our goal point
float flPathDist = pTarget->DistTo( pTravPath->GetAbsOrigin() );
// Find the distance between this test point and our goal point
if ( flPathDist <= flDistance ) { flDistance -= flPathDist; *pTarget = pTravPath->GetAbsOrigin();
// FIXME: Reduce the distance further based on the angle between this segment + the next
continue; } ComputePathDirection( pTravPath, &vecPathDir ); ComputeClosestPoint( *pTarget, flDistance, pTravPath->GetAbsOrigin(), pTarget ); break; } } else { VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecPathDir ); VectorNormalize( vecPathDir ); }
// Add in the horizontal component
ComputePointFromPerpDistance( *pTarget, vecPathDir, flPerpDist, pTarget ); }
//-----------------------------------------------------------------------------
// Methods to find a signed perp distance from the track
// and to compute a point off the path based on the signed perp distance
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputePerpDistanceFromPath( const Vector &vecPointOnPath, const Vector &vecPathDir, const Vector &vecPointOffPath ) { // Make it be a signed distance of the target from the path
// Positive means on the right side, negative means on the left side
Vector vecAcross, vecDelta; CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); VectorSubtract( vecPointOffPath, vecPointOnPath, vecDelta ); VectorMA( vecDelta, -DotProduct( vecPathDir, vecDelta ), vecPathDir, vecDelta );
float flDistanceFromPath = vecDelta.Length2D(); if ( DotProduct2D( vecAcross.AsVector2D(), vecDelta.AsVector2D() ) < 0.0f ) { flDistanceFromPath *= -1.0f; }
return flDistanceFromPath; }
void CAI_TrackPather::ComputePointFromPerpDistance( const Vector &vecPointOnPath, const Vector &vecPathDir, float flPerpDist, Vector *pResult ) { Vector vecAcross; CrossProduct( vecPathDir, Vector( 0, 0, 1 ), vecAcross ); VectorMA( vecPointOnPath, flPerpDist, vecAcross, *pResult ); }
//-----------------------------------------------------------------------------
// Finds the closest point on the path, returns a signed perpendicular distance
// where negative means on the left side of the path (when travelled from prev to next)
// and positive means on the right side
//-----------------------------------------------------------------------------
CPathTrack *CAI_TrackPather::FindClosestPointOnPath( CPathTrack *pPath, const Vector &targetPos, Vector *pVecClosestPoint, Vector *pVecPathDir, float *pDistanceFromPath ) { // Find the node nearest to the destination path target if a path is not specified
if ( pPath == NULL ) { pPath = m_pDestPathTarget; }
// If the path node we're trying to use is not valid, then we're done.
if ( CPathTrack::ValidPath( pPath ) == NULL ) { //FIXME: Implement
Assert(0); return NULL; }
// Find the nearest node to the target (going forward)
CPathTrack *pNearestPath = NULL; float flNearestDist2D = 999999999; float flNearestDist = 999999999; float flPathDist, flPathDist2D;
// NOTE: Gotta do it this crazy way because paths can be one-way.
Vector vecNearestPoint; Vector vecNearestPathSegment; for ( int i = 0; i < 2; ++i ) { int loopCheck = 0; CPathTrack *pTravPath = pPath; CPathTrack *pNextPath;
BEGIN_PATH_TRACK_ITERATION(); for ( ; CPathTrack::ValidPath( pTravPath ); pTravPath = pNextPath, loopCheck++ ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) break;
// Mark it as being visited.
pTravPath->Visit();
pNextPath = (i == 0) ? pTravPath->GetPrevious() : pTravPath->GetNext();
// No alt paths allowed in leading mode.
if ( pTravPath->m_paltpath ) { Warning( "%s: Alternative paths in path_track not allowed when using the leading behavior!\n", GetEntityName().ToCStr() ); }
// Need line segments
if ( !CPathTrack::ValidPath(pNextPath) ) break;
// Find the closest point on the line segment on the path
Vector vecClosest; CalcClosestPointOnLineSegment( targetPos, pTravPath->GetAbsOrigin(), pNextPath->GetAbsOrigin(), vecClosest );
// Find the distance between this test point and our goal point
flPathDist2D = vecClosest.AsVector2D().DistToSqr( targetPos.AsVector2D() ); if ( flPathDist2D > flNearestDist2D ) continue;
flPathDist = vecClosest.z - targetPos.z; flPathDist *= flPathDist; flPathDist += flPathDist2D; if (( flPathDist2D == flNearestDist2D ) && ( flPathDist >= flNearestDist )) continue;
pNearestPath = (i == 0) ? pTravPath : pNextPath; flNearestDist2D = flPathDist2D; flNearestDist = flPathDist; vecNearestPoint = vecClosest; VectorSubtract( pNextPath->GetAbsOrigin(), pTravPath->GetAbsOrigin(), vecNearestPathSegment ); if ( i == 0 ) { vecNearestPathSegment *= -1.0f; } } }
VectorNormalize( vecNearestPathSegment ); *pDistanceFromPath = ComputePerpDistanceFromPath( vecNearestPoint, vecNearestPathSegment, targetPos ); *pVecClosestPoint = vecNearestPoint; *pVecPathDir = vecNearestPathSegment; return pNearestPath; }
//-----------------------------------------------------------------------------
// Breakable paths?
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartBreakableMovement( inputdata_t &inputdata ) { m_bPatrolBreakable = true; }
void CAI_TrackPather::InputStopBreakableMovement( inputdata_t &inputdata ) { m_bPatrolBreakable = false; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartPatrol( inputdata_t &inputdata ) { m_bPatrolling = true; }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStopPatrol( inputdata_t &inputdata ) { m_bPatrolling = false; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputStartPatrolBreakable( inputdata_t &inputdata ) { m_bPatrolBreakable = true; m_bPatrolling = true; }
//------------------------------------------------------------------------------
// Leading behaviors
//------------------------------------------------------------------------------
void CAI_TrackPather::InputStartLeading( inputdata_t &inputdata ) { EnableLeading( true ); SetLeadingDistance( inputdata.value.Int() ); }
void CAI_TrackPather::InputStopLeading( inputdata_t &inputdata ) { EnableLeading( false ); }
//------------------------------------------------------------------------------
// Selects a new destination target
//------------------------------------------------------------------------------
void CAI_TrackPather::SelectNewDestTarget() { if ( !m_bPatrolling ) return;
// NOTE: This version is bugged, but I didn't want to make the fix
// here for fear of breaking a lot of maps late in the day.
// So, only the chopper does the "right" thing.
#ifdef HL2_EPISODIC
// Episodic uses the fixed logic for all trackpathers
if ( 1 ) #else
if ( ShouldUseFixedPatrolLogic() ) #endif
{ CPathTrack *pOldDest = m_pDestPathTarget;
// Only switch polarity of movement if we're at the *end* of the path
// This is really useful for initial conditions of patrolling
// NOTE: We've got to do some extra work for circular paths
bool bIsCircular = false; { BEGIN_PATH_TRACK_ITERATION(); CPathTrack *pTravPath = m_pDestPathTarget; while( CPathTrack::ValidPath( pTravPath ) ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) { bIsCircular = true; break; }
pTravPath->Visit(); pTravPath = NextAlongCurrentPath( pTravPath ); } }
if ( bIsCircular || (NextAlongCurrentPath( m_pDestPathTarget ) == NULL) ) { m_bMovingForward = !m_bMovingForward; }
BEGIN_PATH_TRACK_ITERATION();
while ( true ) { CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); if ( !pNextTrack || (pNextTrack == pOldDest) || pNextTrack->HasBeenVisited() ) break;
pNextTrack->Visit(); m_pDestPathTarget = pNextTrack; } } else { CPathTrack *pOldDest = m_pDestPathTarget;
// For patrolling, switch the polarity of movement
m_bMovingForward = !m_bMovingForward;
int loopCount = 0; while ( true ) { CPathTrack *pNextTrack = NextAlongCurrentPath( m_pDestPathTarget ); if ( !pNextTrack ) break; if ( ++loopCount > 1024 ) { DevMsg(1,"WARNING: Looping path for %s\n", GetDebugName() ); break; }
m_pDestPathTarget = pNextTrack; }
if ( m_pDestPathTarget == pOldDest ) { // This can occur if we move to the first point on the path
SelectNewDestTarget(); } } }
//------------------------------------------------------------------------------
// Moves to the track
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateCurrentTargetLeading() { bool bRestingAtDest = false; CPathTrack *pAdjustedDest;
// Find the point along the line that we're closest to.
const Vector &vecTarget = m_pCurrentPathTarget->GetAbsOrigin(); Vector vecPoint; float t = ClosestPointToCurrentPath( &vecPoint ); if ( (t < 1.0f) && ( vecPoint.DistToSqr( vecTarget ) > m_flTargetTolerance * m_flTargetTolerance ) ) goto visualizeDebugInfo;
// Trip our "path_track reached" output
if ( m_pCurrentPathTarget != m_pLastPathTarget ) { // Get the path's specified max speed
m_flPathMaxSpeed = m_pCurrentPathTarget->m_flSpeed;
variant_t emptyVariant; m_pCurrentPathTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 ); m_pLastPathTarget = m_pCurrentPathTarget; }
// NOTE: CurrentPathTarget doesn't mean the same thing as dest path target!
// It's the "next"most when moving forward + "prev"most when moving backward
// Must do the tests in the same space
pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget );
// Update our dest path target, if appropriate...
if ( m_pCurrentPathTarget == pAdjustedDest ) { m_bForcedMove = false; SelectNewDestTarget();
// NOTE: Must do this again since SelectNewDestTarget may change m_pDestPathTarget
pAdjustedDest = AdjustForMovementDirection( m_pDestPathTarget ); }
if ( m_pCurrentPathTarget != pAdjustedDest ) { // Update to the next path, if there is one...
m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); if ( !m_pCurrentPathTarget ) { m_pCurrentPathTarget = m_pLastPathTarget; } } else { // NOTE: Have to do this here because the NextAlongCurrentPath call above
// could make m_pCurrentPathTarget == m_pDestPathTarget.
// In this case, we're at rest (no patrolling behavior)
bRestingAtDest = true; }
if ( bRestingAtDest ) { // NOTE: Must use current path target, instead of dest
// to get the PreviousAlongCurrentPath working correctly
CPathTrack *pSegmentStart = PreviousAlongCurrentPath( m_pCurrentPathTarget ); if ( !pSegmentStart ) { pSegmentStart = m_pCurrentPathTarget; } m_vecSegmentStartPoint = pSegmentStart->GetAbsOrigin(); } else { m_vecSegmentStartPoint = m_pLastPathTarget->GetAbsOrigin(); }
visualizeDebugInfo: VisualizeDebugInfo( vecPoint, vecTarget ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::UpdateTargetPositionLeading( void ) { Vector targetPos; if ( !GetTrackPatherTarget( &targetPos ) ) return;
// NOTE: FindClosestPointOnPath *always* returns the point on the "far",
// end of the line segment containing the closest point (namely the 'next'
// track, as opposed to the 'prev' track)
Vector vecClosestPoint, vecPathDir; float flTargetDistanceFromPath; CPathTrack *pNextPath = FindClosestPointOnPath( m_pCurrentPathTarget, targetPos, &vecClosestPoint, &vecPathDir, &flTargetDistanceFromPath );
// This means that a valid path could not be found to our target!
if ( CPathTrack::ValidPath( pNextPath ) == NULL ) return;
// NDebugOverlay::Cross3D( vecClosestPoint, -Vector(24,24,24), Vector(24,24,24), 0, 255, 255, true, 0.1f );
// NDebugOverlay::Cross3D( pNextPath->GetAbsOrigin(), -Vector(24,24,24), Vector(24,24,24), 255, 255, 0, true, 0.1f );
// Here's how far we are from the path
m_flTargetDistFromPath = flTargetDistanceFromPath; m_vecTargetPathDir = vecPathDir;
// Here's info about where the target is along the path
m_vecTargetPathPoint = vecClosestPoint; m_pTargetNearestPath = pNextPath;
// Find the best position to be on our path
// NOTE: This will *also* return a path track on the "far" end of the line segment
// containing the leading position, namely the "next" end of the segment as opposed
// to the "prev" end of the segment.
CPathTrack *pDest = ComputeLeadingPointAlongPath( vecClosestPoint, pNextPath, m_flLeadDistance, &targetPos ); SetDesiredPosition( targetPos );
// We only want to switch movement directions when absolutely necessary
// so convert dest into a more appropriate value based on the current movement direction
if ( pDest != m_pDestPathTarget ) { // NOTE: This is really tricky + subtle
// For leading, we don't want to ever change direction when the current target == the
// adjusted destination target. Namely, if we're going forward, both dest + curr
// mean the "next"most node so we can compare them directly against eath other.
// If we're moving backward, dest means "next"most, but curr means "prev"most.
// We first have to adjust the dest to mean "prev"most, and then do the comparison.
// If the adjusted dest == curr, then maintain direction. Otherwise, use the forward along path test.
bool bMovingForward = m_bMovingForward; CPathTrack *pAdjustedDest = AdjustForMovementDirection( pDest ); if ( m_pCurrentPathTarget != pAdjustedDest ) { bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pAdjustedDest ); }
if ( bMovingForward != m_bMovingForward ) { // As a result of the tricky note above, this should never occur
Assert( pAdjustedDest != m_pCurrentPathTarget );
// Oops! Need to reverse direction
m_bMovingForward = bMovingForward; m_vecSegmentStartPoint = m_pCurrentPathTarget->GetAbsOrigin(); m_pCurrentPathTarget = NextAlongCurrentPath( m_pCurrentPathTarget ); } m_pDestPathTarget = pDest; }
// NDebugOverlay::Cross3D( m_pCurrentPathTarget->GetAbsOrigin(), -Vector(36,36,36), Vector(36,36,36), 255, 0, 0, true, 0.1f );
// NDebugOverlay::Cross3D( m_pDestPathTarget->GetAbsOrigin(), -Vector(48,48,48), Vector(48,48,48), 0, 255, 0, true, 0.1f );
// NDebugOverlay::Cross3D( targetPos, -Vector(36,36,36), Vector(36,36,36), 0, 0, 255, true, 0.1f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAI_TrackPather::UpdateTargetPosition( void ) { // Don't update our target if we're being told to go somewhere
if ( m_bForcedMove && !m_bPatrolBreakable ) return;
// Don't update our target if we're patrolling
if ( m_bPatrolling ) { // If we have an enemy, and our patrol is breakable, stop patrolling
if ( !m_bPatrolBreakable || !GetEnemy() ) return;
m_bPatrolling = false; }
Vector targetPos; if ( !GetTrackPatherTarget( &targetPos ) ) return;
// Not time to update again
if ( m_flEnemyPathUpdateTime > gpGlobals->curtime ) return;
// See if the target has moved enough to make us recheck
float flDistSqr = ( targetPos - m_vecLastGoalCheckPosition ).LengthSqr(); if ( flDistSqr < m_flTargetDistanceThreshold * m_flTargetDistanceThreshold ) return;
// Find the best position to be on our path
CPathTrack *pDest = BestPointOnPath( m_pCurrentPathTarget, targetPos, m_flAvoidDistance, true, m_bChooseFarthestPoint );
if ( CPathTrack::ValidPath( pDest ) == NULL ) { // This means that a valid path could not be found to our target!
// Assert(0);
return; }
if ( pDest != m_pDestPathTarget ) { // This is our new destination
bool bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pDest ); if ( bMovingForward != m_bMovingForward ) { // Oops! Need to reverse direction
m_bMovingForward = bMovingForward; if ( pDest != m_pCurrentPathTarget ) { SetupNewCurrentTarget( NextAlongCurrentPath( m_pCurrentPathTarget ) ); } } m_pDestPathTarget = pDest; }
// Keep this goal point for comparisons later
m_vecLastGoalCheckPosition = targetPos; // Only do this on set intervals
m_flEnemyPathUpdateTime = gpGlobals->curtime + 1.0f; }
//------------------------------------------------------------------------------
// Returns the direction of the path at the closest point to the target
//------------------------------------------------------------------------------
const Vector &CAI_TrackPather::TargetPathDirection() const { return m_vecTargetPathDir; }
const Vector &CAI_TrackPather::TargetPathAcrossDirection() const { static Vector s_Result; CrossProduct( m_vecTargetPathDir, Vector( 0, 0, 1 ), s_Result ); return s_Result; }
//------------------------------------------------------------------------------
// Returns the speed of the target relative to the path
//------------------------------------------------------------------------------
float CAI_TrackPather::TargetSpeedAlongPath() const { if ( !GetEnemy() || !IsLeading() ) return 0.0f;
Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); return DotProduct( vecSmoothedVelocity, TargetPathDirection() ); }
//------------------------------------------------------------------------------
// Returns the speed of the target *across* the path
//------------------------------------------------------------------------------
float CAI_TrackPather::TargetSpeedAcrossPath() const { if ( !GetEnemy() || !IsLeading() ) return 0.0f;
Vector vecSmoothedVelocity = GetEnemy()->GetSmoothedVelocity(); return DotProduct( vecSmoothedVelocity, TargetPathAcrossDirection() ); }
//------------------------------------------------------------------------------
// Returns the max distance we can be from the path
//------------------------------------------------------------------------------
float CAI_TrackPather::MaxDistanceFromCurrentPath() const { if ( !IsLeading() || !m_pCurrentPathTarget ) return 0.0f;
CPathTrack *pPrevPath = PreviousAlongCurrentPath( m_pCurrentPathTarget ); if ( !pPrevPath ) { pPrevPath = m_pCurrentPathTarget; }
// NOTE: Can't use m_vecSegmentStartPoint because we don't have a radius defined for it
float t; Vector vecTemp; CalcClosestPointOnLine( GetAbsOrigin(), pPrevPath->GetAbsOrigin(), m_pCurrentPathTarget->GetAbsOrigin(), vecTemp, &t ); t = clamp( t, 0.0f, 1.0f ); float flRadius = (1.0f - t) * pPrevPath->GetRadius() + t * m_pCurrentPathTarget->GetRadius(); return flRadius; }
//------------------------------------------------------------------------------
// Purpose : A different version of the track pather which is more explicit about
// the meaning of dest, current, and prev path points
//------------------------------------------------------------------------------
void CAI_TrackPather::UpdateTrackNavigation( void ) { // No target? Use the string specified. We have no spawn method (sucky!!) so this is how that works
if ( ( CPathTrack::ValidPath( m_pDestPathTarget ) == NULL ) && ( m_target != NULL_STRING ) ) { FlyToPathTrack( m_target ); m_target = NULL_STRING; }
if ( !IsLeading() ) { if ( !m_pCurrentPathTarget ) return;
// Updates our destination node if we're tracking something
UpdateTargetPosition();
// Move along our path towards our current destination
UpdateCurrentTarget(); } else { // Updates our destination position if we're leading something
UpdateTargetPositionLeading();
// Move along our path towards our current destination
UpdateCurrentTargetLeading(); } }
//------------------------------------------------------------------------------
// Sets the farthest path distance
//------------------------------------------------------------------------------
void CAI_TrackPather::SetFarthestPathDist( float flMaxPathDist ) { m_flFarthestPathDist = flMaxPathDist; }
//------------------------------------------------------------------------------
// Sets up a new current path target
//------------------------------------------------------------------------------
void CAI_TrackPather::SetupNewCurrentTarget( CPathTrack *pTrack ) { Assert( pTrack ); m_vecSegmentStartPoint = GetAbsOrigin(); VectorMA( m_vecSegmentStartPoint, -2.0f, GetAbsVelocity(), m_vecSegmentStartSplinePoint ); m_pCurrentPathTarget = pTrack; SetDesiredPosition( m_pCurrentPathTarget->GetAbsOrigin() ); }
//------------------------------------------------------------------------------
// Moves to an explicit track point
//------------------------------------------------------------------------------
void CAI_TrackPather::MoveToTrackPoint( CPathTrack *pTrack ) { if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) { // The track must be valid
if ( CPathTrack::ValidPath( pTrack ) == NULL ) return;
m_pDestPathTarget = pTrack; m_bMovingForward = IsForwardAlongPath( m_pCurrentPathTarget, pTrack ); m_bForcedMove = true; } else { CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false );
// The track must be valid
if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) return;
SetupNewCurrentTarget( pClosestTrack ); m_pDestPathTarget = pTrack; m_bMovingForward = IsForwardAlongPath( pClosestTrack, pTrack ); m_bForcedMove = true; } }
//------------------------------------------------------------------------------
// Moves to the closest track point
//------------------------------------------------------------------------------
void CAI_TrackPather::MoveToClosestTrackPoint( CPathTrack *pTrack ) { if ( IsOnSameTrack( pTrack, m_pDestPathTarget ) ) return;
CPathTrack *pClosestTrack = BestPointOnPath( pTrack, WorldSpaceCenter(), 0.0f, false, false );
// The track must be valid
if ( CPathTrack::ValidPath( pClosestTrack ) == NULL ) return;
SetupNewCurrentTarget( pClosestTrack ); m_pDestPathTarget = pClosestTrack; m_bMovingForward = true;
// Force us to switch tracks if we're leading
if ( IsLeading() ) { m_bForcedMove = true; } }
//-----------------------------------------------------------------------------
// Are the two path tracks connected?
//-----------------------------------------------------------------------------
bool CAI_TrackPather::IsOnSameTrack( CPathTrack *pPath1, CPathTrack *pPath2 ) const { if ( pPath1 == pPath2 ) return true;
{ BEGIN_PATH_TRACK_ITERATION(); CPathTrack *pTravPath = pPath1->GetPrevious(); while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) break;
pTravPath->Visit();
if ( pTravPath == pPath2 ) return true;
pTravPath = pTravPath->GetPrevious(); } }
{ BEGIN_PATH_TRACK_ITERATION(); CPathTrack *pTravPath = pPath1->GetNext(); while( CPathTrack::ValidPath( pTravPath ) && (pTravPath != pPath1) ) { // Circular loop checking
if ( pTravPath->HasBeenVisited() ) break;
pTravPath->Visit();
if ( pTravPath == pPath2 ) return true;
pTravPath = pTravPath->GetNext(); } }
return false; }
//-----------------------------------------------------------------------------
// Deal with teleportation
//-----------------------------------------------------------------------------
void CAI_TrackPather::Teleported() { // This updates the paths so they are reasonable
CPathTrack *pClosestTrack = BestPointOnPath( GetDestPathTarget(), WorldSpaceCenter(), 0.0f, false, false ); m_pDestPathTarget = NULL; MoveToClosestTrackPoint( pClosestTrack ); }
//-----------------------------------------------------------------------------
// Returns distance along path to target, returns FLT_MAX if there's no path
//-----------------------------------------------------------------------------
float CAI_TrackPather::ComputePathDistance( CPathTrack *pPath, CPathTrack *pDest, bool bForward ) const { float flDist = 0.0f; CPathTrack *pLast = pPath;
BEGIN_PATH_TRACK_ITERATION(); while ( CPathTrack::ValidPath( pPath ) ) { // Ciruclar loop checking
if ( pPath->HasBeenVisited() ) return FLT_MAX;
pPath->Visit();
flDist += pLast->GetAbsOrigin().DistTo( pPath->GetAbsOrigin() );
if ( pDest == pPath ) return flDist;
pLast = pPath; pPath = bForward ? pPath->GetNext() : pPath->GetPrevious(); }
return FLT_MAX; }
//-----------------------------------------------------------------------------
// Is pPathTest in "front" of pPath on the same path? (Namely, does GetNext() get us there?)
//-----------------------------------------------------------------------------
bool CAI_TrackPather::IsForwardAlongPath( CPathTrack *pPath, CPathTrack *pPathTest ) const { // Also, in the case of looping paths, we want to return the shortest path
float flForwardDist = ComputePathDistance( pPath, pPathTest, true ); float flReverseDist = ComputePathDistance( pPath, pPathTest, false );
Assert( ( flForwardDist != FLT_MAX ) || ( flReverseDist != FLT_MAX ) ); return ( flForwardDist <= flReverseDist ); }
//-----------------------------------------------------------------------------
// Computes distance + nearest point from the current path..
//-----------------------------------------------------------------------------
float CAI_TrackPather::ClosestPointToCurrentPath( Vector *pVecPoint ) const { if (!m_pCurrentPathTarget) { *pVecPoint = GetAbsOrigin(); return 0; }
float t; CalcClosestPointOnLine( GetAbsOrigin(), m_vecSegmentStartPoint, m_pCurrentPathTarget->GetAbsOrigin(), *pVecPoint, &t ); return t; }
//-----------------------------------------------------------------------------
// Computes a "path" velocity at a particular point along the current path
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputePathTangent( float t, Vector *pVecTangent ) const { CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); if ( !pNextTrack ) { pNextTrack = m_pCurrentPathTarget; }
t = clamp( t, 0.0f, 1.0f ); pVecTangent->Init(0,0,0); Catmull_Rom_Spline_Tangent( m_vecSegmentStartSplinePoint, m_vecSegmentStartPoint, m_pCurrentPathTarget->GetAbsOrigin(), pNextTrack->GetAbsOrigin(), t, *pVecTangent ); VectorNormalize( *pVecTangent ); }
//-----------------------------------------------------------------------------
// Computes the *normalized* velocity at which the helicopter should approach the final point
//-----------------------------------------------------------------------------
void CAI_TrackPather::ComputeNormalizedDestVelocity( Vector *pVecVelocity ) const { if ( m_nPauseState != PAUSE_NO_PAUSE ) { pVecVelocity->Init(0,0,0); return; }
CPathTrack *pNextTrack = NextAlongCurrentPath(m_pCurrentPathTarget); if ( !pNextTrack ) { pNextTrack = m_pCurrentPathTarget; }
if ( ( pNextTrack == m_pCurrentPathTarget ) || ( m_pCurrentPathTarget == m_pDestPathTarget ) ) { pVecVelocity->Init(0,0,0); return; }
VectorSubtract( pNextTrack->GetAbsOrigin(), m_pCurrentPathTarget->GetAbsOrigin(), *pVecVelocity ); VectorNormalize( *pVecVelocity );
// Slow it down if we're approaching a sharp corner
Vector vecDelta; VectorSubtract( m_pCurrentPathTarget->GetAbsOrigin(), m_vecSegmentStartPoint, vecDelta ); VectorNormalize( vecDelta ); float flDot = DotProduct( *pVecVelocity, vecDelta ); *pVecVelocity *= clamp( flDot, 0.0f, 1.0f ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::SetTrack( CBaseEntity *pGoalEnt ) { // Ignore this input if we're *already* on that path.
CPathTrack *pTrack = dynamic_cast<CPathTrack *>(pGoalEnt); if ( !pTrack ) { DevWarning( "%s: Specified entity '%s' must be a path_track!\n", pGoalEnt->GetClassname(), pGoalEnt->GetEntityName().ToCStr() ); return; }
MoveToClosestTrackPoint( pTrack ); }
void CAI_TrackPather::SetTrack( string_t strTrackName ) { // Find our specified target
CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); if ( pGoalEnt == NULL ) { DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); return; }
SetTrack( pGoalEnt ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputSetTrack( inputdata_t &inputdata ) { string_t strTrackName = MAKE_STRING( inputdata.value.String() ); SetTrack( MAKE_STRING( inputdata.value.String() ) ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : strTrackName -
//-----------------------------------------------------------------------------
void CAI_TrackPather::FlyToPathTrack( string_t strTrackName ) { CBaseEntity *pGoalEnt = gEntList.FindEntityByName( NULL, strTrackName ); if ( pGoalEnt == NULL ) { DevWarning( "%s: Could not find path_track '%s'!\n", GetClassname(), STRING( strTrackName ) ); return; }
// Ignore this input if we're *already* on that path.
CPathTrack *pTrack = dynamic_cast<CPathTrack *>(pGoalEnt); if ( !pTrack ) { DevWarning( "%s: Specified entity '%s' must be a path_track!\n", GetClassname(), STRING( strTrackName ) ); return; }
// Find our specified target
MoveToTrackPoint( pTrack ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputFlyToPathTrack( inputdata_t &inputdata ) { // Find our specified target
string_t strTrackName = MAKE_STRING( inputdata.value.String() ); m_nPauseState = PAUSE_NO_PAUSE; FlyToPathTrack( strTrackName ); }
//-----------------------------------------------------------------------------
// Changes the mode used to determine which path point to move to
//-----------------------------------------------------------------------------
void CAI_TrackPather::InputChooseFarthestPathPoint( inputdata_t &inputdata ) { UseFarthestPathPoint( true ); }
void CAI_TrackPather::InputChooseNearestPathPoint( inputdata_t &inputdata ) { UseFarthestPathPoint( false ); }
void CAI_TrackPather::UseFarthestPathPoint( bool useFarthest ) { m_bChooseFarthestPoint = useFarthest; }
|