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.
410 lines
10 KiB
410 lines
10 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
|
|
#include "ai_movesolver.h" |
|
#include "ndebugoverlay.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
inline float V_round( float f ) |
|
{ |
|
return (float)( (int)( f + 0.5 ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// CAI_MoveSolver |
|
//----------------------------------------------------------------------------- |
|
|
|
// The epsilon used by the solver |
|
const float AIMS_EPS = 0.01; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Visualization |
|
//----------------------------------------------------------------------------- |
|
void CAI_MoveSolver::VisualizeRegulations( const Vector& origin ) |
|
{ |
|
if ( m_Regulations.Count() ) |
|
{ |
|
CAI_MoveSuggestions regulations; |
|
regulations.AddVectorToTail( m_Regulations ); |
|
NormalizeSuggestions( ®ulations[0], (®ulations[0]) + regulations.Count() ); |
|
|
|
Vector side1, mid, side2; |
|
for (int i = regulations.Count(); --i >= 0; ) |
|
{ |
|
// Compute the positions of the angles... |
|
float flMinAngle = regulations[i].arc.center - regulations[i].arc.span * 0.5f; |
|
float flMaxAngle = regulations[i].arc.center + regulations[i].arc.span * 0.5f; |
|
|
|
side1 = UTIL_YawToVector( flMinAngle ); |
|
side2 = UTIL_YawToVector( flMaxAngle ); |
|
mid = UTIL_YawToVector( regulations[i].arc.center ); |
|
|
|
// Stronger weighted ones are bigger |
|
if ( regulations[i].weight < 0 ) |
|
{ |
|
float flLength = 10 + 40 * ( regulations[i].weight * -1.0); |
|
side1 *= flLength; |
|
side2 *= flLength; |
|
mid *= flLength; |
|
|
|
side1 += origin; |
|
side2 += origin; |
|
mid += origin; |
|
|
|
NDebugOverlay::Triangle(origin, mid, side1, 255, 0, 0, 48, true, 0.1f ); |
|
NDebugOverlay::Triangle(origin, side2, mid, 255, 0, 0, 48, true, 0.1f ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//------------------------------------- |
|
// Purpose: The actual solver function. Reweights according to type and sums |
|
// all the suggestions, identifying the best. |
|
//------------------------------------- |
|
bool CAI_MoveSolver::Solve( const AI_MoveSuggestion_t *pSuggestions, int nSuggestions, AI_MoveSolution_t *pResult) |
|
{ |
|
//--------------------------------- |
|
// |
|
// Quick out |
|
// |
|
if ( !nSuggestions ) |
|
return false; |
|
|
|
if ( nSuggestions == 1 && m_Regulations.Count() == 0 && pSuggestions->type == AIMST_MOVE ) |
|
{ |
|
pResult->dir = pSuggestions->arc.center; |
|
return true; |
|
} |
|
|
|
//--------------------------------- |
|
// |
|
// Setup |
|
// |
|
CAI_MoveSuggestions suggestions; |
|
|
|
suggestions.EnsureCapacity( m_Regulations.Count() + nSuggestions ); |
|
|
|
suggestions.CopyArray( pSuggestions, nSuggestions); |
|
suggestions.AddVectorToTail( m_Regulations ); |
|
|
|
// Initialize the solver |
|
const int NUM_SOLUTIONS = 120; |
|
const int SOLUTION_ANG = 360 / NUM_SOLUTIONS; |
|
|
|
COMPILE_TIME_ASSERT( ( 360 % NUM_SOLUTIONS ) == 0 ); |
|
|
|
struct Solution_t |
|
{ |
|
// The sum bias |
|
float bias; |
|
float highBias; |
|
AI_MoveSuggestion_t *pHighSuggestion; |
|
}; |
|
|
|
Solution_t solutions[NUM_SOLUTIONS] = { { 0, 0, NULL } }; |
|
|
|
//--------------------------------- |
|
|
|
// The first thing we do is reweight and normalize the weights into a range of [-1..1], where |
|
// a negative weight is a repulsion. This becomes a bias for the solver. |
|
// @TODO (toml 06-18-02): this can be made sligtly more optimal by precalculating regulation adjusted weights |
|
Assert( suggestions.Count() >= 1 ); |
|
NormalizeSuggestions( &suggestions[0], (&suggestions[0]) + suggestions.Count() ); |
|
|
|
// |
|
// Add the biased suggestions to the solutions |
|
// |
|
for ( int iSuggestion = 0; iSuggestion < suggestions.Count(); ++iSuggestion ) |
|
{ |
|
AI_MoveSuggestion_t ¤t = suggestions[iSuggestion]; |
|
|
|
// Convert arc values to solution indices relative to right post. Right is angle down, left is angle up. |
|
float halfSpan = current.arc.span * 0.5; |
|
int center = V_round( ( halfSpan * NUM_SOLUTIONS ) / 360 ); |
|
int left = ( current.arc.span * NUM_SOLUTIONS ) / 360; |
|
|
|
float angRight = current.arc.center - halfSpan; |
|
|
|
if (angRight < 0.0) |
|
angRight += 360; |
|
|
|
int base = ( angRight * NUM_SOLUTIONS ) / 360; |
|
|
|
// Sweep from left to right, summing the bias. For positive suggestions, |
|
// the bias is further weighted to favor the center of the arc. |
|
const float positiveDegradePer180 = 0.05; // i.e., lose 5% of weight by the time hit 180 degrees off center |
|
const float positiveDegrade = ( positiveDegradePer180 / ( NUM_SOLUTIONS * 0.5 ) ); |
|
|
|
for ( int i = 0; i < left + 1; ++i ) |
|
{ |
|
float bias = 0.0; |
|
|
|
if ( current.weight > 0) |
|
{ |
|
int iOffset = center - i; |
|
float degrade = abs( iOffset ) * positiveDegrade; |
|
|
|
if ( ( (current.flags & AIMS_FAVOR_LEFT ) && i > center ) || |
|
( (current.flags & AIMS_FAVOR_RIGHT) && i < center ) ) |
|
{ |
|
degrade *= 0.9; |
|
} |
|
|
|
bias = current.weight - ( current.weight * degrade ); |
|
} |
|
else |
|
bias = current.weight; |
|
|
|
int iCurSolution = (base + i) % NUM_SOLUTIONS; |
|
|
|
solutions[iCurSolution].bias += bias; |
|
if ( bias > solutions[iCurSolution].highBias ) |
|
{ |
|
solutions[iCurSolution].highBias = bias; |
|
solutions[iCurSolution].pHighSuggestion = ¤t; |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Find the best solution |
|
// |
|
int best = -1; |
|
float biasBest = 0; |
|
|
|
for ( int i = 0; i < NUM_SOLUTIONS; ++i ) |
|
{ |
|
if ( solutions[i].bias > biasBest ) |
|
{ |
|
best = i; |
|
biasBest = solutions[i].bias; |
|
} |
|
} |
|
|
|
if ( best == -1 ) |
|
return false; // no solution |
|
|
|
// |
|
// Construct the results |
|
// |
|
float result = best * SOLUTION_ANG; |
|
|
|
// If the matching suggestion is within the solution, use that as the result, |
|
// as it is valid and more precise. |
|
const float suggestionCenter = solutions[best].pHighSuggestion->arc.center; |
|
|
|
if ( suggestionCenter > result && suggestionCenter <= result + SOLUTION_ANG ) |
|
result = suggestionCenter; |
|
|
|
pResult->dir = result; |
|
|
|
return true; |
|
} |
|
|
|
//------------------------------------- |
|
// Purpose: Adjusts the suggestion weights according to the type of the suggestion, |
|
// apply the appropriate sign, ensure values are in expected ranges |
|
//------------------------------------- |
|
|
|
struct AI_MoveSuggWeights |
|
{ |
|
float min; |
|
float max; |
|
}; |
|
|
|
static AI_MoveSuggWeights g_AI_MoveSuggWeights[] = // @TODO (toml 06-18-02): these numbers need tuning |
|
{ |
|
{ 0.20, 1.00 }, // AIMST_MOVE |
|
{ -0.00, -0.25 }, // AIMST_AVOID_DANGER |
|
{ -0.00, -0.25 }, // AIMST_AVOID_OBJECT |
|
{ -0.00, -0.25 }, // AIMST_AVOID_NPC |
|
{ -0.00, -0.25 }, // AIMST_AVOID_WORLD |
|
{ -1.00, -1.00 }, // AIMST_NO_KNOWLEDGE |
|
{ -0.60, -0.60 }, // AIMST_OSCILLATION_DETERRANCE |
|
{ 0.00, 0.00 }, // AIMST_INVALID |
|
}; |
|
|
|
void CAI_MoveSolver::NormalizeSuggestions( AI_MoveSuggestion_t *pBegin, AI_MoveSuggestion_t *pEnd ) |
|
{ |
|
while ( pBegin != pEnd ) |
|
{ |
|
const float min = g_AI_MoveSuggWeights[pBegin->type].min; |
|
const float max = g_AI_MoveSuggWeights[pBegin->type].max; |
|
|
|
Assert( pBegin->weight >= -AIMS_EPS && pBegin->weight <= 1.0 + AIMS_EPS ); |
|
|
|
if ( pBegin->weight < AIMS_EPS ) // zero normalizes to zero |
|
pBegin->weight = 0.0; |
|
else |
|
pBegin->weight = ( ( max - min ) * pBegin->weight ) + min; |
|
|
|
while (pBegin->arc.center < 0) |
|
pBegin->arc.center += 360; |
|
|
|
while (pBegin->arc.center >= 360) |
|
pBegin->arc.center -= 360; |
|
|
|
++pBegin; |
|
} |
|
} |
|
|
|
//------------------------------------- |
|
|
|
bool CAI_MoveSolver::HaveRegulationForObstacle( CBaseEntity *pEntity) |
|
{ |
|
for ( int i = 0; i < m_Regulations.Count(); ++i ) |
|
{ |
|
if ( m_Regulations[i].hObstacleEntity != NULL && |
|
pEntity == m_Regulations[i].hObstacleEntity.Get() ) |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Commands and tests |
|
// |
|
|
|
#ifdef DEBUG |
|
CON_COMMAND(ai_test_move_solver, "Tests the AI move solver system") |
|
{ |
|
#ifdef DEBUG |
|
const float EPS = 0.001; |
|
#endif |
|
DevMsg( "Beginning move solver tests...\n" ); |
|
|
|
CAI_MoveSolver solver; |
|
AI_MoveSolution_t solution; |
|
int i; |
|
|
|
// |
|
// Value in, no regulations, should yield value out |
|
// |
|
{ |
|
DevMsg( "Simple... " ); |
|
|
|
for (i = 0; i < 360; ++i) |
|
{ |
|
Assert( solver.Solve( AI_MoveSuggestion_t( AIMST_MOVE, 1, i, 180 ), &solution ) ); |
|
Assert( solution.dir == (float)i ); |
|
|
|
} |
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
// |
|
// Two values in, should yield the first |
|
// |
|
{ |
|
DevMsg( "Two positive... " ); |
|
|
|
AI_MoveSuggestion_t suggestions[2]; |
|
|
|
suggestions[0].Set( AIMST_MOVE, 1.0, 180, 100 ); |
|
suggestions[1].Set( AIMST_MOVE, 0.5, 0, 100 ); |
|
|
|
Assert( solver.Solve( suggestions, 2, &solution ) ); |
|
Assert( solution.dir == (float)suggestions[0].arc.center ); |
|
|
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
// |
|
// Two values in, first regulated, should yield the second |
|
// |
|
{ |
|
DevMsg( "Avoid one of two... " ); |
|
|
|
AI_MoveSuggestion_t suggestions[2]; |
|
|
|
solver.AddRegulation(AI_MoveSuggestion_t( AIMST_AVOID_OBJECT, 1, 260, 60 ) ); |
|
|
|
suggestions[0].Set( AIMST_MOVE, 1.0, 270, 45 ); |
|
suggestions[1].Set( AIMST_MOVE, 1.0, 0, 45 ); |
|
|
|
Assert( solver.Solve( suggestions, 2, &solution ) ); |
|
Assert( solution.dir == (float)suggestions[1].arc.center ); |
|
|
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
// |
|
// No solution |
|
// |
|
{ |
|
DevMsg( "No solution... " ); |
|
|
|
AI_MoveSuggestion_t suggestions[2]; |
|
|
|
suggestions[0].Set( AIMST_MOVE, 1.0, 270, 90 ); |
|
suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 180 ); |
|
|
|
Assert( !solver.Solve( suggestions, 2, &solution ) ); |
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
// |
|
// Nearest solution, in tolerance |
|
// |
|
{ |
|
DevMsg( "Nearest solution, in tolerance... " ); |
|
|
|
AI_MoveSuggestion_t suggestions[2]; |
|
|
|
suggestions[0].Set( AIMST_MOVE, 1.0, 278, 90 ); |
|
suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 24 ); |
|
|
|
Assert( solver.Solve( suggestions, 2, &solution ) ); |
|
Assert( solution.dir == (float)suggestions[0].arc.center ); |
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
// |
|
// Nearest solution |
|
// |
|
{ |
|
DevMsg( "Nearest solution... " ); |
|
|
|
AI_MoveSuggestion_t suggestions[2]; |
|
|
|
suggestions[0].Set( AIMST_MOVE, 1.0, 270, 90 ); |
|
suggestions[1].Set( AIMST_AVOID_OBJECT, 1.0, 260, 40 ); |
|
|
|
Assert( solver.Solve( suggestions, 2, &solution ) ); |
|
Assert( solution.dir - 282 < EPS ); // given 60 solutions |
|
|
|
DevMsg( "pass.\n" ); |
|
solver.ClearRegulations(); |
|
} |
|
|
|
} |
|
#endif |
|
|
|
//============================================================================= |
|
|
|
|