You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
960 lines
28 KiB
960 lines
28 KiB
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
|
|
#include <float.h> // for FLT_MAX |
|
|
|
#include "ai_planesolver.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_motor.h" |
|
#include "ai_basenpc.h" |
|
#include "ai_route.h" |
|
#include "ndebugoverlay.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
const float PLANE_SOLVER_THINK_FREQUENCY[2] = { 0.0f, 0.2f }; |
|
const float MAX_PROBE_DIST[2] = { (10.0f*12.0f), (8.0f*12.0f) }; |
|
|
|
//#define PROFILE_PLANESOLVER 1 |
|
|
|
#ifdef PROFILE_PLANESOLVER |
|
#define PLANESOLVER_PROFILE_SCOPE( tag ) AI_PROFILE_SCOPE( tag ) |
|
#else |
|
#define PLANESOLVER_PROFILE_SCOPE( tag ) ((void)0) |
|
#endif |
|
|
|
#define ProbeForNpcs() 0 |
|
|
|
//#define TESTING_SUGGESTIONS |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
inline float sq( float f ) |
|
{ |
|
return ( f * f ); |
|
} |
|
|
|
inline float cube( float f ) |
|
{ |
|
return ( f * f * f ); |
|
} |
|
|
|
CUtlFixedLinkedList<CAI_PlaneSolver::CircleObstacles_t> CAI_PlaneSolver::s_GlobalObstacles; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CAI_PlaneSolver::CAI_PlaneSolver( CAI_BaseNPC *pNpc ) |
|
: m_pNpc( pNpc ), |
|
m_fSolvedPrev( false ), |
|
m_PrevTarget( FLT_MAX, FLT_MAX, FLT_MAX ), |
|
m_PrevSolution( 0 ), |
|
m_ClosestHaveBeenToCurrent( FLT_MAX ), |
|
m_TimeLastProgress( FLT_MAX ), |
|
m_fCannotSolveCurrent( false ), |
|
m_RefreshSamplesTimer( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] - 0.05 ) |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Convenience accessors |
|
//----------------------------------------------------------------------------- |
|
inline CAI_BaseNPC *CAI_PlaneSolver::GetNpc() |
|
{ |
|
return m_pNpc; |
|
} |
|
|
|
inline CAI_Motor *CAI_PlaneSolver::GetMotor() |
|
{ |
|
return m_pNpc->GetMotor(); |
|
} |
|
|
|
inline const Vector &CAI_PlaneSolver::GetLocalOrigin() |
|
{ |
|
return m_pNpc->GetLocalOrigin(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// class CAI_PlaneSolver |
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_PlaneSolver::MoveLimit( Navigation_t navType, const Vector &target, bool ignoreTransients, bool fCheckStep, int contents, AIMoveTrace_t *pMoveTrace ) |
|
{ |
|
AI_PROFILE_SCOPE( CAI_PlaneSolver_MoveLimit ); |
|
|
|
int flags = ( navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT; |
|
|
|
if ( ignoreTransients ) |
|
{ |
|
Assert( !ProbeForNpcs() ); |
|
flags |= AIMLF_IGNORE_TRANSIENTS; |
|
} |
|
|
|
CAI_MoveProbe *pProbe = m_pNpc->GetMoveProbe(); |
|
return pProbe->MoveLimit( navType, GetLocalOrigin(), target, contents, |
|
m_pNpc->GetNavTargetEntity(), (fCheckStep) ? 100 : 0, |
|
flags, |
|
pMoveTrace ); |
|
} |
|
|
|
bool CAI_PlaneSolver::MoveLimit( Navigation_t navType, const Vector &target, bool ignoreTransients, bool fCheckStep, AIMoveTrace_t *pMoveTrace ) |
|
{ |
|
return MoveLimit( navType, target, ignoreTransients, fCheckStep, GetNpc()->GetAITraceMask(), pMoveTrace ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_PlaneSolver::DetectUnsolvable( const AILocalMoveGoal_t &goal ) |
|
{ |
|
#ifndef TESTING_SUGGESTIONS |
|
float curDistance = ( goal.target.AsVector2D() - GetLocalOrigin().AsVector2D() ).Length(); |
|
if ( m_PrevTarget != goal.target ) |
|
{ |
|
m_TimeLastProgress = gpGlobals->curtime; |
|
m_ClosestHaveBeenToCurrent = curDistance; |
|
m_fCannotSolveCurrent = false; |
|
} |
|
else |
|
{ |
|
if ( m_fCannotSolveCurrent ) |
|
{ |
|
return true; |
|
} |
|
|
|
if ( m_ClosestHaveBeenToCurrent - curDistance > 0 ) |
|
{ |
|
m_TimeLastProgress = gpGlobals->curtime; |
|
m_ClosestHaveBeenToCurrent = curDistance; |
|
} |
|
else |
|
{ |
|
if ( gpGlobals->curtime - m_TimeLastProgress > 0.75 ) |
|
{ |
|
m_fCannotSolveCurrent = true; |
|
return true; |
|
} |
|
} |
|
} |
|
#endif |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_PlaneSolver::AdjustRegulationWeight( CBaseEntity *pEntity, float weight ) |
|
{ |
|
if ( pEntity->MyNPCPointer() != NULL ) |
|
{ |
|
// @TODO (toml 10-03-02): How to do this with non-NPC entities. Should be using intended solve velocity... |
|
Vector2D velOwner = GetNpc()->GetMotor()->GetCurVel().AsVector2D(); |
|
Vector2D velBlocker = ((CAI_BaseNPC *)pEntity)->GetMotor()->GetCurVel().AsVector2D(); |
|
|
|
Vector2D velOwnerNorm = velOwner; |
|
Vector2D velBlockerNorm = velBlocker; |
|
|
|
float speedOwner = Vector2DNormalize( velOwnerNorm ); |
|
float speedBlocker = Vector2DNormalize( velBlockerNorm ); |
|
|
|
float dot = velOwnerNorm.Dot( velBlockerNorm ); |
|
|
|
if ( speedBlocker > 0 ) |
|
{ |
|
if ( dot > 0 && speedBlocker >= speedOwner * 0.9 ) |
|
{ |
|
if ( dot > 0.86 ) |
|
{ |
|
// @Note (toml 10-10-02): Even in the case of no obstacle, we generate |
|
// a suggestion in because we still want to continue sweeping the |
|
// search |
|
weight = 0; |
|
} |
|
else if ( dot > 0.7 ) |
|
{ |
|
weight *= sq( weight ); |
|
} |
|
else |
|
weight *= weight; |
|
} |
|
} |
|
} |
|
|
|
return weight; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_PlaneSolver::CalculateRegulationWeight( const AIMoveTrace_t &moveTrace, float pctBlocked ) |
|
{ |
|
float weight = 0; |
|
|
|
if ( pctBlocked > 0.9) |
|
weight = 1; |
|
else if ( pctBlocked < 0.1) |
|
weight = 0; |
|
else |
|
{ |
|
weight = sq( ( pctBlocked - 0.1 ) / 0.8 ); |
|
weight = AdjustRegulationWeight( moveTrace.pObstruction, weight ); |
|
} |
|
|
|
return weight; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_PlaneSolver::GenerateSuggestionFromTrace( const AILocalMoveGoal_t &goal, |
|
const AIMoveTrace_t &moveTrace, float probeDist, |
|
float arcCenter, float arcSpan, int probeOffset ) |
|
{ |
|
AI_MoveSuggestion_t suggestion; |
|
AI_MoveSuggType_t type; |
|
|
|
switch ( moveTrace.fStatus ) |
|
{ |
|
case AIMR_BLOCKED_ENTITY: type = AIMST_AVOID_OBJECT; break; |
|
case AIMR_BLOCKED_WORLD: type = AIMST_AVOID_WORLD; break; |
|
case AIMR_BLOCKED_NPC: type = AIMST_AVOID_NPC; break; |
|
case AIMR_ILLEGAL: type = AIMST_AVOID_DANGER; break; |
|
default: type = AIMST_NO_KNOWLEDGE; AssertMsg( 0, "Unexpected mode status" ); break; |
|
} |
|
|
|
if ( goal.pMoveTarget != NULL && goal.pMoveTarget == moveTrace.pObstruction ) |
|
{ |
|
suggestion.Set( type, 0, |
|
arcCenter, arcSpan, |
|
moveTrace.pObstruction ); |
|
|
|
m_Solver.AddRegulation( suggestion ); |
|
|
|
return; |
|
} |
|
|
|
float clearDist = probeDist - moveTrace.flDistObstructed; |
|
float pctBlocked = 1.0 - ( clearDist / probeDist ); |
|
|
|
float weight = CalculateRegulationWeight( moveTrace, pctBlocked ); |
|
|
|
if ( weight < 0.001 ) |
|
return; |
|
|
|
if ( pctBlocked < 0.5 ) |
|
{ |
|
arcSpan *= pctBlocked * 2.0; |
|
} |
|
|
|
Vector vecToEnd = moveTrace.vEndPosition - GetLocalOrigin(); |
|
Vector crossProduct; |
|
bool favorLeft = false, favorRight = false; |
|
|
|
if ( moveTrace.fStatus == AIMR_BLOCKED_NPC ) |
|
{ |
|
Vector vecToOther = moveTrace.pObstruction->GetLocalOrigin() - GetLocalOrigin(); |
|
|
|
CrossProduct(vecToEnd, vecToOther, crossProduct); |
|
|
|
favorLeft = ( crossProduct.z < 0 ); |
|
favorRight = ( crossProduct.z > 0 ); |
|
} |
|
else if ( moveTrace.vHitNormal != vec3_origin ) |
|
{ |
|
CrossProduct(vecToEnd, moveTrace.vHitNormal, crossProduct); |
|
favorLeft = ( crossProduct.z > 0 ); |
|
favorRight = ( crossProduct.z < 0 ); |
|
} |
|
|
|
float thirdSpan = arcSpan / 3.0; |
|
float favoredWeight = weight * pctBlocked; |
|
|
|
suggestion.Set( type, weight, |
|
arcCenter, thirdSpan, |
|
moveTrace.pObstruction ); |
|
|
|
m_Solver.AddRegulation( suggestion ); |
|
|
|
suggestion.Set( type, ( favorRight ) ? favoredWeight : weight, |
|
arcCenter - thirdSpan, thirdSpan, |
|
moveTrace.pObstruction ); |
|
|
|
m_Solver.AddRegulation( suggestion ); |
|
|
|
suggestion.Set( type, ( favorLeft ) ? favoredWeight : weight, |
|
arcCenter + thirdSpan, thirdSpan, |
|
moveTrace.pObstruction ); |
|
|
|
m_Solver.AddRegulation( suggestion ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_PlaneSolver::CalcYawsFromOffset( float yawScanCenter, float spanPerProbe, int probeOffset, |
|
float *pYawTest, float *pYawCenter ) |
|
{ |
|
if ( probeOffset != 0 ) |
|
{ |
|
float sign = ( probeOffset > 0 ) ? 1 : -1; |
|
|
|
*pYawCenter = yawScanCenter + probeOffset * spanPerProbe; |
|
if ( *pYawCenter < 0 ) |
|
*pYawCenter += 360; |
|
else if ( *pYawCenter >= 360 ) |
|
*pYawCenter -= 360; |
|
|
|
*pYawTest = *pYawCenter - ( sign * spanPerProbe * 0.5 ); |
|
if ( *pYawTest < 0 ) |
|
*pYawTest += 360; |
|
else if ( *pYawTest >= 360 ) |
|
*pYawTest -= 360; |
|
} |
|
else |
|
{ |
|
*pYawCenter = *pYawTest = yawScanCenter; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_PlaneSolver::GenerateObstacleNpcs( const AILocalMoveGoal_t &goal, float probeDist ) |
|
{ |
|
if ( !ProbeForNpcs() ) |
|
{ |
|
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); |
|
Vector minsSelf, maxsSelf; |
|
m_pNpc->CollisionProp()->WorldSpaceSurroundingBounds( &minsSelf, &maxsSelf ); |
|
float radiusSelf = (minsSelf.AsVector2D() - maxsSelf.AsVector2D()).Length() * 0.5; |
|
|
|
for ( int i = 0; i < g_AI_Manager.NumAIs(); i++ ) |
|
{ |
|
CAI_BaseNPC *pAI = ppAIs[i]; |
|
if ( pAI != m_pNpc && pAI->IsAlive() && ( !goal.pPath || pAI != goal.pPath->GetTarget() ) ) |
|
{ |
|
Vector mins, maxs; |
|
|
|
pAI->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); |
|
if ( mins.z < maxsSelf.z + 12.0 && maxs.z > minsSelf.z - 12.0 ) |
|
{ |
|
float radius = (mins.AsVector2D() - maxs.AsVector2D()).Length() * 0.5; |
|
float distance = ( pAI->GetAbsOrigin().AsVector2D() - m_pNpc->GetAbsOrigin().AsVector2D() ).Length(); |
|
if ( distance - radius < radiusSelf + probeDist ) |
|
{ |
|
AddObstacle( pAI->WorldSpaceCenter(), radius, pAI, AIMST_AVOID_NPC ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 ); |
|
if ( pPlayer ) |
|
{ |
|
Vector mins, maxs; |
|
|
|
pPlayer->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); |
|
if ( mins.z < maxsSelf.z + 12.0 && maxs.z > minsSelf.z - 12.0 ) |
|
{ |
|
float radius = (mins.AsVector2D() - maxs.AsVector2D()).Length(); |
|
float distance = ( pPlayer->GetAbsOrigin().AsVector2D() - m_pNpc->GetAbsOrigin().AsVector2D() ).Length(); |
|
if ( distance - radius < radiusSelf + probeDist ) |
|
{ |
|
AddObstacle( pPlayer->WorldSpaceCenter(), radius, pPlayer, AIMST_AVOID_NPC ); |
|
} |
|
} |
|
} |
|
|
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
AI_SuggestorResult_t CAI_PlaneSolver::GenerateObstacleSuggestion( const AILocalMoveGoal_t &goal, float yawScanCenter, |
|
float probeDist, float spanPerProbe, int probeOffset) |
|
{ |
|
AIMoveTrace_t moveTrace; |
|
float yawTest; |
|
float arcCenter; |
|
|
|
CalcYawsFromOffset( yawScanCenter, spanPerProbe, probeOffset, &yawTest, &arcCenter ); |
|
|
|
Vector probeDir = UTIL_YawToVector( yawTest ); |
|
float requiredMovement = goal.speed * GetMotor()->GetMoveInterval(); |
|
|
|
// Probe immediate move with footing, then look further out ignoring footing |
|
bool fTraceClear = true; |
|
if ( probeDist > requiredMovement ) |
|
{ |
|
if ( !MoveLimit( goal.navType, GetLocalOrigin() + probeDir * requiredMovement, !ProbeForNpcs(), true, &moveTrace ) ) |
|
{ |
|
fTraceClear = false; |
|
moveTrace.flDistObstructed = (probeDist - requiredMovement) + moveTrace.flDistObstructed; |
|
} |
|
} |
|
|
|
if ( fTraceClear ) |
|
{ |
|
fTraceClear = MoveLimit( goal.navType, GetLocalOrigin() + probeDir * probeDist, !ProbeForNpcs(), false, &moveTrace ); |
|
} |
|
|
|
|
|
if ( !fTraceClear ) |
|
{ |
|
GenerateSuggestionFromTrace( goal, moveTrace, probeDist, arcCenter, spanPerProbe, probeOffset ); |
|
return SR_OK; |
|
} |
|
|
|
return SR_NONE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
AI_SuggestorResult_t CAI_PlaneSolver::GenerateObstacleSuggestions( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace, |
|
float distClear, float probeDist, float degreesToProbe, int nProbes ) |
|
{ |
|
Assert( nProbes % 2 == 1 ); |
|
|
|
PLANESOLVER_PROFILE_SCOPE( CAI_PlaneSolver_GenerateObstacleSuggestions ); |
|
|
|
AI_SuggestorResult_t seekResult = SR_NONE; |
|
bool fNewTarget = ( !m_fSolvedPrev || m_PrevTarget != goal.target ); |
|
|
|
if ( fNewTarget ) |
|
m_RefreshSamplesTimer.Force(); |
|
|
|
if ( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] == 0.0 || m_RefreshSamplesTimer.Expired() ) |
|
{ |
|
m_Solver.ClearRegulations(); |
|
|
|
if ( !ProbeForNpcs() ) |
|
GenerateObstacleNpcs( goal, probeDist ); |
|
|
|
if ( GenerateCircleObstacleSuggestions( goal, probeDist ) ) |
|
seekResult = SR_OK; |
|
|
|
float spanPerProbe = degreesToProbe / nProbes; |
|
int nSideProbes = (nProbes - 1) / 2; |
|
float yawGoalDir = UTIL_VecToYaw( goal.dir ); |
|
|
|
Vector probeTarget; |
|
AIMoveTrace_t moveTrace; |
|
int i; |
|
|
|
// Generate suggestion from direct trace, or probe if direct trace doesn't match |
|
if ( fabs( probeDist - ( distClear + directTrace.flDistObstructed ) ) < 0.1 && |
|
( ProbeForNpcs() || directTrace.fStatus != AIMR_BLOCKED_NPC ) ) |
|
{ |
|
if ( directTrace.fStatus != AIMR_OK ) |
|
{ |
|
seekResult = SR_OK; |
|
GenerateSuggestionFromTrace( goal, directTrace, probeDist, yawGoalDir, spanPerProbe, 0 ); |
|
} |
|
} |
|
else if ( GenerateObstacleSuggestion( goal, yawGoalDir, probeDist, spanPerProbe, 0 ) == SR_OK ) |
|
{ |
|
seekResult = SR_OK; |
|
} |
|
|
|
// Scan left. Note that in the left and right scan, the algorithm stops as soon |
|
// as there is a clear path. This is an optimization in anticipation of the |
|
// behavior of the underlying solver. This will break more often the higher |
|
// PLANE_SOLVER_THINK_FREQUENCY becomes |
|
bool foundClear = false; |
|
|
|
for ( i = 1; i <= nSideProbes; i++ ) |
|
{ |
|
if ( !foundClear ) |
|
{ |
|
AI_SuggestorResult_t curSeekResult = GenerateObstacleSuggestion( goal, yawGoalDir, probeDist, |
|
spanPerProbe, i ); |
|
if ( curSeekResult == SR_OK ) |
|
{ |
|
seekResult = SR_OK; |
|
} |
|
else |
|
foundClear = true; |
|
} |
|
else |
|
{ |
|
float ignored; |
|
float arcCenter; |
|
CalcYawsFromOffset( yawGoalDir, spanPerProbe, i, &ignored, &arcCenter ); |
|
m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, spanPerProbe ) ); |
|
} |
|
} |
|
|
|
// Scan right |
|
foundClear = false; |
|
|
|
for ( i = -1; i >= -nSideProbes; i-- ) |
|
{ |
|
if ( !foundClear ) |
|
{ |
|
AI_SuggestorResult_t curSeekResult = GenerateObstacleSuggestion( goal, yawGoalDir, probeDist, |
|
spanPerProbe, i ); |
|
if ( curSeekResult == SR_OK ) |
|
{ |
|
seekResult = SR_OK; |
|
} |
|
else |
|
foundClear = true; |
|
} |
|
else |
|
{ |
|
float ignored; |
|
float arcCenter; |
|
CalcYawsFromOffset( yawGoalDir, spanPerProbe, i, &ignored, &arcCenter ); |
|
m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, spanPerProbe ) ); |
|
} |
|
} |
|
|
|
if ( seekResult == SR_OK ) |
|
{ |
|
float arcCenter = yawGoalDir - 180; |
|
if ( arcCenter < 0 ) |
|
arcCenter += 360; |
|
|
|
// Since these are not sampled every think, place a negative arc in all directions not sampled |
|
m_Solver.AddRegulation( AI_MoveSuggestion_t( AIMST_NO_KNOWLEDGE, 1, arcCenter, 360 - degreesToProbe ) ); |
|
|
|
} |
|
|
|
m_RefreshSamplesTimer.Reset( PLANE_SOLVER_THINK_FREQUENCY[AIStrongOpt()] ); |
|
} |
|
else if ( m_Solver.HaveRegulations() ) |
|
seekResult = SR_OK; |
|
|
|
return seekResult; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Visualizes the regulations for debugging purposes |
|
//----------------------------------------------------------------------------- |
|
void CAI_PlaneSolver::VisualizeRegulations() |
|
{ |
|
// Visualization of regulations |
|
if ((GetNpc()->m_debugOverlays & OVERLAY_NPC_STEERING_REGULATIONS) != 0) |
|
{ |
|
m_Solver.VisualizeRegulations( GetNpc()->WorldSpaceCenter() ); |
|
} |
|
} |
|
|
|
void CAI_PlaneSolver::VisualizeSolution( const Vector &vecGoal, const Vector& vecActual ) |
|
{ |
|
if ((GetNpc()->m_debugOverlays & OVERLAY_NPC_STEERING_REGULATIONS) != 0) |
|
{ |
|
// Compute centroid... |
|
Vector centroid = GetNpc()->WorldSpaceCenter(); |
|
Vector goalPt, actualPt; |
|
|
|
VectorMA( centroid, 20, vecGoal, goalPt ); |
|
VectorMA( centroid, 20, vecActual, actualPt ); |
|
|
|
NDebugOverlay::Line(centroid, goalPt, 255, 255, 255, true, 0.1f ); |
|
NDebugOverlay::Line(centroid, actualPt, 255, 255, 0, true, 0.1f ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Adjust the solution for fliers |
|
//----------------------------------------------------------------------------- |
|
#define MIN_ZDIR_TO_RADIUS 0.1f |
|
|
|
void CAI_PlaneSolver::AdjustSolutionForFliers( const AILocalMoveGoal_t &goal, float flSolutionYaw, Vector *pSolution ) |
|
{ |
|
// Fliers should move up if there are local obstructions... |
|
// A hacky solution, but the bigger the angle of deflection, the more likely |
|
// we're close to a problem and the higher we should go up. |
|
Assert( pSolution->z == 0.0f ); |
|
|
|
// If we're largely needing to move down, then blow off the upward motion... |
|
Vector vecDelta, vecDir; |
|
VectorSubtract( goal.target, GetLocalOrigin(), vecDelta ); |
|
vecDir = vecDelta; |
|
VectorNormalize( vecDir ); |
|
float flRadius = sqrt( vecDir.x * vecDir.x + vecDir.y * vecDir.y ); |
|
*pSolution *= flRadius; |
|
pSolution->z = vecDir.z; |
|
AssertFloatEquals( pSolution->LengthSqr(), 1.0f, 1e-3 ); |
|
|
|
// Move up 0 when we have to move forward as much as we have to move down z (45 degree angle) |
|
// Move up max when we have to move forward 5x as much as we have to move down z, |
|
// or if we have to move up z. |
|
float flUpAmount = 0.0f; |
|
if ( vecDir.z >= -flRadius * MIN_ZDIR_TO_RADIUS) |
|
{ |
|
flUpAmount = 1.0f; |
|
} |
|
else if ((vecDir.z <= -flRadius) || (fabs(vecDir.z) < 1e-3)) |
|
{ |
|
flUpAmount = 0.0f; |
|
} |
|
else |
|
{ |
|
flUpAmount = (-flRadius / vecDir.z) - 1.0f; |
|
flUpAmount *= MIN_ZDIR_TO_RADIUS; |
|
Assert( (flUpAmount >= 0.0f) && (flUpAmount <= 1.0f) ); |
|
} |
|
|
|
// Check the deflection amount... |
|
pSolution->z += flUpAmount * 5.0f; |
|
|
|
// FIXME: Also, if we've got a bunch of regulations, we may |
|
// also wish to raise up a little bit..because this indicates |
|
// that we've got a bunch of stuff to avoid |
|
VectorNormalize( *pSolution ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
unsigned CAI_PlaneSolver::ComputeTurnBiasFlags( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace ) |
|
{ |
|
if ( directTrace.fStatus == AIMR_BLOCKED_WORLD ) |
|
{ |
|
// @TODO (toml 11-11-02): stuff plane normal of hit into trace Use here to compute a bias? |
|
// |
|
return 0; |
|
} |
|
|
|
if ( directTrace.fStatus == AIMR_BLOCKED_NPC ) |
|
{ |
|
return AIMS_FAVOR_LEFT; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_PlaneSolver::RunMoveSolver( const AILocalMoveGoal_t &goal, const AIMoveTrace_t &directTrace, float degreesPositiveArc, |
|
bool fDeterOscillation, Vector *pResult ) |
|
{ |
|
PLANESOLVER_PROFILE_SCOPE( CAI_PlaneSolver_RunMoveSolver ); |
|
|
|
AI_MoveSolution_t solution; |
|
|
|
if ( m_Solver.HaveRegulations() ) |
|
{ |
|
// @TODO (toml 07-19-02): add a movement threshhold here (the target may be the same, |
|
// but the ai is nowhere near where the last solution was derived) |
|
bool fNewTarget = ( !m_fSolvedPrev || m_PrevTarget != goal.target ); |
|
|
|
// For debugging, visualize our regulations |
|
VisualizeRegulations(); |
|
|
|
AI_MoveSuggestion_t moveSuggestions[2]; |
|
int nSuggestions = 1; |
|
|
|
moveSuggestions[0].Set( AIMST_MOVE, 1, UTIL_VecToYaw( goal.dir ), degreesPositiveArc ); |
|
moveSuggestions[0].flags |= ComputeTurnBiasFlags( goal, directTrace ); |
|
|
|
if ( fDeterOscillation && !fNewTarget ) |
|
{ |
|
#ifndef TESTING_SUGGESTIONS |
|
moveSuggestions[nSuggestions++].Set( AIMST_OSCILLATION_DETERRANCE, 1, m_PrevSolution - 180, 180 ); |
|
#endif |
|
} |
|
|
|
if ( m_Solver.Solve( moveSuggestions, nSuggestions, &solution ) ) |
|
{ |
|
*pResult = UTIL_YawToVector( solution.dir ); |
|
|
|
if (goal.navType == NAV_FLY) |
|
{ |
|
// FIXME: Does the z component have to occur during the goal |
|
// setting because it's there & only there where MoveLimit |
|
// will report contact with the world if we move up? |
|
AdjustSolutionForFliers( goal, solution.dir, pResult ); |
|
} |
|
// A crude attempt at oscillation detection: if we solved last time, and this time, and the same target is |
|
// involved, and we resulted in nearly a 180, we are probably oscillating |
|
#ifndef TESTING_SUGGESTIONS |
|
if ( !fNewTarget ) |
|
{ |
|
float delta = solution.dir - m_PrevSolution; |
|
if ( delta < 0 ) |
|
delta += 360; |
|
if ( delta > 165 && delta < 195 ) |
|
return false; |
|
} |
|
#endif |
|
m_PrevSolution = solution.dir; |
|
m_PrevSolutionVector = *pResult; |
|
|
|
Vector curVelocity = m_pNpc->GetSmoothedVelocity(); |
|
if ( curVelocity != vec3_origin ) |
|
{ |
|
VectorNormalize( curVelocity ); |
|
if ( !fNewTarget ) |
|
{ |
|
*pResult = curVelocity * 0.1 + m_PrevSolutionVector * 0.1 + *pResult * 0.8; |
|
} |
|
else |
|
{ |
|
*pResult = curVelocity * 0.2 + *pResult * 0.8; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
} |
|
else |
|
{ |
|
if (goal.navType != NAV_FLY) |
|
{ |
|
*pResult = goal.dir; |
|
} |
|
else |
|
{ |
|
VectorSubtract( goal.target, GetLocalOrigin(), *pResult ); |
|
VectorNormalize( *pResult ); |
|
} |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
float CAI_PlaneSolver::CalcProbeDist( float speed ) |
|
{ |
|
// one second or one hull |
|
float result = GetLookaheadTime() * speed; |
|
if ( result < m_pNpc->GetMoveProbe()->GetHullWidth() ) |
|
return m_pNpc->GetMoveProbe()->GetHullWidth(); |
|
if ( result > MAX_PROBE_DIST[AIStrongOpt()] ) |
|
return MAX_PROBE_DIST[AIStrongOpt()]; |
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CAI_PlaneSolver::AddObstacle( const Vector ¢er, float radius, CBaseEntity *pEntity, AI_MoveSuggType_t type ) |
|
{ |
|
m_Obstacles.AddToTail( CircleObstacles_t( center, radius, pEntity, type ) ); |
|
} |
|
|
|
Obstacle_t CAI_PlaneSolver::AddGlobalObstacle( const Vector ¢er, float radius, CBaseEntity *pEntity, AI_MoveSuggType_t type ) |
|
{ |
|
return (Obstacle_t)s_GlobalObstacles.AddToTail( CircleObstacles_t( center, radius, pEntity, type ) ); |
|
} |
|
|
|
void CAI_PlaneSolver::RemoveGlobalObstacle( Obstacle_t hObstacle ) |
|
{ |
|
s_GlobalObstacles.Remove( (int)hObstacle ); |
|
} |
|
|
|
void CAI_PlaneSolver::RemoveGlobalObstacles( void ) |
|
{ |
|
s_GlobalObstacles.RemoveAll(); |
|
} |
|
|
|
bool CAI_PlaneSolver::IsSegmentBlockedByGlobalObstacles( const Vector &vecStart, const Vector &vecEnd ) |
|
{ |
|
for ( int i = s_GlobalObstacles.Head(); i != s_GlobalObstacles.InvalidIndex(); i = s_GlobalObstacles.Next( i ) ) |
|
{ |
|
const CircleObstacles_t& obstacle = s_GlobalObstacles[i]; |
|
if ( obstacle.type == AIMST_MOVE ) |
|
continue; |
|
|
|
const Vector &vecObstacle = obstacle.center; |
|
float flDistSqr = CalcDistanceSqrToLineSegment( vecObstacle, vecStart, vecEnd ); |
|
if ( flDistSqr < obstacle.radius * obstacle.radius ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates a single suggestion |
|
//----------------------------------------------------------------------------- |
|
bool CAI_PlaneSolver::GenerateCircleObstacleSuggestion( const CircleObstacles_t &obstacle, |
|
const AILocalMoveGoal_t &moveGoal, float probeDist, const Vector& npcLoc, float radiusNpc ) |
|
{ |
|
CBaseEntity *pObstacleEntity = NULL; |
|
|
|
float zDistTooFar; |
|
if ( obstacle.hEntity && obstacle.hEntity->CollisionProp() ) |
|
{ |
|
pObstacleEntity = obstacle.hEntity.Get(); |
|
|
|
// HEY! I'm trying to avoid the very thing I'm trying to get to. This will make we wobble like a drunk as I approach. Don't do it. |
|
if( pObstacleEntity == moveGoal.pMoveTarget && (pObstacleEntity->IsNPC() || pObstacleEntity->IsPlayer()) ) |
|
return false; |
|
|
|
Vector mins, maxs; |
|
pObstacleEntity->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); |
|
zDistTooFar = ( maxs.z - mins.z ) * 0.5 + GetNpc()->GetHullHeight() * 0.5; |
|
} |
|
else |
|
{ |
|
zDistTooFar = GetNpc()->GetHullHeight(); |
|
} |
|
|
|
if ( fabs( obstacle.center.z - npcLoc.z ) > zDistTooFar ) |
|
return false; |
|
|
|
Vector vecToNpc = npcLoc - obstacle.center; |
|
vecToNpc.z = 0; |
|
float distToObstacleSq = sq(vecToNpc.x) + sq(vecToNpc.y); |
|
float radius = obstacle.radius + radiusNpc; |
|
|
|
if ( distToObstacleSq <= 0.001 || distToObstacleSq >= sq( radius + probeDist ) ) |
|
return false; |
|
|
|
Vector vecToObstacle = vecToNpc * -1; |
|
float distToObstacle = VectorNormalize( vecToObstacle ); |
|
float weight; |
|
float arc; |
|
float radiusSq = sq(radius); |
|
|
|
float flDot = DotProduct( vecToObstacle, moveGoal.dir ); |
|
|
|
// Don't steer around to avoid obstacles we've already passed, unless we're right up against them. |
|
// That is, do this computation without the probeDist added in. |
|
if( flDot < 0.0f && distToObstacleSq > radiusSq ) |
|
return false; |
|
|
|
if ( radiusSq < distToObstacleSq ) |
|
{ |
|
Vector vecTangent; |
|
float distToTangent = FastSqrt( distToObstacleSq - radiusSq ); |
|
|
|
float oneOverDistToObstacleSq = 1 / distToObstacleSq; |
|
|
|
vecTangent.x = ( -distToTangent * vecToNpc.x + radius * vecToNpc.y ) * oneOverDistToObstacleSq; |
|
vecTangent.y = ( -distToTangent * vecToNpc.y - radius * vecToNpc.x ) * oneOverDistToObstacleSq; |
|
vecTangent.z = 0; |
|
|
|
float cosHalfArc = vecToObstacle.Dot( vecTangent ); |
|
arc = RAD2DEG(acosf( cosHalfArc )) * 2.0; |
|
weight = 1.0 - (distToObstacle - radius) / probeDist; |
|
if ( weight > 0.75 ) |
|
{ |
|
arc += (arc * 0.5) * (weight - 0.75) / 0.25; |
|
} |
|
|
|
Assert( weight >= 0.0 && weight <= 1.0 ); |
|
|
|
#if DEBUG_OBSTACLES |
|
// ------------------------- |
|
Msg( "Adding arc %f, w %f\n", arc, weight ); |
|
|
|
Vector pointTangent = npcLoc + ( vecTangent * distToTangent ); |
|
|
|
NDebugOverlay::Line( npcLoc - Vector( 0, 0, 64 ), npcLoc + Vector(0,0,64), 0,255,0, false, 0.1 ); |
|
NDebugOverlay::Line( center - Vector( 0, 0, 64 ), center + Vector(0,0,64), 0,255,0, false, 0.1 ); |
|
NDebugOverlay::Line( pointTangent - Vector( 0, 0, 64 ), pointTangent + Vector(0,0,64), 0,255,0, false, 0.1 ); |
|
|
|
NDebugOverlay::Line( npcLoc + Vector(0,0,64), center + Vector(0,0,64), 0,0,255, false, 0.1 ); |
|
NDebugOverlay::Line( center + Vector(0,0,64), pointTangent + Vector(0,0,64), 0,0,255, false, 0.1 ); |
|
NDebugOverlay::Line( pointTangent + Vector(0,0,64), npcLoc + Vector(0,0,64), 0,0,255, false, 0.1 ); |
|
#endif |
|
} |
|
else |
|
{ |
|
arc = 210; |
|
weight = 1.0; |
|
} |
|
|
|
if ( obstacle.hEntity != NULL ) |
|
{ |
|
weight = AdjustRegulationWeight( obstacle.hEntity, weight ); |
|
} |
|
|
|
AI_MoveSuggestion_t suggestion( obstacle.type, weight, UTIL_VecToYaw(vecToObstacle), arc ); |
|
m_Solver.AddRegulation( suggestion ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
bool CAI_PlaneSolver::GenerateCircleObstacleSuggestions( const AILocalMoveGoal_t &moveGoal, float probeDist ) |
|
{ |
|
bool result = false; |
|
Vector mins, maxs; |
|
Vector npcLoc = m_pNpc->WorldSpaceCenter(); |
|
m_pNpc->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); |
|
float radiusNpc = (mins.AsVector2D() - maxs.AsVector2D()).Length() * 0.5; |
|
|
|
for ( int i = 0; i < m_Obstacles.Count(); i++ ) |
|
{ |
|
if ( GenerateCircleObstacleSuggestion( m_Obstacles[i], moveGoal, probeDist, npcLoc, radiusNpc ) ) |
|
{ |
|
result = true; |
|
} |
|
} |
|
m_Obstacles.RemoveAll(); |
|
|
|
// Global obstacles |
|
for ( int i = s_GlobalObstacles.Head(); i != s_GlobalObstacles.InvalidIndex(); i = s_GlobalObstacles.Next( i ) ) |
|
{ |
|
if ( GenerateCircleObstacleSuggestion( s_GlobalObstacles[i], moveGoal, probeDist, npcLoc, radiusNpc ) ) |
|
{ |
|
result = true; |
|
} |
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CAI_PlaneSolver::Solve( const AILocalMoveGoal_t &goal, float distClear, Vector *pSolution ) |
|
{ |
|
bool solved = false; |
|
|
|
//--------------------------------- |
|
|
|
if ( goal.speed == 0 ) |
|
return false; |
|
|
|
if ( DetectUnsolvable( goal ) ) |
|
return false; |
|
|
|
//--------------------------------- |
|
|
|
bool fVeryClose = ( distClear < 1.0 ); |
|
float degreesPositiveArc = ( !fVeryClose ) ? DEGREES_POSITIVE_ARC : DEGREES_POSITIVE_ARC_CLOSE_OBSTRUCTION; |
|
float probeDist = CalcProbeDist( goal.speed ); |
|
|
|
if ( goal.flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) |
|
{ |
|
probeDist = MIN( goal.maxDist, probeDist ); |
|
} |
|
|
|
if ( GenerateObstacleSuggestions( goal, goal.directTrace, distClear, probeDist, degreesPositiveArc, NUM_PROBES ) != SR_FAIL ) |
|
{ |
|
if ( RunMoveSolver( goal, goal.directTrace, degreesPositiveArc, !fVeryClose, pSolution ) ) |
|
{ |
|
// Visualize desired + actual directions |
|
VisualizeSolution( goal.dir, *pSolution ); |
|
|
|
AIMoveTrace_t moveTrace; |
|
float requiredMovement = goal.speed * GetMotor()->GetMoveInterval(); |
|
|
|
MoveLimit( goal.navType, GetLocalOrigin() + *pSolution * requiredMovement, false, true, &moveTrace ); |
|
|
|
if ( !IsMoveBlocked( moveTrace ) ) |
|
solved = true; |
|
else |
|
solved = false; |
|
} |
|
} |
|
|
|
m_fSolvedPrev = ( solved && goal.speed != 0 ); // a solution found when speed is zero is not meaningful |
|
m_PrevTarget = goal.target; |
|
|
|
return solved; |
|
} |
|
|
|
//=============================================================================
|
|
|