Files
GTASource/game/vehicleAi/VehicleNavigationHelper.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

616 lines
17 KiB
C++

#include "VehicleNavigationHelper.h"
#include "physics\PhysicsHelpers.h"
#include "Vehicles/Vehicle.h"
#include "debug/DebugScene.h"
#include "scene/world/gameWorld.h"
#include "peds/Ped.h"
#include "physics\PhysicsHelpers.h"
#include "system/ControlMgr.h"
#include "vehicleAi\task\TaskVehiclePark.h"
#include "scene\world\GameWorldHeightMap.h"
#include "physics/WorldProbe/worldprobe.h"
AI_OPTIMISATIONS()
AI_VEHICLE_OPTIMISATIONS()
int VehicleNavigationHelper::s_iMaxNumProbes = 5;
VehicleNavigationHelper::VehicleNavigationHelper() :
m_vTargetPosition(),
m_vLastValidTarget(),
m_vCurrentGoToTarget(),
m_fSearchTimer( 1.0f ),
m_fSearchTime( 1.0f ),
m_fSightDistance(100.0f),
m_pVehicle(NULL),
m_pTarget(NULL),
m_bIntermediaryGoToValid(false),
m_bIsPerformingSearch(false),
m_bStartNewSearch(false),
m_currentSearchType(eSearchStart),
m_navigationProbeResults(),
m_navigationTargets(),
m_iNumProbesUsed(0)
{
m_navigationProbeResults = rage_new WorldProbe::CShapeTestSingleResult[s_iMaxNumProbes*2];
m_navigationTargets = rage_new Vector3[s_iMaxNumProbes];
}
VehicleNavigationHelper::~VehicleNavigationHelper()
{
delete[] m_navigationProbeResults;
delete[] m_navigationTargets;
}
float VehicleNavigationHelper::ComputeTargetZToAvoidHeightMap(const Vector3& vPosition, float fSpeed,
const Vector3& vTargetPosition, float minHeight,
fp_HeightQuery in_fpHeightQuery, bool checkCurrentPosition /* = true */,
bool heightAtFullDistance /* = true */)
{
Vector3 vVehiclePos(vPosition);
float vehicleZ = in_fpHeightQuery(vVehiclePos.x, vVehiclePos.y);
vehicleZ += minHeight;
if ( checkCurrentPosition && vVehiclePos.z < vehicleZ )
{
// need to go straight up
return 10000000.0f;
}
Vector3 vVehicleDirXY = vTargetPosition - vVehiclePos;
float fDistXY = vVehicleDirXY.XYMag();
float targetZ = vTargetPosition.z;
// initialize the target position
float terrainZ = in_fpHeightQuery(vTargetPosition.x, vTargetPosition.y);
terrainZ += minHeight;
targetZ = Max(terrainZ, targetZ);
if ( fDistXY > 0.001f )
{
vVehicleDirXY.z = 0.0f;
vVehicleDirXY *= 1/ fDistXY;
float maxSlope = targetZ / fDistXY;
maxSlope *= Sign(vTargetPosition.z - vVehiclePos.z);
int iNumHeightmapFutureSamples = 10;
for (int i = 0; i < iNumHeightmapFutureSamples; i++)
{
float t = (float)(i+1);// * (float)iNumHeightmapFutureSamples;
float xyLength = fSpeed * t;
// don't check beyond target
if ( xyLength >= fDistXY )
{
break;
}
Vector3 vSamplePosition = vVehiclePos + vVehicleDirXY * xyLength;
// sample heightmap
vSamplePosition.z = in_fpHeightQuery(vSamplePosition.x, vSamplePosition.y);
vSamplePosition.z += minHeight;
#if DEBUG_DRAW
static bool s_DrawHeightMapTests = false;
if ( s_DrawHeightMapTests ) grcDebugDraw::Sphere(vSamplePosition, 1.5f, Color_red, true, 1);
#endif
// compute our slope dy/dx for sample point
Vector3 vDelta = vSamplePosition - vVehiclePos;
if(xyLength > SMALL_FLOAT)
{
float slope = vDelta.z / xyLength;
if ( slope > maxSlope )
{
maxSlope = slope;
if(heightAtFullDistance)
{
// compute our target z at full distance z = mx + b
targetZ = (slope * fDistXY) + vVehiclePos.z;
}
else
{
targetZ = vSamplePosition.z;
}
}
}
}
}
//make sure not to return lower than target
targetZ = Max(targetZ, vTargetPosition.z);
return targetZ;
}
void VehicleNavigationHelper::HelperExcludeCollisionEntities(WorldProbe::CShapeTestDesc& o_ShapeTest, const CEntity& in_Entity)
{
if ( in_Entity.GetIsTypeVehicle() )
{
const CVehicle& vehicle = static_cast<const CVehicle&>(in_Entity);
const CEntity* pEntity = vehicle.GetEntityBeingTowed();
if ( pEntity )
{
o_ShapeTest.AddExludeInstance(pEntity->GetCurrentPhysicsInst());
HelperExcludeCollisionEntities(o_ShapeTest, *pEntity);
}
}
// grab the attachments
CEntity* pChild = static_cast<CEntity*>(in_Entity.GetChildAttachment());
while ( pChild )
{
o_ShapeTest.AddExludeInstance(pChild->GetCurrentPhysicsInst());
HelperExcludeCollisionEntities(o_ShapeTest, *pChild);
pChild = static_cast<CEntity*>(pChild->GetSiblingAttachment());
}
}
bool VehicleNavigationHelper::CanNavigateOverObject(const Vector3& collisionPoint, const Vector3& end, int numProbes /*= 10*/, float depthOffset /*= -5.0f */)
{
#if __DEV
static bool s_drawDebug = false;
#endif
Vector3 intersectionPoint(0.0f, 0.0f, 0.0f);
//do a check from target to our collide point, to get the end of the object between us
if(WorldCollisionTest(intersectionPoint, end, collisionPoint, NULL))
{
bool doVerticalTests = false;
if(doVerticalTests)
{
Vector3 offset = intersectionPoint - collisionPoint;
Vector3 probeOffset = offset / (float)numProbes;
#if __DEV
if(s_drawDebug)
{
grcDebugDraw::Sphere(collisionPoint, 0.5f, Color_green, true, -1);
grcDebugDraw::Sphere(intersectionPoint, 0.5f, Color_red, true, -1);
grcDebugDraw::Line(collisionPoint, intersectionPoint, Color_green, -1);
}
#endif // __DEV
for (int i = 0; i < numProbes; i++)
{
Vector3 vSamplePosition = collisionPoint + (probeOffset * (float)(i+1));
vSamplePosition.z = 100.0f;
float fWaterHeight;
if(!Water::GetWaterLevelNoWaves(vSamplePosition, &fWaterHeight, POOL_DEPTH, 999999.9f, NULL))
{
#if __DEV
if(s_drawDebug)
{
Vector3 vEndPos = vSamplePosition;
vEndPos.z = vSamplePosition.z - 200.0f;
grcDebugDraw::Line(vSamplePosition, vEndPos, Color_red, -1);
}
#endif // __DEV
return false;
}
#if __DEV
if(s_drawDebug)
{
Vector3 vEndPos = vSamplePosition;
vEndPos.z = fWaterHeight + depthOffset;
grcDebugDraw::Line(vSamplePosition, vEndPos, Color_green, -1);
}
#endif // __DEV
}
}
else
{
float fWaterHeight;
if(Water::GetWaterLevelNoWaves(collisionPoint, &fWaterHeight, POOL_DEPTH, 999999.9f, NULL))
{
fWaterHeight += depthOffset;
Vector3 vStart = collisionPoint;
vStart.z = fWaterHeight;
Vector3 vEnd = intersectionPoint;
vEnd.z = fWaterHeight;
Vector3 tmpPoint(0.0f, 0.0f, 0.0f);
//check along surface for any objects
if(WorldCollisionTest(tmpPoint, vStart, vEnd, NULL))
{
return false;
}
}
}
}
return true;
}
bool VehicleNavigationHelper::CheckHasLineOfSight(const Vector3& vStart, const Vector3& vEnd, const CEntity* pVehicle, const CEntity* pTargetVehicle, Vector3& collidePoint)
{
WorldProbe::CShapeTestProbeDesc probeDesc;
WorldProbe::CShapeTestFixedResults<> probeResult;
probeDesc.SetResultsStructure(&probeResult);
probeDesc.SetStartAndEnd(vStart, vEnd);
if(pVehicle)
{
probeDesc.SetExcludeEntity(pVehicle);
}
if(pTargetVehicle)
{
probeDesc.SetExcludeEntity(pTargetVehicle);
}
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
collidePoint = probeResult[0].GetHitPosition();
return false;
}
return true;
}
bool VehicleNavigationHelper::WorldCollisionTest(Vector3& intersectionPoint_out, const Vector3& vStart, const Vector3 &vEnd, const CEntity* pException)
{
WorldProbe::CShapeTestProbeDesc probeDesc;
WorldProbe::CShapeTestFixedResults<> probeResult;
probeDesc.SetResultsStructure(&probeResult);
probeDesc.SetStartAndEnd(vStart, vEnd);
if(pException)
{
probeDesc.SetExcludeEntity(pException);
}
probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc))
{
intersectionPoint_out = probeResult[0].GetHitPosition();
return true;
}
return false;
}
void VehicleNavigationHelper::LimitEndByDistance(const Vector3& startPos, Vector3& endPos, float distanceLimit)
{
Vector3 lineDir = endPos - startPos;
float lineDist2 = lineDir.Mag2();
if(lineDist2 > distanceLimit*distanceLimit)
{
lineDir.Normalize();
endPos = startPos + (lineDir * distanceLimit);
}
}
bool VehicleNavigationHelper::CanNavigateStraightTo(const Vector3& currentPos, const Vector3& targetPos)
{
Vector3 outPos = targetPos;
LimitEndByDistance(currentPos, outPos, m_fSightDistance);
Vector3 collidePoint;
if(!CheckHasLineOfSight(currentPos,outPos, m_pVehicle, m_pTarget, collidePoint))
{
if(!m_pVehicle->InheritsFromSubmarine() && !m_pVehicle->InheritsFromSubmarineCar())
{
return true;
}
//subs can't navigate to objects that are out the water
return CanNavigateOverObject(collidePoint, outPos);
}
return true;
}
void VehicleNavigationHelper::Update(float fTimeStep, const Vector3& targetPos, bool forceUpdate /*= false*/)
{
Assertf(m_pVehicle,"Invalid vehicle pointer when updating navigation");
Vector3 vehPos (VEC3V_TO_VECTOR3(m_pVehicle->GetTransform().GetPosition()));
m_vTargetPosition = targetPos;
//we've arrived
if ((m_vTargetPosition - vehPos).Mag() < m_fArrivalDistance)
{
return;
}
bool shouldRecheck = forceUpdate;
bool atCurrentTarget = false;
//we're at our current target, will need to recheck
if (m_bIntermediaryGoToValid && (m_vCurrentGoToTarget - vehPos).Mag() < m_fArrivalDistance)
{
shouldRecheck = true;
atCurrentTarget = true;
}
if( m_bIsPerformingSearch)
{
UpdateSearchResults();
}
else
{
m_fSearchTime -= fTimeStep;
if(m_fSearchTime <= 0.0f || shouldRecheck)
{
m_fSearchTime = m_fSearchTimer;
if( CanNavigateStraightTo(vehPos, m_vTargetPosition) )
{
m_vCurrentGoToTarget = targetPos;
m_vLastValidTarget = targetPos;
}
else
{
if(atCurrentTarget || !m_bIntermediaryGoToValid)
{
//haven't got temp goto, or at current goto but still can't see target - find a new target
CalculateNewIntermediaryTarget(vehPos);
}
else
{
//we are going to a intermediary point - can we get there
if( m_bIntermediaryGoToValid )
{
if( !CanNavigateStraightTo(vehPos, m_vCurrentGoToTarget))
{
m_bIntermediaryGoToValid = false;
//we can't get to our last intermediary point, need to recheck
CalculateNewIntermediaryTarget(vehPos);
}
}
}
}
if( m_bIsPerformingSearch)
{
UpdateSearchResults();
}
}
}
}
void VehicleNavigationHelper::UpdateSearchResults()
{
Assertf(m_bIsPerformingSearch, "Trying to check search results when not searching");
if(!m_bStartNewSearch)
{
bool resultsReady = true;
for(int i = 0; i < m_iNumProbesUsed*2; ++i)
{
resultsReady &= m_navigationProbeResults[i].GetResultsReady();
}
if(resultsReady)
{
//check results
bool positionValid = false;
int validIndex;
for(validIndex = 0; validIndex < m_iNumProbesUsed; ++validIndex)
{
int otherIndex = validIndex + m_iNumProbesUsed;
//check current to new, and new to original are both clear
if ( !m_navigationProbeResults[validIndex][0].GetHitDetected() &&
!m_navigationProbeResults[otherIndex][0].GetHitDetected())
{
positionValid = true;
break;
}
else
{
positionValid = false;
if(m_pVehicle->InheritsFromSubmarine() || m_pVehicle->InheritsFromSubmarineCar())
{
positionValid = true;
if(m_navigationProbeResults[validIndex][0].GetHitDetected())
{
positionValid &= CanNavigateOverObject(m_navigationProbeResults[validIndex][0].GetHitPosition(), m_vTargetPosition);
}
if(positionValid && m_navigationProbeResults[otherIndex][0].GetHitDetected())
{
positionValid &= CanNavigateOverObject(m_navigationProbeResults[otherIndex][0].GetHitPosition(), m_vTargetPosition);
}
if( positionValid)
{
break;
}
}
}
}
if(positionValid)
{
m_vCurrentGoToTarget = m_navigationTargets[validIndex];
m_bIntermediaryGoToValid = true;
m_bIsPerformingSearch = false;
}
else
{
//try a more detailed test
if( m_currentSearchType == eSearchBasic)
{
m_currentSearchType = eSearchCircle;
m_bStartNewSearch = true;
}
else
{
m_currentSearchType = eSearchStart;
//we really can't find out how to get there, just try going to actual target
m_vCurrentGoToTarget = m_vTargetPosition;
m_bIntermediaryGoToValid = true;
m_bIsPerformingSearch = false;
}
}
//clear tests
for(int i = 0; i < m_iNumProbesUsed*2; ++i)
{
m_navigationProbeResults[i].Reset();
}
}
}
if(m_bStartNewSearch)
{
m_bStartNewSearch = false;
spdSphere compositeSphere = m_pVehicle->GetBoundSphere();
const Vector3 vVehiclePos = VEC3V_TO_VECTOR3(compositeSphere.GetCenter());
switch (m_currentSearchType)
{
case eSearchBasic: //checks positions at normal to collide point
{
m_iNumProbesUsed = 2;
Vector3 collidePoint(0.0f, 0.0f, 0.0f);
//get obstruction position
if(WorldCollisionTest(collidePoint, vVehiclePos, m_vTargetPosition, m_pTarget))
{
Vector3 toObstruction = collidePoint - vVehiclePos;
toObstruction.Normalize();
Vector3 toObstructionRight = Vector3(toObstruction.GetY(), -toObstruction.GetX(), toObstruction.GetZ());
float rightDistance = 10.0f; //base on distance or speed or something
for(int i = 0; i < m_iNumProbesUsed; i++)
{
WorldProbe::CShapeTestProbeDesc probe;
probe.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
probe.SetExcludeInstance(m_pVehicle->GetCurrentPhysicsInst());
probe.SetResultsStructure(&m_navigationProbeResults[i]);
probe.SetMaxNumResultsToUse(1);
WorldProbe::CShapeTestProbeDesc probe2;
probe2.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
probe2.SetExcludeInstance(m_pVehicle->GetCurrentPhysicsInst());
probe2.SetResultsStructure(&m_navigationProbeResults[i + m_iNumProbesUsed]);
probe2.SetMaxNumResultsToUse(1);
Vector3 newTargetPos = collidePoint + (toObstruction * rightDistance ) +
(toObstructionRight * ( i == 0 ? rightDistance : -rightDistance));
m_navigationTargets[i] = newTargetPos;
probe.SetStartAndEnd(newTargetPos, m_vTargetPosition); //new target to original
probe2.SetStartAndEnd(vVehiclePos, newTargetPos); //current position to new target
// #if DEBUG_DRAW
// grcDebugDraw::Line(VECTOR3_TO_VEC3V(vVehiclePos),VECTOR3_TO_VEC3V(newTargetPos), Color_blue, 5);
// grcDebugDraw::Line(VECTOR3_TO_VEC3V(newTargetPos),VECTOR3_TO_VEC3V(m_vTargetPosition), Color_red, 5);
// #endif
WorldProbe::GetShapeTestManager()->SubmitTest(probe, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
WorldProbe::GetShapeTestManager()->SubmitTest(probe2, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
}
}
break;
}
case eSearchCircle: //checks positions around the target position
{
m_iNumProbesUsed = s_iMaxNumProbes;
float circleRadius = 10.0f; //base on distance or speed or something
for(int i = 0; i < m_iNumProbesUsed; i++)
{
WorldProbe::CShapeTestProbeDesc probe;
probe.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
probe.SetExcludeInstance(m_pVehicle->GetCurrentPhysicsInst());
probe.SetResultsStructure(&m_navigationProbeResults[i]);
probe.SetMaxNumResultsToUse(1);
WorldProbe::CShapeTestProbeDesc probe2;
probe2.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES);
probe2.SetExcludeInstance(m_pVehicle->GetCurrentPhysicsInst());
probe2.SetResultsStructure(&m_navigationProbeResults[i + m_iNumProbesUsed]);
probe2.SetMaxNumResultsToUse(1);
//spread out points around circle
//pre-compute these
float angle = PI*2.0f / m_iNumProbesUsed * i;
Vector3 direction ( cosf(angle), sinf(angle), 0.0f);
Vector3 newTargetPos = vVehiclePos + (direction * circleRadius);
m_navigationTargets[i] = newTargetPos;
probe.SetStartAndEnd(newTargetPos, m_vTargetPosition);
probe2.SetStartAndEnd(vVehiclePos, newTargetPos);
// #if DEBUG_DRAW
// grcDebugDraw::Line(VECTOR3_TO_VEC3V(vVehiclePos),VECTOR3_TO_VEC3V(newTargetPos), Color_blue, 5);
// grcDebugDraw::Line(VECTOR3_TO_VEC3V(newTargetPos),VECTOR3_TO_VEC3V(m_vTargetPosition), Color_red, 5);
// #endif
WorldProbe::GetShapeTestManager()->SubmitTest(probe, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
WorldProbe::GetShapeTestManager()->SubmitTest(probe2, WorldProbe::PERFORM_ASYNCHRONOUS_TEST);
}
break;
}
default:
break;
}
}
}
void VehicleNavigationHelper::CalculateNewIntermediaryTarget(const Vector3& currentPos)
{
bool useLastKnown = UseLastKnownOverSearch(currentPos);
if(useLastKnown)
{
m_vCurrentGoToTarget = m_vLastValidTarget;
m_bIntermediaryGoToValid = true;
}
else
{
m_bIsPerformingSearch = true;
m_currentSearchType = eSearchBasic;
m_bStartNewSearch = true;
}
}
bool VehicleNavigationHelper::UseLastKnownOverSearch(const Vector3& currentPos)
{
if(CanNavigateStraightTo(currentPos, m_vLastValidTarget))
{
//some more detailed checks - take into account time, direction etc
return true;
}
return false;
}
void VehicleNavigationHelper::Init(const CVehicle* vehicle, const CEntity* pTarget, const Vector3& currentTarget)
{
m_pVehicle = vehicle;
m_pTarget = pTarget;
m_vCurrentGoToTarget = currentTarget;
m_vLastValidTarget = currentTarget;
m_vTargetPosition = currentTarget;
}
/////////////////////////////////////////////////////////////////////////////////////
#if !__FINAL
void VehicleNavigationHelper::Debug() const
{
#if DEBUG_DRAW
grcDebugDraw::Sphere(VECTOR3_TO_VEC3V(m_vTargetPosition), 0.1f, Color_blue, false);
if(m_bIntermediaryGoToValid)
{
grcDebugDraw::Sphere(VECTOR3_TO_VEC3V(m_vCurrentGoToTarget), 0.1f, Color_green, false);
if(!m_vLastValidTarget.IsEqual(m_vCurrentGoToTarget))
{
grcDebugDraw::Sphere(VECTOR3_TO_VEC3V(m_vLastValidTarget), 0.1f, Color_red, false);
}
}
#endif
}
#endif