A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
VRAIPerceptionOverrides.cpp
Go to the documentation of this file.
1// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2
4#include "EngineDefines.h"
5#include "EngineGlobals.h"
6#include "CollisionQueryParams.h"
7#include "Engine/Engine.h"
8#include "AIModule/Classes/AISystem.h"
9#include "AIModule/Classes/Perception/AIPerceptionComponent.h"
10#include "VisualLogger/VisualLogger.h"
11#include "AIModule/Classes/Perception/AISightTargetInterface.h"
12#include "AIModule/Classes/Perception/AISenseConfig_Sight.h"
13#include "AIModule/Classes/Perception/AIPerceptionSystem.h"
14
15#if WITH_GAMEPLAY_DEBUGGER
16#include "GameplayDebugger/Public/GameplayDebuggerTypes.h"
17#include "GameplayDebugger/Public/GameplayDebuggerCategory.h"
18#endif
19DEFINE_LOG_CATEGORY(LogAIPerceptionVR);
20
21#define DO_SIGHT_VLOGGINGVR (0 && ENABLE_VISUAL_LOG)
22
23#if DO_SIGHT_VLOGGINGVR
24#define SIGHT_LOG_SEGMENTVR UE_VLOG_SEGMENT
25#define SIGHT_LOG_LOCATIONVR UE_VLOG_LOCATION
26#else
27#define SIGHT_LOG_SEGMENTVR(...)
28#define SIGHT_LOG_LOCATIONVR(...)
29#endif // DO_SIGHT_VLOGGING
30
31DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight"), STAT_AI_Sense_Sight, STATGROUP_AI);
32DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Update Sort"), STAT_AI_Sense_Sight_UpdateSort, STATGROUP_AI);
33DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Listener Update"), STAT_AI_Sense_Sight_ListenerUpdate, STATGROUP_AI);
34DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Register Target"), STAT_AI_Sense_Sight_RegisterTarget, STATGROUP_AI);
35DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove By Listener"), STAT_AI_Sense_Sight_RemoveByListener, STATGROUP_AI);
36DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight, Remove To Target"), STAT_AI_Sense_Sight_RemoveToTarget, STATGROUP_AI);
37
38
39static const int32 DefaultMaxTracesPerTick = 6;
40static const int32 DefaultMinQueriesPerTimeSliceCheck = 40;
41
42enum class EForEachResult : uint8
43{
44 Break,
46};
47
48template <typename T, class PREDICATE_CLASS>
49EForEachResult ForEach(T& Array, const PREDICATE_CLASS& Predicate)
50{
51 for (typename T::ElementType& Element : Array)
52 {
53 if (Predicate(Element) == EForEachResult::Break)
54 {
56 }
57 }
59}
60
66
67template <typename T, class PREDICATE_CLASS>
68EReverseForEachResult ReverseForEach(T& Array, const PREDICATE_CLASS& Predicate)
69{
71 for (int32 Index = Array.Num() - 1; Index >= 0; --Index)
72 {
73 if (Predicate(Array, Index) == EReverseForEachResult::Modified)
74 {
76 }
77 }
78 return RetVal;
79}
80
81//----------------------------------------------------------------------//
82// helpers
83//----------------------------------------------------------------------//
84FORCEINLINE_DEBUGGABLE bool CheckIsTargetInSightPie(const FPerceptionListener& Listener, const UAISense_Sight_VR::FDigestedSightProperties& DigestedProps, const FVector& TargetLocation, const float SightRadiusSq)
85{
86 if (FVector::DistSquared(Listener.CachedLocation, TargetLocation) <= SightRadiusSq)
87 {
88 const FVector DirectionToTarget = (TargetLocation - Listener.CachedLocation).GetUnsafeNormal();
89 return FVector::DotProduct(DirectionToTarget, Listener.CachedDirection) > DigestedProps.PeripheralVisionAngleCos;
90 }
91
92 return false;
93}
94
95//----------------------------------------------------------------------//
96// FAISightTargetVR
97//----------------------------------------------------------------------//
98const FAISightTargetVR::FTargetId FAISightTargetVR::InvalidTargetId = FAISystem::InvalidUnsignedID;
99
100FAISightTargetVR::FAISightTargetVR(AActor* InTarget, FGenericTeamId InTeamId)
101 : Target(InTarget), SightTargetInterface(NULL), TeamId(InTeamId)
102{
103 if (InTarget)
104 {
105 TargetId = InTarget->GetUniqueID();
106 }
107 else
108 {
110 }
111}
112
113//----------------------------------------------------------------------//
114// FDigestedSightProperties
115//----------------------------------------------------------------------//
117{
118 SightRadiusSq = FMath::Square(SenseConfig.SightRadius);
119 LoseSightRadiusSq = FMath::Square(SenseConfig.LoseSightRadius);
120 PeripheralVisionAngleCos = FMath::Cos(FMath::Clamp(FMath::DegreesToRadians(SenseConfig.PeripheralVisionAngleDegrees), 0.f, PI));
121 AffiliationFlags = SenseConfig.DetectionByAffiliation.GetAsFlags();
122 // keep the special value of FAISystem::InvalidRange (-1.f) if it's set.
123 AutoSuccessRangeSqFromLastSeenLocation = (SenseConfig.AutoSuccessRangeFromLastSeenLocation == FAISystem::InvalidRange) ? FAISystem::InvalidRange : FMath::Square(SenseConfig.AutoSuccessRangeFromLastSeenLocation);
124}
125
127 : PeripheralVisionAngleCos(0.f), SightRadiusSq(-1.f), AutoSuccessRangeSqFromLastSeenLocation(FAISystem::InvalidRange), LoseSightRadiusSq(-1.f), AffiliationFlags(-1)
128{}
129
130//----------------------------------------------------------------------//
131// UAISense_Sight_VR
132//----------------------------------------------------------------------//
133UAISense_Sight_VR::UAISense_Sight_VR(const FObjectInitializer& ObjectInitializer)
134 : Super(ObjectInitializer)
137 , MaxTimeSlicePerTick(0.005) // 5ms
139 , MaxQueryImportance(60.f)
141{
142 if (HasAnyFlags(RF_ClassDefaultObject) == false)
143 {
144 UAISenseConfig_Sight_VR* SightConfigCDO = GetMutableDefault<UAISenseConfig_Sight_VR>();
145 SightConfigCDO->Implementation = UAISense_Sight_VR::StaticClass();
146
147 OnNewListenerDelegate.BindUObject(this, &UAISense_Sight_VR::OnNewListenerImpl);
148 OnListenerUpdateDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerUpdateImpl);
149 OnListenerRemovedDelegate.BindUObject(this, &UAISense_Sight_VR::OnListenerRemovedImpl);
150 }
151
152 NotifyType = EAISenseNotifyType::OnPerceptionChange;
153
154 bAutoRegisterAllPawnsAsSources = true;
155 bNeedsForgettingNotification = true;
156
158}
159
160FORCEINLINE_DEBUGGABLE float UAISense_Sight_VR::CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const
161{
162 const float DistanceSq = FVector::DistSquared(Listener.CachedLocation, TargetLocation);
164 : FMath::Clamp((SightLimitQueryImportance - MaxQueryImportance) / SightRadiusSq * DistanceSq + MaxQueryImportance, 0.f, MaxQueryImportance);
165}
166
168{
169 Super::PostInitProperties();
171}
172
173#if WITH_EDITOR
174void UAISenseConfig_Sight_VR::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
175{
176 static const FName NAME_AutoSuccessRangeFromLastSeenLocation = GET_MEMBER_NAME_CHECKED(UAISenseConfig_Sight_VR, AutoSuccessRangeFromLastSeenLocation);
177
178 Super::PostEditChangeProperty(PropertyChangedEvent);
179
180 if (PropertyChangedEvent.Property)
181 {
182 const FName PropName = PropertyChangedEvent.Property->GetFName();
183 if (PropName == NAME_AutoSuccessRangeFromLastSeenLocation)
184 {
185 if (AutoSuccessRangeFromLastSeenLocation < 0)
186 {
187 AutoSuccessRangeFromLastSeenLocation = FAISystem::InvalidRange;
188 }
189 }
190 }
191}
192#endif // WITH_EDITOR
193
194bool UAISense_Sight_VR::ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQueryVR* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const
195{
196 OutStimulusStrength = 1.0f;
197
198 if ((PropDigest.AutoSuccessRangeSqFromLastSeenLocation != FAISystem::InvalidRange) && (SightQuery->LastSeenLocation != FAISystem::InvalidLocation))
199 {
200 // Changed this up to support my VR Characters
201 const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(TargetActor);
202 const float DistanceToLastSeenLocationSq = FVector::DistSquared(VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation(), SightQuery->LastSeenLocation);
203 return (DistanceToLastSeenLocationSq <= PropDigest.AutoSuccessRangeSqFromLastSeenLocation);
204 }
205
206 return false;
207}
208
210{
211 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight);
212
213 const UWorld* World = GEngine->GetWorldFromContextObject(GetPerceptionSystem()->GetOuter(), EGetWorldErrorMode::LogAndReturnNull);
214
215 if (World == NULL)
216 {
217 return SuspendNextUpdate;
218 }
219
220 // sort Sight Queries
221 {
222 auto RecalcScore = [](FAISightQueryVR& SightQuery)->EForEachResult
223 {
224 SightQuery.RecalcScore();
226 };
227
228 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_UpdateSort);
229 // Sort out of range queries
231 {
232 ForEach(SightQueriesOutOfRange, RecalcScore);
236 }
237
238 // Sort in range queries
239 ForEach(SightQueriesInRange, RecalcScore);
241 }
242
243 int32 TracesCount = 0;
244 int32 NumQueriesProcessed = 0;
245 double TimeSliceEnd = FPlatformTime::Seconds() + MaxTimeSlicePerTick;
246 bool bHitTimeSliceLimit = false;
247 //#define AISENSE_SIGHT_TIMESLICING_DEBUG
248#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
249 double TimeSpent = 0.0;
250 double LastTime = FPlatformTime::Seconds();
251#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
252 static const int32 InitialInvalidItemsSize = 16;
253 enum class EOperationType : uint8
254 {
255 Remove,
256 SwapList
257 };
258 struct FQueryOperation
259 {
260 FQueryOperation(bool bInInRange, EOperationType InOpType, int32 InIndex) : bInRange(bInInRange), OpType(InOpType), Index(InIndex) {}
261 bool bInRange;
262 EOperationType OpType;
263 int32 Index;
264 };
265 TArray<FQueryOperation> QueryOperations;
266 TArray<FAISightTargetVR::FTargetId> InvalidTargets;
267 QueryOperations.Reserve(InitialInvalidItemsSize);
268 InvalidTargets.Reserve(InitialInvalidItemsSize);
269
270 AIPerception::FListenerMap& ListenersMap = *GetListeners();
271
272 int32 InRangeItr = 0;
273 int32 OutOfRangeItr = 0;
274 for (int32 QueryIndex = 0; QueryIndex < SightQueriesInRange.Num() + SightQueriesOutOfRange.Num(); ++QueryIndex)
275 {
276 // Calculate next in range query
277 int32 InRangeIndex = SightQueriesInRange.IsValidIndex(InRangeItr) ? InRangeItr : INDEX_NONE;
278 FAISightQueryVR* InRangeQuery = InRangeIndex != INDEX_NONE ? &SightQueriesInRange[InRangeIndex] : nullptr;
279
280 // Calculate next out of range query
281 int32 OutOfRangeIndex = SightQueriesOutOfRange.IsValidIndex(OutOfRangeItr) ? (NextOutOfRangeIndex + OutOfRangeItr) % SightQueriesOutOfRange.Num() : INDEX_NONE;
282 FAISightQueryVR* OutOfRangeQuery = OutOfRangeIndex != INDEX_NONE ? &SightQueriesOutOfRange[OutOfRangeIndex] : nullptr;
283 if (OutOfRangeQuery)
284 {
285 OutOfRangeQuery->RecalcScore();
286 }
287
288 // Compare to real find next query
289 const bool bIsInRangeQuery = (InRangeQuery && OutOfRangeQuery) ? FAISightQueryVR::FSortPredicate()(*InRangeQuery, *OutOfRangeQuery) : !OutOfRangeQuery;
290 FAISightQueryVR* SightQuery = bIsInRangeQuery ? InRangeQuery : OutOfRangeQuery;
291
292 // Time slice limit check - spread out checks to every N queries so we don't spend more time checking timer than doing work
293 NumQueriesProcessed++;
294#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
295 TimeSpent += (FPlatformTime::Seconds() - LastTime);
296 LastTime = FPlatformTime::Seconds();
297#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
298 if (bHitTimeSliceLimit == false && (NumQueriesProcessed % MinQueriesPerTimeSliceCheck) == 0 && FPlatformTime::Seconds() > TimeSliceEnd)
299 {
300 bHitTimeSliceLimit = true;
301 // do not break here since that would bypass queue aging
302 }
303
304 if (TracesCount < MaxTracesPerTick && bHitTimeSliceLimit == false)
305 {
306 bIsInRangeQuery ? ++InRangeItr : ++OutOfRangeItr;
307
308 FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId];
309
310 FAISightTargetVR& Target = ObservedTargets[SightQuery->TargetId];
311 AActor* TargetActor = Target.Target.Get();
312 UAIPerceptionComponent* ListenerPtr = Listener.Listener.Get();
313 ensure(ListenerPtr);
314
315 // @todo figure out what should we do if not valid
316 if (TargetActor && ListenerPtr)
317 {
318 //AActor* nTargetActor = Target.Target.Get();
319 // Changed this up to support my VR Characters
320 const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(TargetActor);
321 const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor->GetActorLocation();
322
323 const FDigestedSightProperties& PropDigest = DigestedProperties[SightQuery->ObserverId];
324 const float SightRadiusSq = SightQuery->bLastResult ? PropDigest.LoseSightRadiusSq : PropDigest.SightRadiusSq;
325
326 float StimulusStrength = 1.f;
327
328 // @Note that automagical "seeing" does not care about sight range nor vision cone
329 const bool bShouldAutomatically = ShouldAutomaticallySeeTarget(PropDigest, SightQuery, Listener, TargetActor, StimulusStrength);
330 if (bShouldAutomatically)
331 {
332 // Pretend like we've seen this target where we last saw them
333 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, SightQuery->LastSeenLocation, Listener.CachedLocation));
334 SightQuery->bLastResult = true;
335 }
336 else if (CheckIsTargetInSightPie(Listener, PropDigest, TargetLocation, SightRadiusSq))
337 {
338 SIGHT_LOG_SEGMENTVR(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Green, TEXT("%s"), *(Target.TargetId.ToString()));
339
340 FVector OutSeenLocation(0.f);
341 // do line checks
342 if (Target.SightTargetInterface != NULL)
343 {
344 int32 NumberOfLoSChecksPerformed = 0;
345 // defaulting to 1 to have "full strength" by default instead of "no strength"
346 const bool bWasVisible = SightQuery->bLastResult;
347 if (Target.SightTargetInterface->CanBeSeenFrom(Listener.CachedLocation, OutSeenLocation, NumberOfLoSChecksPerformed, StimulusStrength, ListenerPtr->GetBodyActor(), &bWasVisible, &SightQuery->UserData) == true)
348 {
349 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, StimulusStrength, OutSeenLocation, Listener.CachedLocation));
350 SightQuery->bLastResult = true;
351 SightQuery->LastSeenLocation = OutSeenLocation;
352 }
353 // communicate failure only if we've seen give actor before
354 else if (SightQuery->bLastResult == true)
355 {
356 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
357 SightQuery->bLastResult = false;
358 SightQuery->LastSeenLocation = FAISystem::InvalidLocation;
359 }
360
361 if (SightQuery->bLastResult == false)
362 {
363 SIGHT_LOG_LOCATIONVR(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT(""));
364 }
365
366 TracesCount += NumberOfLoSChecksPerformed;
367 }
368 else
369 {
370 // we need to do tests ourselves
371 FHitResult HitResult;
372 const bool bHit = World->LineTraceSingleByChannel(HitResult, Listener.CachedLocation, TargetLocation
374 , FCollisionQueryParams(SCENE_QUERY_STAT(AILineOfSight), true, ListenerPtr->GetBodyActor()));
375
376 ++TracesCount;
377
378 auto HitResultActorIsOwnedByTargetActor = [&HitResult, TargetActor]()
379 {
380 AActor* HitResultActor = HitResult.Actor.Get();
381 return (HitResultActor ? HitResultActor->IsOwnedBy(TargetActor) : false);
382 };
383
384 if (bHit == false || HitResultActorIsOwnedByTargetActor())
385 {
386 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 1.f, TargetLocation, Listener.CachedLocation));
387 SightQuery->bLastResult = true;
388 SightQuery->LastSeenLocation = TargetLocation;
389 }
390 // communicate failure only if we've seen give actor before
391 else if (SightQuery->bLastResult == true)
392 {
393 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
394 SightQuery->bLastResult = false;
395 SightQuery->LastSeenLocation = FAISystem::InvalidLocation;
396 }
397
398 if (SightQuery->bLastResult == false)
399 {
400 SIGHT_LOG_LOCATIONVR(ListenerPtr->GetOwner(), TargetLocation, 25.f, FColor::Red, TEXT(""));
401 }
402 }
403 }
404 // communicate failure only if we've seen give actor before
405 else if (SightQuery->bLastResult)
406 {
407 SIGHT_LOG_SEGMENTVR(ListenerPtr->GetOwner(), Listener.CachedLocation, TargetLocation, FColor::Red, TEXT("%s"), *(Target.TargetId.ToString()));
408 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, TargetLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
409 SightQuery->bLastResult = false;
410 }
411
412 SightQuery->Importance = CalcQueryImportance(Listener, TargetLocation, SightRadiusSq);
413 const bool bShouldBeInRange = SightQuery->Importance > 0.0f;
414 if (bIsInRangeQuery != bShouldBeInRange)
415 {
416 QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::SwapList, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex));
417 }
418
419 // restart query
420 SightQuery->OnProcessed();
421 }
422 else
423 {
424 // put this index to "to be removed" array
425 QueryOperations.Add(FQueryOperation(bIsInRangeQuery, EOperationType::Remove, bIsInRangeQuery ? InRangeIndex : OutOfRangeIndex));
426 if (TargetActor == nullptr)
427 {
428 InvalidTargets.AddUnique(SightQuery->TargetId);
429 }
430 }
431 }
432 else
433 {
434 break;
435 }
436 }
437
439
440#ifdef AISENSE_SIGHT_TIMESLICING_DEBUG
441 UE_LOG(LogAIPerceptionVR, VeryVerbose, TEXT("UAISense_Sight_VR::Update processed %d sources in %f seconds [time slice limited? %d]"), NumQueriesProcessed, TimeSpent, bHitTimeSliceLimit ? 1 : 0);
442#else
443 UE_LOG(LogAIPerceptionVR, VeryVerbose, TEXT("UAISense_Sight_VR::Update processed %d sources [time slice limited? %d]"), NumQueriesProcessed, bHitTimeSliceLimit ? 1 : 0);
444#endif // AISENSE_SIGHT_TIMESLICING_DEBUG
445
446 if (QueryOperations.Num() > 0)
447 {
448 // Sort by InRange and by descending Index
449 QueryOperations.Sort([](const FQueryOperation& LHS, const FQueryOperation& RHS)->bool
450 {
451 if (LHS.bInRange != RHS.bInRange)
452 return LHS.bInRange;
453 return LHS.Index > RHS.Index;
454 });
455 // Do all the removes first and save the out of range swaps because we will insert them at the right location to prevent sorting
456 TArray<FAISightQueryVR> SightQueriesOutOfRangeToInsert;
457 for (FQueryOperation& Operation : QueryOperations)
458 {
459 if (Operation.OpType == EOperationType::SwapList)
460 {
461 if (Operation.bInRange)
462 {
463 SightQueriesOutOfRangeToInsert.Push(SightQueriesInRange[Operation.Index]);
464 }
465 else
466 {
467 SightQueriesInRange.Add(SightQueriesOutOfRange[Operation.Index]);
468 }
469 }
470
471 if (Operation.bInRange)
472 {
473 // In range queries are always sorted at the beginning of the update
474 SightQueriesInRange.RemoveAtSwap(Operation.Index, 1, /*bAllowShrinking*/false);
475 }
476 else
477 {
478 // Preserve the list ordered
479 SightQueriesOutOfRange.RemoveAt(Operation.Index, 1, /*bAllowShrinking*/false);
480 if (Operation.Index < NextOutOfRangeIndex)
481 {
483 }
484 }
485 }
486 // Reinsert the saved out of range swaps
487 if (SightQueriesOutOfRangeToInsert.Num() > 0)
488 {
489 SightQueriesOutOfRange.Insert(SightQueriesOutOfRangeToInsert.GetData(), SightQueriesOutOfRangeToInsert.Num(), NextOutOfRangeIndex);
490 NextOutOfRangeIndex += SightQueriesOutOfRangeToInsert.Num();
491 }
492
493 if (InvalidTargets.Num() > 0)
494 {
495 // this should not be happening since UAIPerceptionSystem::OnPerceptionStimuliSourceEndPlay introduction
496 UE_VLOG(GetPerceptionSystem(), LogAIPerceptionVR, Error, TEXT("Invalid sight targets found during UAISense_Sight_VR::Update call"));
497
498 for (const auto& TargetId : InvalidTargets)
499 {
500 // remove affected queries
501 RemoveAllQueriesToTarget(TargetId);
502 // remove target itself
503 ObservedTargets.Remove(TargetId);
504 }
505
506 // remove holes
507 ObservedTargets.Compact();
508 }
509 }
510
511 //return SightQueryQueue.Num() > 0 ? 1.f/6 : FLT_MAX;
512 return 0.f;
513}
514
519
521{
522 RegisterTarget(SourceActor);
523}
524
526{
527 const FAISightTargetVR::FTargetId AsTargetId = SourceActor.GetUniqueID();
528 FAISightTargetVR AsTarget;
529
530 if (ObservedTargets.RemoveAndCopyValue(AsTargetId, AsTarget)
531 && (SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) > 0)
532 {
533 AActor* TargetActor = AsTarget.Target.Get();
534
535 if (TargetActor)
536 {
537 // notify all interested observers that this source is no longer
538 // visible
539 AIPerception::FListenerMap& ListenersMap = *GetListeners();
540 auto RemoveQuery = [this, &ListenersMap, &AsTargetId, &TargetActor](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
541 {
542 FAISightQueryVR* SightQuery = &SightQueries[QueryIndex];
543 if (SightQuery->TargetId == AsTargetId)
544 {
545 if (SightQuery->bLastResult == true)
546 {
547 FPerceptionListener& Listener = ListenersMap[SightQuery->ObserverId];
548 ensure(Listener.Listener.IsValid());
549
550 Listener.RegisterStimulus(TargetActor, FAIStimulus(*this, 0.f, SightQuery->LastSeenLocation, Listener.CachedLocation, FAIStimulus::SensingFailed));
551 }
552
553 SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
555 }
556
558 };
559
562 {
564 }
565 }
566 }
567}
568
569bool UAISense_Sight_VR::RegisterTarget(AActor& TargetActor, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc /*= nullptr*/)
570{
571 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RegisterTarget);
572
573 FAISightTargetVR* SightTarget = ObservedTargets.Find(TargetActor.GetUniqueID());
574
575 if (SightTarget != nullptr && SightTarget->GetTargetActor() != &TargetActor)
576 {
577 // this means given unique ID has already been recycled.
578 FAISightTargetVR NewSightTarget(&TargetActor);
579
580 SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget));
581 SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor);
582 }
583 else if (SightTarget == nullptr)
584 {
585 FAISightTargetVR NewSightTarget(&TargetActor);
586
587 SightTarget = &(ObservedTargets.Add(NewSightTarget.TargetId, NewSightTarget));
588 SightTarget->SightTargetInterface = Cast<IAISightTargetInterface>(&TargetActor);
589 }
590
591 // set/update data
592 SightTarget->TeamId = FGenericTeamId::GetTeamIdentifier(&TargetActor);
593
594 // generate all pairs and add them to current Sight Queries
595 bool bNewQueriesAdded = false;
596 AIPerception::FListenerMap& ListenersMap = *GetListeners();
597
598 // Changed this up to support my VR Characters
599 const AVRBaseCharacter * VRChar = Cast<const AVRBaseCharacter>(&TargetActor);
600 const FVector TargetLocation = VRChar != nullptr ? VRChar->GetVRLocation_Inline() : TargetActor.GetActorLocation();
601
602 for (AIPerception::FListenerMap::TConstIterator ItListener(ListenersMap); ItListener; ++ItListener)
603 {
604 const FPerceptionListener& Listener = ItListener->Value;
605 const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent();
606
607 if (Listener.HasSense(GetSenseID()) && Listener.GetBodyActor() != &TargetActor)
608 {
609 const FDigestedSightProperties& PropDigest = DigestedProperties[Listener.GetListenerID()];
610 if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, TargetActor, PropDigest.AffiliationFlags))
611 {
612 // create a sight query
613 const float Importance = CalcQueryImportance(ItListener->Value, TargetLocation, PropDigest.SightRadiusSq);
614 const bool bInRange = Importance > 0.0f;
615 if (!bInRange)
616 {
618 }
619 FAISightQueryVR& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef();
620 AddedQuery.ObserverId = ItListener->Key;
621 AddedQuery.TargetId = SightTarget->TargetId;
622
623 AddedQuery.Importance = Importance;
624
625 if (OnAddedFunc)
626 {
627 OnAddedFunc(AddedQuery);
628 }
629 bNewQueriesAdded = true;
630 }
631 }
632 }
633
634 // sort Sight Queries
635 if (bNewQueriesAdded)
636 {
637 RequestImmediateUpdate();
638 }
639
640 return bNewQueriesAdded;
641}
642
643void UAISense_Sight_VR::OnNewListenerImpl(const FPerceptionListener& NewListener)
644{
645 UAIPerceptionComponent* NewListenerPtr = NewListener.Listener.Get();
646 check(NewListenerPtr);
647 const UAISenseConfig_Sight_VR* SenseConfig = Cast<const UAISenseConfig_Sight_VR>(NewListenerPtr->GetSenseConfig(GetSenseID()));
648
649 check(SenseConfig);
650 const FDigestedSightProperties PropertyDigest(*SenseConfig);
651 DigestedProperties.Add(NewListener.GetListenerID(), PropertyDigest);
652
653 GenerateQueriesForListener(NewListener, PropertyDigest);
654}
655
656void UAISense_Sight_VR::GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction<void(FAISightQueryVR&)>& OnAddedFunc/*= nullptr */)
657{
658 bool bNewQueriesAdded = false;
659 const IGenericTeamAgentInterface* ListenersTeamAgent = Listener.GetTeamAgent();
660 const AActor* Avatar = Listener.GetBodyActor();
661
662 // create sight queries with all legal targets
663 for (FTargetsContainer::TConstIterator ItTarget(ObservedTargets); ItTarget; ++ItTarget)
664 {
665 const AActor* TargetActor = ItTarget->Value.GetTargetActor();
666 if (TargetActor == NULL || TargetActor == Avatar)
667 {
668 continue;
669 }
670
671 if (FAISenseAffiliationFilter::ShouldSenseTeam(ListenersTeamAgent, *TargetActor, PropertyDigest.AffiliationFlags))
672 {
673 // create a sight query
674 const float Importance = CalcQueryImportance(Listener, ItTarget->Value.GetLocationSimple(), PropertyDigest.SightRadiusSq);
675 const bool bInRange = Importance > 0.0f;
676 if (!bInRange)
677 {
679 }
680 FAISightQueryVR& AddedQuery = bInRange ? SightQueriesInRange.AddDefaulted_GetRef() : SightQueriesOutOfRange.AddDefaulted_GetRef();
681 AddedQuery.ObserverId = Listener.GetListenerID();
682 AddedQuery.TargetId = ItTarget->Key;
683
684 AddedQuery.Importance = Importance;
685
686
687 if (OnAddedFunc)
688 {
689 OnAddedFunc(AddedQuery);
690 }
691 bNewQueriesAdded = true;
692 }
693 }
694
695 // sort Sight Queries
696 if (bNewQueriesAdded)
697 {
698 RequestImmediateUpdate();
699 }
700}
701
702void UAISense_Sight_VR::OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener)
703{
704 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_ListenerUpdate);
705
706 // first, naive implementation:
707 // 1. remove all queries by this listener
708 // 2. proceed as if it was a new listener
709
710 // see if this listener is a Target as well
711 const FAISightTargetVR::FTargetId AsTargetId = UpdatedListener.GetBodyActorUniqueID();
712 FAISightTargetVR* AsTarget = ObservedTargets.Find(AsTargetId);
713 if (AsTarget != NULL)
714 {
715 if (AsTarget->Target.IsValid())
716 {
717 // if still a valid target then backup list of observers for which the listener was visible to restore in the newly created queries
718 TSet<FPerceptionListenerID> LastVisibleObservers;
719 RemoveAllQueriesToTarget(AsTargetId, [&LastVisibleObservers](const FAISightQueryVR& Query)
720 {
721 if (Query.bLastResult)
722 {
723 LastVisibleObservers.Add(Query.ObserverId);
724 }
725 });
726
727 RegisterTarget(*(AsTarget->Target.Get()), [&LastVisibleObservers](FAISightQueryVR& Query)
728 {
729 Query.bLastResult = LastVisibleObservers.Contains(Query.ObserverId);
730 });
731 }
732 else
733 {
734 RemoveAllQueriesToTarget(AsTargetId);
735 }
736 }
737
738 const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID();
739
740 if (UpdatedListener.HasSense(GetSenseID()))
741 {
742 // if still a valid sense then backup list of targets that were visible by the listener to restore in the newly created queries
743 TSet<FAISightTargetVR::FTargetId> LastVisibleTargets;
744 RemoveAllQueriesByListener(UpdatedListener, [&LastVisibleTargets](const FAISightQueryVR& Query)
745 {
746 if (Query.bLastResult)
747 {
748 LastVisibleTargets.Add(Query.TargetId);
749 }
750 });
751
752 const UAISenseConfig_Sight_VR* SenseConfig = Cast<const UAISenseConfig_Sight_VR>(UpdatedListener.Listener->GetSenseConfig(GetSenseID()));
753 check(SenseConfig);
754 FDigestedSightProperties& PropertiesDigest = DigestedProperties.FindOrAdd(ListenerID);
755 PropertiesDigest = FDigestedSightProperties(*SenseConfig);
756
757 GenerateQueriesForListener(UpdatedListener, PropertiesDigest, [&LastVisibleTargets](FAISightQueryVR & Query)
758 {
759 Query.bLastResult = LastVisibleTargets.Contains(Query.TargetId);
760 });
761 }
762 else
763 {
764 // remove all queries
765 RemoveAllQueriesByListener(UpdatedListener);
766 DigestedProperties.Remove(ListenerID);
767 }
768}
769
770void UAISense_Sight_VR::OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener)
771{
772
773 bool bSkipListenerUpdate = false;
774 const FPerceptionListenerID ListenerID = UpdatedListener.GetListenerID();
775
776
777 FDigestedSightProperties* PropertiesDigest = DigestedProperties.Find(ListenerID);
778 if (PropertiesDigest)
779 {
780 // 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.
781 const UAISenseConfig_Sight_VR* SenseConfig = CastChecked<const UAISenseConfig_Sight_VR>(UpdatedListener.Listener->GetSenseConfig(GetSenseID()));
782 FDigestedSightProperties NewPropertiesDigest(*SenseConfig);
783 bSkipListenerUpdate = NewPropertiesDigest.AffiliationFlags == PropertiesDigest->AffiliationFlags;
784 *PropertiesDigest = NewPropertiesDigest;
785 }
786
787 if (!bSkipListenerUpdate)
788 {
789 Super::OnListenerConfigUpdated(UpdatedListener);
790 }
791}
792
793void UAISense_Sight_VR::OnListenerRemovedImpl(const FPerceptionListener& RemovedListener)
794{
795
796 RemoveAllQueriesByListener(RemovedListener);
797
798 DigestedProperties.FindAndRemoveChecked(RemovedListener.GetListenerID());
799
800 // note: there use to be code to remove all queries _to_ listener here as well
801 // but that was wrong - the fact that a listener gets unregistered doesn't have to
802 // mean it's being removed from the game altogether.
803}
804
805
806void UAISense_Sight_VR::RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc/*= nullptr */)
807{
808 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveByListener);
809
810 if ((SightQueriesInRange.Num() + SightQueriesOutOfRange.Num()) == 0)
811 {
812 return;
813 }
814
815 const uint32 ListenerId = Listener.GetListenerID();
816
817
818
819 auto RemoveQuery = [&ListenerId, &OnRemoveFunc](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
820 {
821 const FAISightQueryVR& SightQuery = SightQueries[QueryIndex];
822
823 if (SightQuery.ObserverId == ListenerId)
824 {
825 if (OnRemoveFunc)
826 {
827 OnRemoveFunc(SightQuery);
828 }
829 SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
830
832 }
833
834
835
837 };
840 {
841
843 }
844}
845
846void UAISense_Sight_VR::RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId& TargetId, const TFunction<void(const FAISightQueryVR&)>& OnRemoveFunc/*= nullptr */)
847{
848 SCOPE_CYCLE_COUNTER(STAT_AI_Sense_Sight_RemoveToTarget);
849
850 auto RemoveQuery = [&TargetId, &OnRemoveFunc](TArray<FAISightQueryVR>& SightQueries, const int32 QueryIndex)->EReverseForEachResult
851 {
852 const FAISightQueryVR& SightQuery = SightQueries[QueryIndex];
853
854 if (SightQuery.TargetId == TargetId)
855 {
856 if (OnRemoveFunc)
857 {
858 OnRemoveFunc(SightQuery);
859 }
860 SightQueries.RemoveAtSwap(QueryIndex, 1, /*bAllowShrinking=*/false);
861
863 }
864
866 };
869 {
870
872 }
873}
874
875
876void UAISense_Sight_VR::OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget)
877{
878 const uint32 ListenerId = Listener.GetListenerID();
879 const uint32 TargetId = ActorToForget.GetUniqueID();
880
881 auto ForgetPreviousResult = [&ListenerId, &TargetId](FAISightQueryVR& SightQuery)->EForEachResult
882 {
883 if (SightQuery.ObserverId == ListenerId && SightQuery.TargetId == TargetId)
884 {
885 // assuming one query per observer-target pair
886 SightQuery.ForgetPreviousResult();
888 }
890 };
891
892 if (ForEach(SightQueriesInRange, ForgetPreviousResult) == EForEachResult::Continue)
893 {
894 ForEach(SightQueriesOutOfRange, ForgetPreviousResult);
895 }
896}
897
898void UAISense_Sight_VR::OnListenerForgetsAll(const FPerceptionListener& Listener)
899{
900 const uint32 ListenerId = Listener.GetListenerID();
901
902 auto ForgetPreviousResult = [&ListenerId](FAISightQueryVR& SightQuery)->EForEachResult
903 {
904 if (SightQuery.ObserverId == ListenerId)
905 {
906 SightQuery.ForgetPreviousResult();
907 }
908
910 };
911
912 ForEach(SightQueriesInRange, ForgetPreviousResult);
913 ForEach(SightQueriesOutOfRange, ForgetPreviousResult);
914}
915
916
917//----------------------------------------------------------------------//
918//
919//----------------------------------------------------------------------//
920UAISenseConfig_Sight_VR::UAISenseConfig_Sight_VR(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
921{
922 DebugColor = FColor::Green;
923 AutoSuccessRangeFromLastSeenLocation = -1.0;
924 SightRadius = 3000.f;
925 LoseSightRadius = 3500.f;
926 PeripheralVisionAngleDegrees = 90;
927 DetectionByAffiliation.bDetectEnemies = true;
928 Implementation = UAISense_Sight_VR::StaticClass();
929}
930
932{
933 return *Implementation;
934}
935
936#if WITH_GAMEPLAY_DEBUGGER
937static FString DescribeColorHelper(const FColor& Color)
938{
939 int32 MaxColors = GColorList.GetColorsNum();
940 for (int32 Idx = 0; Idx < MaxColors; Idx++)
941 {
942 if (Color == GColorList.GetFColorByIndex(Idx))
943 {
944 return GColorList.GetColorNameByIndex(Idx);
945 }
946 }
947
948 return FString(TEXT("color"));
949}
950
951void UAISenseConfig_Sight_VR::DescribeSelfToGameplayDebugger(const UAIPerceptionComponent* PerceptionComponent, FGameplayDebuggerCategory* DebuggerCategory) const
952{
953 if (PerceptionComponent == nullptr || DebuggerCategory == nullptr)
954 {
955 return;
956 }
957
958 FColor SightRangeColor = FColor::Green;
959 FColor LoseSightRangeColor = FColorList::NeonPink;
960
961 // don't call Super implementation on purpose, replace color description line
962 DebuggerCategory->AddTextLine(
963 FString::Printf(TEXT("%s: {%s}%s {white}rangeIN:{%s}%s {white} rangeOUT:{%s}%s"), *GetSenseName(),
964 *GetDebugColor().ToString(), *DescribeColorHelper(GetDebugColor()),
965 *SightRangeColor.ToString(), *DescribeColorHelper(SightRangeColor),
966 *LoseSightRangeColor.ToString(), *DescribeColorHelper(LoseSightRangeColor))
967 );
968
969 const AActor* BodyActor = PerceptionComponent->GetBodyActor();
970 if (BodyActor != nullptr)
971 {
972 FVector BodyLocation, BodyFacing;
973 PerceptionComponent->GetLocationAndDirection(BodyLocation, BodyFacing);
974
975 DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, LoseSightRadius, 25.0f, LoseSightRangeColor));
976 DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeCylinder(BodyLocation, SightRadius, 25.0f, SightRangeColor));
977
978 const float SightPieLength = FMath::Max(LoseSightRadius, SightRadius);
979 DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing * SightPieLength), SightRangeColor));
980 DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor));
981 DebuggerCategory->AddShape(FGameplayDebuggerShape::MakeSegment(BodyLocation, BodyLocation + (BodyFacing.RotateAngleAxis(-PeripheralVisionAngleDegrees, FVector::UpVector) * SightPieLength), SightRangeColor));
982 }
983}
984#endif // WITH_GAMEPLAY_DEBUGGER
static const int32 DefaultMaxTracesPerTick
EReverseForEachResult ReverseForEach(T &Array, const PREDICATE_CLASS &Predicate)
EForEachResult ForEach(T &Array, const PREDICATE_CLASS &Predicate)
DECLARE_CYCLE_STAT(TEXT("Perception Sense: Sight"), STAT_AI_Sense_Sight, STATGROUP_AI)
DEFINE_LOG_CATEGORY(LogAIPerceptionVR)
#define SIGHT_LOG_LOCATIONVR(...)
FORCEINLINE_DEBUGGABLE bool CheckIsTargetInSightPie(const FPerceptionListener &Listener, const UAISense_Sight_VR::FDigestedSightProperties &DigestedProps, const FVector &TargetLocation, const float SightRadiusSq)
static const int32 DefaultMinQueriesPerTimeSliceCheck
#define SIGHT_LOG_SEGMENTVR(...)
FVector GetVRLocation_Inline() const
void RemoveAllQueriesToTarget(const FAISightTargetVR::FTargetId &TargetId, const TFunction< void(const FAISightQueryVR &)> &OnRemoveFunc=nullptr)
virtual bool ShouldAutomaticallySeeTarget(const FDigestedSightProperties &PropDigest, FAISightQueryVR *SightQuery, FPerceptionListener &Listener, AActor *TargetActor, float &OutStimulusStrength) const
TArray< FAISightQueryVR > SightQueriesInRange
void RemoveAllQueriesByListener(const FPerceptionListener &Listener, const TFunction< void(const FAISightQueryVR &)> &OnRemoveFunc=nullptr)
float HighImportanceQueryDistanceThreshold
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
int32 MaxTracesPerTick
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
TMap< FPerceptionListenerID, FDigestedSightProperties > DigestedProperties
float CalcQueryImportance(const FPerceptionListener &Listener, const FVector &TargetLocation, const float SightRadiusSq) const
virtual void PostInitProperties() override
virtual void RegisterSource(AActor &SourceActors) override
virtual void OnListenerForgetsActor(const FPerceptionListener &Listener, AActor &ActorToForget) override
float MaxQueryImportance
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
TArray< FAISightQueryVR > SightQueriesOutOfRange
bool RegisterTarget(AActor &TargetActor, const TFunction< void(FAISightQueryVR &)> &OnAddedFunc=nullptr)
float SightLimitQueryImportance
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
double MaxTimeSlicePerTick
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
void OnListenerUpdateImpl(const FPerceptionListener &UpdatedListener)
void RegisterEvent(const FAISightEventVR &Event)
ECollisionChannel DefaultSightCollisionChannel
FTargetsContainer ObservedTargets
virtual void UnregisterSource(AActor &SourceActor) override
virtual void OnListenerForgetsAll(const FPerceptionListener &Listener) override
void GenerateQueriesForListener(const FPerceptionListener &Listener, const FDigestedSightProperties &PropertyDigest, const TFunction< void(FAISightQueryVR &)> &OnAddedFunc=nullptr)
void OnListenerRemovedImpl(const FPerceptionListener &RemovedListener)
virtual float Update() override
virtual void OnListenerConfigUpdated(const FPerceptionListener &UpdatedListener) override
int32 MinQueriesPerTimeSliceCheck
UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config)
void OnNewListenerImpl(const FPerceptionListener &NewListener)
UCLASS(meta = (DisplayName = "AI Sight VR config"))
float PeripheralVisionAngleDegrees
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0....
FAISenseAffiliationFilter DetectionByAffiliation
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config)
float LoseSightRadius
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0....
TSubclassOf< UAISense_Sight_VR > Implementation
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", NoClear, config)
virtual TSubclassOf< UAISense > GetSenseImplementation() const override
float AutoSuccessRangeFromLastSeenLocation
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config)
float SightRadius
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Sense", config, meta = (UIMin = 0....
FAISightTargetVR::FTargetId TargetId
FPerceptionListenerID ObserverId
static const FTargetId InvalidTargetId
IAISightTargetInterface * SightTargetInterface
TWeakObjectPtr< AActor > Target
FORCEINLINE const AActor * GetTargetActor() const
FAISightTargetVR(AActor *InTarget=NULL, FGenericTeamId InTeamId=FGenericTeamId::NoTeam)