You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
846 lines
31 KiB
C++
846 lines
31 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Perception/AISense_Sight.h"
|
|
#include "EngineDefines.h"
|
|
#include "EngineGlobals.h"
|
|
#include "CollisionQueryParams.h"
|
|
#include "Engine/Engine.h"
|
|
#include "AISystem.h"
|
|
#include "AIHelpers.h"
|
|
#include "Perception/AIPerceptionComponent.h"
|
|
#include "VisualLogger/VisualLogger.h"
|
|
#include "Perception/AISightTargetInterface.h"
|
|
#include "Perception/AISenseConfig_Sight.h"
|
|
|
|
#define DO_SIGHT_VLOGGING (0 && ENABLE_VISUAL_LOG)
|
|
|
|
#if DO_SIGHT_VLOGGING
|
|
#define SIGHT_LOG_SEGMENT(LogOwner, SegmentStart, SegmentEnd, Color, Format, ...) UE_VLOG_SEGMENT(LogOwner, LogAIPerception, Verbose, SegmentStart, SegmentEnd, Color, Format, ##__VA_ARGS__)
|
|
#define SIGHT_LOG_LOCATION(LogOwner, Location, Radius, Color, Format, ...) UE_VLOG_LOCATION(LogOwner, LogAIPerception, Verbose, Location, Radius, Color, Format, ##__VA_ARGS__)
|
|
#else
|
|
#define SIGHT_LOG_SEGMENT(...)
|
|
#define SIGHT_LOG_LOCATION(...)
|
|
#endif // DO_SIGHT_VLOGGING
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight"),STAT_AI_Sense_Sight,STATGROUP_AI);
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Update Sort"),STAT_AI_Sense_Sight_UpdateSort,STATGROUP_AI);
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Listener Update"), STAT_AI_Sense_Sight_ListenerUpdate, STATGROUP_AI);
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Register Target"), STAT_AI_Sense_Sight_RegisterTarget, STATGROUP_AI);
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove By Listener"), STAT_AI_Sense_Sight_RemoveByListener, STATGROUP_AI);
|
|
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove To Target"), STAT_AI_Sense_Sight_RemoveToTarget, STATGROUP_AI);
|
|
|
|
|
|
static const int32 DefaultMaxTracesPerTick = 6;
|
|
static const int32 DefaultMinQueriesPerTimeSliceCheck = 40;
|
|
|
|
enum class EForEachResult : uint8
|
|
{
|
|
Break,
|
|
Continue,
|
|
};
|
|
|
|
template <typename T, class PREDICATE_CLASS>
|
|
EForEachResult ForEach(T& Array, const PREDICATE_CLASS& Predicate)
|
|
{
|
|
for (typename T::ElementType& Element : Array)
|
|
{
|
|
if (Predicate(Element) == EForEachResult::Break)
|
|
{
|
|
return EForEachResult::Break;
|
|
}
|
|
}
|
|
return EForEachResult::Continue;
|
|
}
|
|
|
|
enum EReverseForEachResult : uint8
|
|
{
|
|
UnTouched,
|
|
Modified,
|
|
};
|
|
|
|
template <typename T, class PREDICATE_CLASS>
|
|
EReverseForEachResult ReverseForEach(T& Array, const PREDICATE_CLASS& Predicate)
|
|
{
|
|
EReverseForEachResult RetVal = EReverseForEachResult::UnTouched;
|
|
for (int32 Index = Array.Num()-1; Index >= 0; --Index)
|
|
{
|
|
if (Predicate(Array, Index) == EReverseForEachResult::Modified)
|
|
{
|
|
RetVal = EReverseForEachResult::Modified;
|
|
}
|
|
}
|
|
return RetVal;
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FAISightTarget
|
|
//----------------------------------------------------------------------//
|
|
const FAISightTarget::FTargetId FAISightTarget::InvalidTargetId = FAISystem::InvalidUnsignedID;
|
|
|
|
FAISightTarget::FAISightTarget(AActor* InTarget, FGenericTeamId InTeamId)
|
|
: Target(InTarget), SightTargetInterface(NULL), TeamId(InTeamId)
|
|
{
|
|
if (InTarget)
|
|
{
|
|
TargetId = InTarget->GetUniqueID();
|
|
}
|
|
else
|
|
{
|
|
TargetId = InvalidTargetId;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// FDigestedSightProperties
|
|
//----------------------------------------------------------------------//
|
|
UAISense_Sight::FDigestedSightProperties::FDigestedSightProperties(const UAISenseConfig_Sight& SenseConfig)
|
|
{
|
|
SightRadiusSq = FMath::Square(SenseConfig.SightRadius + SenseConfig.PointOfViewBackwardOffset);
|
|
LoseSightRadiusSq = FMath::Square(SenseConfig.LoseSightRadius + SenseConfig.PointOfViewBackwardOffset);
|
|
PointOfViewBackwardOffset = SenseConfig.PointOfViewBackwardOffset;
|
|
NearClippingRadiusSq = FMath::Square(SenseConfig.NearClippingRadius);
|
|
PeripheralVisionAngleCos = FMath::Cos(FMath::Clamp(FMath::DegreesToRadians(SenseConfig.PeripheralVisionAngleDegrees), 0.f, PI));
|
|
AffiliationFlags = SenseConfig.DetectionByAffiliation.GetAsFlags();
|
|
// keep the special value of FAISystem::InvalidRange (-1.f) if it's set.
|
|
AutoSuccessRangeSqFromLastSeenLocation = (SenseConfig.AutoSuccessRangeFromLastSeenLocation == FAISystem::InvalidRange) ? FAISystem::InvalidRange : FMath::Square(SenseConfig.AutoSuccessRangeFromLastSeenLocation);
|
|
}
|
|
|
|
UAISense_Sight::FDigestedSightProperties::FDigestedSightProperties()
|
|
: PeripheralVisionAngleCos(0.f), SightRadiusSq(-1.f), AutoSuccessRangeSqFromLastSeenLocation(FAISystem::InvalidRange), LoseSightRadiusSq(-1.f), PointOfViewBackwardOffset(0.0f), NearClippingRadiusSq(0.0f), AffiliationFlags(-1)
|
|
{}
|
|
|
|
//----------------------------------------------------------------------//
|
|
// UAISense_Sight
|
|
//----------------------------------------------------------------------//
|
|
UAISense_Sight::UAISense_Sight(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, MaxTracesPerTick(DefaultMaxTracesPerTick)
|
|
, MinQueriesPerTimeSliceCheck(DefaultMinQueriesPerTimeSliceCheck)
|
|
, MaxTimeSlicePerTick(0.005) // 5ms
|
|
, HighImportanceQueryDistanceThreshold(300.f)
|
|
, MaxQueryImportance(60.f)
|
|
, SightLimitQueryImportance(10.f)
|
|
{
|
|
if (HasAnyFlags(RF_ClassDefaultObject) == false)
|
|
{
|
|
UAISenseConfig_Sight* SightConfigCDO = GetMutableDefault<UAISenseConfig_Sight>();
|
|
SightConfigCDO->Implementation = UAISense_Sight::StaticClass();
|
|
|
|
OnNewListenerDelegate.BindUObject(this, &UAISense_Sight::OnNewListenerImpl);
|
|
OnListenerUpdateDelegate.BindUObject(this, &UAISense_Sight::OnListenerUpdateImpl);
|
|
OnListenerRemovedDelegate.BindUObject(this, &UAISense_Sight::OnListenerRemovedImpl);
|
|
}
|
|
|
|
NotifyType = EAISenseNotifyType::OnPerceptionChange;
|
|
|
|
bAutoRegisterAllPawnsAsSources = true;
|
|
bNeedsForgettingNotification = true;
|
|
|
|
DefaultSightCollisionChannel = GET_AI_CONFIG_VAR(DefaultSightCollisionChannel);
|
|
}
|
|
|
|
FORCEINLINE_DEBUGGABLE float UAISense_Sight::CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const
|
|
{
|
|
const float DistanceSq = FVector::DistSquared(Listener.CachedLocation, TargetLocation);
|
|
return DistanceSq <= HighImportanceDistanceSquare ? MaxQueryImportance
|
|
: FMath::Clamp((SightLimitQueryImportance - MaxQueryImportance) / SightRadiusSq * DistanceSq + MaxQueryImportance, 0.f, MaxQueryImportance);
|
|
}
|
|
|
|
void UAISense_Sight::PostInitProperties()
|
|
{
|
|
Super::PostInitProperties();
|
|
HighImportanceDistanceSquare = FMath::Square(HighImportanceQueryDistanceThreshold);
|
|
}
|
|
|
|
bool UAISense_Sight::ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQuery* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const
|
|
{
|
|
OutStimulusStrength = 1.0f;
|
|
|
|
if ((PropDigest.AutoSuccessRangeSqFromLastSeenLocation != FAISystem::InvalidRange) && (SightQuery->LastSeenLocation != FAISystem::InvalidLocation))
|
|
{
|
|
const float DistanceToLastSeenLocationSq = FVector::DistSquared(TargetActor->GetActorLocation(), SightQuery->LastSeenLocation);
|
|
return (DistanceToLastSeenLocationSq <= PropDigest.AutoSuccessRangeSqFromLastSeenLocation);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
float UAISense_Sight::Update()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight);
|
|
|
|
const UWorld* World = GEngine->GetWorldFromContextObject(GetPerceptionSystem()->GetOuter(), EGetWorldErrorMode::LogAndReturnNull);
|
|
|
|
if (World == NULL)
|
|
{
|
|
return SuspendNextUpdate;
|
|
}
|
|
|
|
// sort Sight Queries
|
|
{
|
|
auto RecalcScore = [](FAISightQuery& SightQuery)->EForEachResult
|
|
{
|
|
SightQuery.RecalcScore();
|
|
return EForEachResult::Continue;
|
|
};
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_UpdateSort);
|
|
// Sort out of range queries
|
|
if (bSightQueriesOutOfRangeDirty)
|
|
{
|
|
ForEach(SightQueriesOutOfRange, RecalcScore);
|
|
SightQueriesOutOfRange.Sort(FAISightQuery::FSortPredicate());
|
|
NextOutOfRangeIndex = 0;
|
|
bSightQueriesOutOfRangeDirty = false;
|
|
}
|
|
|
|
// Sort in range queries
|
|
ForEach(SightQueriesInRange, RecalcScore);
|
|
SightQueriesInRange.Sort(FAISightQuery::FSortPredicate());
|
|
}
|
|
|
|
int32 TracesCount = 0;
|
|
int32 NumQueriesProcessed = 0;
|
|
double TimeSliceEnd = FPlatformTime::Seconds() + MaxTimeSlicePerTick;
|
|
bool bHitTimeSliceLimit = false;
|
|
//#define AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
double TimeSpent = 0.0;
|
|
double LastTime = FPlatformTime::Seconds();
|
|
#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
static const int32 InitialInvalidItemsSize = 16;
|
|
enum class EOperationType : uint8
|
|
{
|
|
Remove,
|
|
SwapList
|
|
};
|
|
struct FQueryOperation
|
|
{
|
|
FQueryOperation(bool bInInRange, EOperationType InOpType, int32 InIndex) : bInRange(bInInRange), OpType(InOpType), Index(InIndex) {}
|
|
bool bInRange;
|
|
EOperationType OpType;
|
|
int32 Index;
|
|
};
|
|
TArray<FQueryOperation> QueryOperations;
|
|
TArray<FAISightTarget::FTargetId> InvalidTargets;
|
|
QueryOperations.Reserve(InitialInvalidItemsSize);
|
|
InvalidTargets.Reserve(InitialInvalidItemsSize);
|
|
|
|
AIPerception::FListenerMap& ListenersMap = *GetListeners();
|
|
|
|
int32 InRangeItr = 0;
|
|
int32 OutOfRangeItr = 0;
|
|
for (int32 QueryIndex = 0; QueryIndex < SightQueriesInRange.Num() + SightQueriesOutOfRange.Num(); ++QueryIndex)
|
|
{
|
|
// Calculate next in range query
|
|
int32 InRangeIndex = SightQueriesInRange.IsValidIndex(InRangeItr) ? InRangeItr : INDEX_NONE;
|
|
FAISightQuery* InRangeQuery = InRangeIndex != INDEX_NONE ? &SightQueriesInRange[InRangeIndex] : nullptr;
|
|
|
|
// Calculate next out of range query
|
|
int32 OutOfRangeIndex = SightQueriesOutOfRange.IsValidIndex(OutOfRangeItr) ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : INDEX_NONE;
|
|
FAISightQuery* OutOfRangeQuery = OutOfRangeIndex != INDEX_NONE ? &SightQueriesOutOfRange[OutOfRangeIndex] : nullptr;
|
|
if (OutOfRangeQuery)
|
|
{
|
|
OutOfRangeQuery->RecalcScore();
|
|
}
|
|
|
|
// Compare to real find next query
|
|
const bool bIsInRangeQuery = (InRangeQuery && OutOfRangeQuery) ? FAISightQuery::FSortPredicate()(*InRangeQuery,*OutOfRangeQuery) : !OutOfRangeQuery;
|
|
FAISightQuery* SightQuery = bIsInRangeQuery ? InRangeQuery : OutOfRangeQuery;
|
|
|
|
// Time slice limit check - spread out checks to every N queries so we don't spend more time checking timer than doing work
|
|
NumQueriesProcessed++;
|
|
#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
TimeSpent += (FPlatformTime::Seconds() - LastTime);
|
|
LastTime = FPlatformTime::Seconds();
|
|
#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
if (bHitTimeSliceLimit == false && (NumQueriesProcessed % MinQueriesPerTimeSliceCheck) == 0 && FPlatformTime::Seconds() > TimeSliceEnd)
|
|
{
|
|
bHitTimeSliceLimit = true;
|
|
// do not break here since that would bypass queue aging
|
|
}
|
|
|
|
if (TracesCount < MaxTracesPerTick && bHitTimeSliceLimit == false)
|
|
{
|
|
bIsInRangeQuery ? ++InRangeItr : ++OutOfRangeItr;
|
|
|
|
FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId];
|
|
FAISightTarget& Target = ObservedTargets[SightQuery->TargetId];
|
|
|
|
AActor* TargetActor = Target.Target.Get();
|
|
UAIPerceptionComponent* ListenerPtr = Listener.Listener.Get();
|
|
ensure(ListenerPtr);
|
|
|
|
// @todo figure out what should we do if not valid
|
|
if (TargetActor && ListenerPtr)
|
|
{
|
|
const FVector TargetLocation = TargetActor->GetActorLocation();
|
|
const FDigestedSightProperties& PropDigest = DigestedProperties[SightQuery->ObserverId];
|
|
const float SightRadiusSq = SightQuery->bLastResult ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq;
|
|
|
|
float StimulusStrength = 1.f;
|
|
|
|
// @Note that automagical "seeing" does not care about sight range nor vision cone
|
|
const bool bShouldAutomatically = ShouldAutomaticallySeeTarget(PropDigest, SightQuery, Listener, TargetActor, StimulusStrength);
|
|
if (bShouldAutomatically)
|
|
{
|
|
// Pretend like we've seen this target where we last saw them
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, SightQuery->LastSeenLocation, Listener.CachedLocation));
|
|
SightQuery->bLastResult = true;
|
|
}
|
|
else if (FAISystem::CheckIsTargetInSightCone(Listener.CachedLocation, Listener.CachedDirection, PropDigest.PeripheralVisionAngleCos, PropDigest.PointOfViewBackwardOffset, PropDigest.NearClippingRadiusSq, SightRadiusSq, TargetLocation))
|
|
{
|
|
SIGHT_LOG_SEGMENT(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Green, TEXT("TargetID %d"), Target.TargetId);
|
|
|
|
FVector OutSeenLocation(0.f);
|
|
// do line checks
|
|
if (Target.SightTargetInterface != NULL)
|
|
{
|
|
int32 NumberOfLoSChecksPerformed = 0;
|
|
// defaulting to 1 to have "full strength" by default instead of "no strength"
|
|
const bool bWasVisible = SightQuery->bLastResult;
|
|
if (Target.SightTargetInterface->CanBeSeenFrom(Listener.CachedLocation, OutSeenLocation, NumberOfLoSChecksPerformed, StimulusStrength, ListenerPtr->GetBodyActor(), &bWasVisible, &SightQuery->UserData) == true)
|
|
{
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, OutSeenLocation, Listener.CachedLocation));
|
|
SightQuery->bLastResult = true;
|
|
SightQuery->LastSeenLocation = OutSeenLocation;
|
|
}
|
|
// communicate failure only if we've seen give actor before
|
|
else if (SightQuery->bLastResult == true)
|
|
{
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
|
|
SightQuery->bLastResult = false;
|
|
SightQuery->LastSeenLocation = FAISystem::InvalidLocation;
|
|
}
|
|
|
|
if (SightQuery->bLastResult == false)
|
|
{
|
|
SIGHT_LOG_LOCATION(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT(""));
|
|
}
|
|
|
|
TracesCount += NumberOfLoSChecksPerformed;
|
|
}
|
|
else
|
|
{
|
|
// we need to do tests ourselves
|
|
FHitResult HitResult;
|
|
const bool bHit = World->LineTraceSingleByChannel(HitResult, Listener.CachedLocation, TargetLocation
|
|
, DefaultSightCollisionChannel
|
|
, FCollisionQueryParams(SCENE_QUERY_STAT(AILineOfSight), true, ListenerPtr->GetBodyActor()));
|
|
|
|
++TracesCount;
|
|
|
|
auto HitResultActorIsOwnedByTargetActor = [&HitResult, TargetActor]()
|
|
{
|
|
AActor* HitResultActor = HitResult.HitObjectHandle.FetchActor();
|
|
return (HitResultActor ? HitResultActor->IsOwnedBy(TargetActor) : false);
|
|
};
|
|
|
|
if (bHit == false || HitResultActorIsOwnedByTargetActor())
|
|
{
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 1.f, TargetLocation, Listener.CachedLocation));
|
|
SightQuery->bLastResult = true;
|
|
SightQuery->LastSeenLocation = TargetLocation;
|
|
}
|
|
// communicate failure only if we've seen give actor before
|
|
else if (SightQuery->bLastResult == true)
|
|
{
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
|
|
SightQuery->bLastResult = false;
|
|
SightQuery->LastSeenLocation = FAISystem::InvalidLocation;
|
|
}
|
|
|
|
if (SightQuery->bLastResult == false)
|
|
{
|
|
SIGHT_LOG_LOCATION(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT(""));
|
|
}
|
|
}
|
|
}
|
|
// communicate failure only if we've seen give actor before
|
|
else if (SightQuery->bLastResult)
|
|
{
|
|
SIGHT_LOG_SEGMENT(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Red, TEXT("TargetID %d"), Target.TargetId);
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
|
|
SightQuery->bLastResult = false;
|
|
}
|
|
|
|
SightQuery->Importance = CalcQueryImportance(Listener, TargetLocation, SightRadiusSq);
|
|
const bool bShouldBeInRange = SightQuery->Importance > 0.0f;
|
|
if (bIsInRangeQuery != bShouldBeInRange)
|
|
{
|
|
QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::SwapList, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex));
|
|
}
|
|
|
|
// restart query
|
|
SightQuery->OnProcessed();
|
|
}
|
|
else
|
|
{
|
|
// put this index to "to be removed" array
|
|
QueryOperations.Add( FQueryOperation(bIsInRangeQuery, EOperationType::Remove, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex) );
|
|
if (TargetActor == nullptr)
|
|
{
|
|
InvalidTargets.AddUnique(SightQuery->TargetId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
NextOutOfRangeIndex = SightQueriesOutOfRange.Num() > 0 ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : 0;
|
|
|
|
#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
UE_LOG(LogAIPerception, VeryVerbose, TEXT("UAISense_Sight::Update processed %d sources in %f seconds [time slice limited? %d]"), NumQueriesProcessed, TimeSpent, bHitTimeSliceLimit ? 1 : 0);
|
|
#else
|
|
UE_LOG(LogAIPerception, VeryVerbose, TEXT("UAISense_Sight::Update processed %d sources [time slice limited? %d]"), NumQueriesProcessed, bHitTimeSliceLimit ? 1 : 0);
|
|
#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
|
|
|
|
if (QueryOperations.Num() > 0)
|
|
{
|
|
// Sort by InRange and by descending Index
|
|
QueryOperations.Sort([](const FQueryOperation& LHS, const FQueryOperation& RHS)->bool
|
|
{
|
|
if (LHS.bInRange != RHS.bInRange)
|
|
return LHS.bInRange;
|
|
return LHS.Index > RHS.Index;
|
|
});
|
|
// Do all the removes first and save the out of range swaps because we will insert them at the right location to prevent sorting
|
|
TArray<FAISightQuery> SightQueriesOutOfRangeToInsert;
|
|
for (FQueryOperation& Operation : QueryOperations)
|
|
{
|
|
if (Operation.OpType == EOperationType::SwapList)
|
|
{
|
|
if (Operation.bInRange)
|
|
{
|
|
SightQueriesOutOfRangeToInsert.Push(SightQueriesInRange[Operation.Index]);
|
|
}
|
|
else
|
|
{
|
|
SightQueriesInRange.Add(SightQueriesOutOfRange[Operation.Index]);
|
|
}
|
|
}
|
|
|
|
if (Operation.bInRange)
|
|
{
|
|
// In range queries are always sorted at the beginning of the update
|
|
SightQueriesInRange.RemoveAtSwap(Operation.Index, 1, /*bAllowShrinking*/false);
|
|
}
|
|
else
|
|
{
|
|
// Preserve the list ordered
|
|
SightQueriesOutOfRange.RemoveAt(Operation.Index, 1, /*bAllowShrinking*/false);
|
|
if (Operation.Index < NextOutOfRangeIndex)
|
|
{
|
|
NextOutOfRangeIndex--;
|
|
}
|
|
}
|
|
}
|
|
// Reinsert the saved out of range swaps
|
|
if (SightQueriesOutOfRangeToInsert.Num() > 0)
|
|
{
|
|
SightQueriesOutOfRange.Insert(SightQueriesOutOfRangeToInsert.GetData(), SightQueriesOutOfRangeToInsert.Num(), NextOutOfRangeIndex);
|
|
NextOutOfRangeIndex += SightQueriesOutOfRangeToInsert.Num();
|
|
}
|
|
|
|
if (InvalidTargets.Num() > 0)
|
|
{
|
|
// this should not be happening since UAIPerceptionSystem::OnPerceptionStimuliSourceEndPlay introduction
|
|
UE_VLOG(GetPerceptionSystem(), LogAIPerception, Error, TEXT("Invalid sight targets found during UAISense_Sight::Update call"));
|
|
|
|
for (const auto& TargetId : InvalidTargets)
|
|
{
|
|
// remove affected queries
|
|
RemoveAllQueriesToTarget(TargetId);
|
|
// remove target itself
|
|
ObservedTargets.Remove(TargetId);
|
|
}
|
|
|
|
// remove holes
|
|
ObservedTargets.Compact();
|
|
}
|
|
}
|
|
|
|
//return SightQueries.Num() > 0 ? 1.f/6 : FLT_MAX;
|
|
return 0.f;
|
|
}
|
|
|
|
void UAISense_Sight::RegisterEvent(const FAISightEvent& Event)
|
|
{
|
|
|
|
}
|
|
|
|
void UAISense_Sight::RegisterSource(AActor& SourceActor)
|
|
{
|
|
RegisterTarget(SourceActor);
|
|
}
|
|
|
|
void UAISense_Sight::UnregisterSource(AActor& SourceActor)
|
|
{
|
|
const FAISightTarget::FTargetId AsTargetId = SourceActor.GetUniqueID();
|
|
FAISightTarget AsTarget;
|
|
|
|
if (ObservedTargets.RemoveAndCopyValue(AsTargetId, AsTarget)
|
|
&& (SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) > 0)
|
|
{
|
|
AActor* TargetActor = AsTarget.Target.Get();
|
|
|
|
if (TargetActor)
|
|
{
|
|
// notify all interested observers that this source is no longer
|
|
// visible
|
|
AIPerception::FListenerMap& ListenersMap = *GetListeners();
|
|
auto RemoveQuery = [this,&ListenersMap,&AsTargetId,&TargetActor](TArray<FAISightQuery>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
|
|
{
|
|
FAISightQuery* SightQuery = &SightQueries[QueryIndex];
|
|
if (SightQuery->TargetId == AsTargetId)
|
|
{
|
|
if (SightQuery->bLastResult == true)
|
|
{
|
|
FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId];
|
|
ensure(Listener.Listener.IsValid());
|
|
|
|
Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, SightQuery->LastSeenLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
|
|
}
|
|
|
|
SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
|
|
return EReverseForEachResult::Modified;
|
|
}
|
|
return EReverseForEachResult::UnTouched;
|
|
};
|
|
ReverseForEach(SightQueriesInRange, RemoveQuery);
|
|
if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified)
|
|
{
|
|
bSightQueriesOutOfRangeDirty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UAISense_Sight::RegisterTarget(AActor& TargetActor, const TFunction<void(FAISightQuery&)>& OnAddedFunc /*= nullptr*/)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RegisterTarget);
|
|
|
|
FAISightTarget* SightTarget = ObservedTargets.Find(TargetActor.GetUniqueID());
|
|
|
|
if (SightTarget != nullptr && SightTarget->GetTargetActor() != &TargetActor)
|
|
{
|
|
// this means given unique ID has already been recycled.
|
|
FAISightTarget NewSightTarget(&TargetActor);
|
|
|
|
SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget));
|
|
SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor);
|
|
}
|
|
else if (SightTarget == nullptr)
|
|
{
|
|
FAISightTarget NewSightTarget(&TargetActor);
|
|
|
|
SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget));
|
|
SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor);
|
|
}
|
|
|
|
// set/update data
|
|
SightTarget->TeamId = FGenericTeamId::GetTeamIdentifier(&TargetActor);
|
|
|
|
// generate all pairs and add them to current Sight Queries
|
|
bool bNewQueriesAdded = false;
|
|
AIPerception::FListenerMap& ListenersMap = *GetListeners();
|
|
const FVector TargetLocation = TargetActor.GetActorLocation();
|
|
|
|
for (AIPerception::FListenerMap::TConstIterator ItListener(ListenersMap); ItListener; ++ItListener)
|
|
{
|
|
const FPerceptionListener& Listener = ItListener->Value;
|
|
const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent();
|
|
|
|
if (Listener.HasSense(GetSenseID()) && Listener.GetBodyActor() != &TargetActor)
|
|
{
|
|
const FDigestedSightProperties& PropDigest = DigestedProperties[Listener.GetListenerID()];
|
|
if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, TargetActor, PropDigest.AffiliationFlags))
|
|
{
|
|
// create a sight query
|
|
const float Importance = CalcQueryImportance(ItListener->Value, TargetLocation, PropDigest.SightRadiusSq);
|
|
const bool bInRange = Importance > 0.0f;
|
|
if (!bInRange)
|
|
{
|
|
bSightQueriesOutOfRangeDirty = true;
|
|
}
|
|
FAISightQuery& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef();
|
|
AddedQuery.ObserverId = ItListener->Key;
|
|
AddedQuery.TargetId = SightTarget->TargetId;
|
|
AddedQuery.Importance = Importance;
|
|
|
|
if (OnAddedFunc)
|
|
{
|
|
OnAddedFunc(AddedQuery);
|
|
}
|
|
bNewQueriesAdded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort Sight Queries
|
|
if (bNewQueriesAdded)
|
|
{
|
|
RequestImmediateUpdate();
|
|
}
|
|
|
|
return bNewQueriesAdded;
|
|
}
|
|
|
|
void UAISense_Sight::OnNewListenerImpl(const FPerceptionListener& NewListener)
|
|
{
|
|
UAIPerceptionComponent* NewListenerPtr = NewListener.Listener.Get();
|
|
check(NewListenerPtr);
|
|
const UAISenseConfig_Sight* SenseConfig = Cast<const UAISenseConfig_Sight>(NewListenerPtr->GetSenseConfig(GetSenseID()));
|
|
check(SenseConfig);
|
|
const FDigestedSightProperties PropertyDigest(*SenseConfig);
|
|
DigestedProperties.Add(NewListener.GetListenerID(), PropertyDigest);
|
|
|
|
GenerateQueriesForListener(NewListener, PropertyDigest);
|
|
}
|
|
|
|
void UAISense_Sight::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction<void(FAISightQuery&)>& OnAddedFunc/*= nullptr */)
|
|
{
|
|
bool bNewQueriesAdded = false;
|
|
const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent();
|
|
const AActor* Avatar = Listener.GetBodyActor();
|
|
|
|
// create sight queries with all legal targets
|
|
for (FTargetsContainer::TConstIterator ItTarget(ObservedTargets); ItTarget; ++ItTarget)
|
|
{
|
|
const AActor* TargetActor = ItTarget->Value.GetTargetActor();
|
|
if (TargetActor == NULL || TargetActor == Avatar)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, *TargetActor, PropertyDigest.AffiliationFlags))
|
|
{
|
|
// create a sight query
|
|
const float Importance = CalcQueryImportance(Listener, ItTarget->Value.GetLocationSimple(), PropertyDigest.SightRadiusSq);
|
|
const bool bInRange = Importance > 0.0f;
|
|
if (!bInRange)
|
|
{
|
|
bSightQueriesOutOfRangeDirty = true;
|
|
}
|
|
FAISightQuery& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef();
|
|
AddedQuery.ObserverId = Listener.GetListenerID();
|
|
AddedQuery.TargetId = ItTarget->Key;
|
|
AddedQuery.Importance = Importance;
|
|
|
|
if (OnAddedFunc)
|
|
{
|
|
OnAddedFunc(AddedQuery);
|
|
}
|
|
bNewQueriesAdded = true;
|
|
}
|
|
}
|
|
|
|
// sort Sight Queries
|
|
if (bNewQueriesAdded)
|
|
{
|
|
RequestImmediateUpdate();
|
|
}
|
|
}
|
|
|
|
void UAISense_Sight::OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ListenerUpdate);
|
|
|
|
// first, naive implementation:
|
|
// 1. remove all queries by this listener
|
|
// 2. proceed as if it was a new listener
|
|
|
|
// see if this listener is a Target as well
|
|
const FAISightTarget::FTargetId AsTargetId = UpdatedListener.GetBodyActorUniqueID();
|
|
FAISightTarget* AsTarget = ObservedTargets.Find(AsTargetId);
|
|
if (AsTarget != NULL)
|
|
{
|
|
if (AsTarget->Target.IsValid())
|
|
{
|
|
// if still a valid target then backup list of observers for which the listener was visible to restore in the newly created queries
|
|
TSet<FPerceptionListenerID> LastVisibleObservers;
|
|
RemoveAllQueriesToTarget(AsTargetId, [&LastVisibleObservers](const FAISightQuery& Query)
|
|
{
|
|
if (Query.bLastResult)
|
|
{
|
|
LastVisibleObservers.Add(Query.ObserverId);
|
|
}
|
|
});
|
|
|
|
RegisterTarget(*(AsTarget->Target.Get()), [&LastVisibleObservers](FAISightQuery& Query)
|
|
{
|
|
Query.bLastResult = LastVisibleObservers.Contains(Query.ObserverId);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
RemoveAllQueriesToTarget(AsTargetId);
|
|
}
|
|
}
|
|
|
|
const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID();
|
|
|
|
if (UpdatedListener.HasSense(GetSenseID()))
|
|
{
|
|
// if still a valid sense then backup list of targets that were visible by the listener to restore in the newly created queries
|
|
TSet<FAISightTarget::FTargetId> LastVisibleTargets;
|
|
RemoveAllQueriesByListener(UpdatedListener, [&LastVisibleTargets](const FAISightQuery& Query)
|
|
{
|
|
if (Query.bLastResult)
|
|
{
|
|
LastVisibleTargets.Add(Query.TargetId);
|
|
}
|
|
});
|
|
|
|
const UAISenseConfig_Sight* SenseConfig = Cast<const UAISenseConfig_Sight>(UpdatedListener.Listener->GetSenseConfig(GetSenseID()));
|
|
check(SenseConfig);
|
|
FDigestedSightProperties& PropertiesDigest = DigestedProperties.FindOrAdd(ListenerID);
|
|
PropertiesDigest = FDigestedSightProperties(*SenseConfig);
|
|
|
|
GenerateQueriesForListener(UpdatedListener, PropertiesDigest, [&LastVisibleTargets](FAISightQuery& Query)
|
|
{
|
|
Query.bLastResult = LastVisibleTargets.Contains(Query.TargetId);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// remove all queries
|
|
RemoveAllQueriesByListener(UpdatedListener);
|
|
|
|
DigestedProperties.Remove(ListenerID);
|
|
}
|
|
}
|
|
|
|
void UAISense_Sight::OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener)
|
|
{
|
|
bool bSkipListenerUpdate = false;
|
|
const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID();
|
|
|
|
FDigestedSightProperties* PropertiesDigest = DigestedProperties.Find(ListenerID);
|
|
if (PropertiesDigest)
|
|
{
|
|
// The only parameter we need to rebuild all the queries for this listener is if the affiliation mask changed, otherwise there is nothing to update.
|
|
const UAISenseConfig_Sight* SenseConfig = CastChecked<const UAISenseConfig_Sight>(UpdatedListener.Listener->GetSenseConfig(GetSenseID()));
|
|
FDigestedSightProperties NewPropertiesDigest(*SenseConfig);
|
|
bSkipListenerUpdate = NewPropertiesDigest.AffiliationFlags == PropertiesDigest->AffiliationFlags;
|
|
*PropertiesDigest = NewPropertiesDigest;
|
|
}
|
|
|
|
if (!bSkipListenerUpdate)
|
|
{
|
|
Super::OnListenerConfigUpdated(UpdatedListener);
|
|
}
|
|
}
|
|
|
|
|
|
void UAISense_Sight::OnListenerRemovedImpl(const FPerceptionListener& RemovedListener)
|
|
{
|
|
RemoveAllQueriesByListener(RemovedListener);
|
|
|
|
DigestedProperties.FindAndRemoveChecked(RemovedListener.GetListenerID());
|
|
|
|
// note: there use to be code to remove all queries _to_ listener here as well
|
|
// but that was wrong - the fact that a listener gets unregistered doesn't have to
|
|
// mean it's being removed from the game altogether.
|
|
}
|
|
|
|
void UAISense_Sight::RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction<void(const FAISightQuery&)>& OnRemoveFunc/*= nullptr */)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveByListener);
|
|
|
|
if ((SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const uint32 ListenerId = Listener.GetListenerID();
|
|
|
|
auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray<FAISightQuery>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
|
|
{
|
|
const FAISightQuery& SightQuery = SightQueries[QueryIndex];
|
|
|
|
if (SightQuery.ObserverId == ListenerId)
|
|
{
|
|
if (OnRemoveFunc)
|
|
{
|
|
OnRemoveFunc(SightQuery);
|
|
}
|
|
SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
|
|
return EReverseForEachResult::Modified;
|
|
}
|
|
return EReverseForEachResult::UnTouched;
|
|
};
|
|
ReverseForEach(SightQueriesInRange, RemoveQuery);
|
|
if(ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified)
|
|
{
|
|
bSightQueriesOutOfRangeDirty = true;
|
|
}
|
|
}
|
|
|
|
void UAISense_Sight::RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, const TFunction<void(const FAISightQuery&)>& OnRemoveFunc/*= nullptr */)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveToTarget);
|
|
|
|
auto RemoveQuery = [&TargetId, &OnRemoveFunc](TArray<FAISightQuery>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
|
|
{
|
|
const FAISightQuery& SightQuery = SightQueries[QueryIndex];
|
|
|
|
if (SightQuery.TargetId == TargetId)
|
|
{
|
|
if (OnRemoveFunc)
|
|
{
|
|
OnRemoveFunc(SightQuery);
|
|
}
|
|
SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
|
|
return EReverseForEachResult::Modified;
|
|
}
|
|
return EReverseForEachResult::UnTouched;
|
|
};
|
|
ReverseForEach(SightQueriesInRange, RemoveQuery);
|
|
if (ReverseForEach(SightQueriesOutOfRange, RemoveQuery) == EReverseForEachResult::Modified)
|
|
{
|
|
bSightQueriesOutOfRangeDirty = true;
|
|
}
|
|
}
|
|
|
|
void UAISense_Sight::OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget)
|
|
{
|
|
const uint32 ListenerId = Listener.GetListenerID();
|
|
const uint32 TargetId = ActorToForget.GetUniqueID();
|
|
|
|
auto ForgetPreviousResult = [&ListenerId, &TargetId](FAISightQuery& SightQuery)->EForEachResult
|
|
{
|
|
if (SightQuery.ObserverId == ListenerId && SightQuery.TargetId == TargetId)
|
|
{
|
|
// assuming one query per observer-target pair
|
|
SightQuery.ForgetPreviousResult();
|
|
return EForEachResult::Break;
|
|
}
|
|
return EForEachResult::Continue;
|
|
};
|
|
|
|
if (ForEach(SightQueriesInRange, ForgetPreviousResult) == EForEachResult::Continue)
|
|
{
|
|
ForEach(SightQueriesOutOfRange, ForgetPreviousResult);
|
|
}
|
|
}
|
|
|
|
void UAISense_Sight::OnListenerForgetsAll(const FPerceptionListener& Listener)
|
|
{
|
|
const uint32 ListenerId = Listener.GetListenerID();
|
|
|
|
auto ForgetPreviousResult = [&ListenerId](FAISightQuery& SightQuery)->EForEachResult
|
|
{
|
|
if (SightQuery.ObserverId == ListenerId)
|
|
{
|
|
SightQuery.ForgetPreviousResult();
|
|
}
|
|
return EForEachResult::Continue;
|
|
};
|
|
|
|
ForEach(SightQueriesInRange, ForgetPreviousResult);
|
|
ForEach(SightQueriesOutOfRange, ForgetPreviousResult);
|
|
}
|