// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" #include "GenericTeamAgentInterface.h" #include "WorldCollision.h" #include "Misc/MTTransactionallySafeAccessDetector.h" #include "Perception/AISense.h" #include "AISense_Sight.generated.h" class IAISightTargetInterface; class UAISense_Sight; class UAISenseConfig_Sight; namespace ESightPerceptionEventName { enum Type { Undefined, GainedSight, LostSight }; } USTRUCT() struct FAISightEvent { GENERATED_USTRUCT_BODY() typedef UAISense_Sight FSenseClass; float Age; ESightPerceptionEventName::Type EventType; UPROPERTY() TObjectPtr SeenActor; UPROPERTY() TObjectPtr Observer; FAISightEvent() : SeenActor(nullptr), Observer(nullptr) {} FAISightEvent(AActor* InSeenActor, AActor* InObserver, ESightPerceptionEventName::Type InEventType) : Age(0.f), EventType(InEventType), SeenActor(InSeenActor), Observer(InObserver) { } }; struct FAISightTarget { typedef uint32 FTargetId; static AIMODULE_API const FTargetId InvalidTargetId; TWeakObjectPtr Target; TWeakInterfacePtr WeakSightTargetInterface; FGenericTeamId TeamId; FTargetId TargetId; UE_DEPRECATED_FORGAME(5.5, "SightTargetInterface is deprecated. Use WeakSightTargetInterface instead.") IAISightTargetInterface* SightTargetInterface = nullptr; AIMODULE_API FAISightTarget(AActor* InTarget = NULL, FGenericTeamId InTeamId = FGenericTeamId::NoTeam); inline FVector GetLocationSimple() const { const AActor* TargetPtr = Target.Get(); return TargetPtr ? TargetPtr->GetActorLocation() : FVector::ZeroVector; } inline const AActor* GetTargetActor() const { return Target.Get(); } }; struct FAISightQuery { FPerceptionListenerID ObserverId; FAISightTarget::FTargetId TargetId; float Score; float Importance; FVector LastSeenLocation; /** User data that can be used inside the IAISightTargetInterface::CanBeSeenFrom method to store a persistence state */ mutable int32 UserData; union { /** * We can share the memory for these values because they aren't used at the same time : * - The FrameInfo is used when the query is queued for an update at a later frame. It stores the last time the * query was processed so that we can prioritize it accordingly against the other queries * - The TraceInfo is used when the query has requested a asynchronous trace and is waiting for the result. * The engine guarantees that we'll get the info at the next frame, but since we can have multiple queries that * are pending at the same time, we need to store some information to identify them when receiving the result callback */ struct { uint64 bLastResult:1; uint64 LastProcessedFrameNumber:63; } FrameInfo; /** * The 'FrameNumber' value can increase indefinitely while the 'Index' represents the number of queries that were * already requested during this frame. So it shouldn't reach high values in the allocated 32 bits. * Thanks to that we can reliable only use 31 bits for this value and thus have space to keep the bLastResult value */ struct { uint32 bLastResult:1; uint32 Index:31; uint32 FrameNumber; } TraceInfo; }; FAISightQuery(FPerceptionListenerID ListenerId = FPerceptionListenerID::InvalidID(), FAISightTarget::FTargetId Target = FAISightTarget::InvalidTargetId) : ObserverId(ListenerId), TargetId(Target), Score(0), Importance(0), LastSeenLocation(FAISystem::InvalidLocation), UserData(0) { FrameInfo.bLastResult = false; FrameInfo.LastProcessedFrameNumber = GFrameCounter; } /** * Note: This should only be called on queries that are queued up for later processing (in SightQueriesOutOfRange or SightQueriesOutOfRange) */ float GetAge() const { return (float)(GFrameCounter - FrameInfo.LastProcessedFrameNumber); } /** * Note: This should only be called on queries that are queued up for later processing (in SightQueriesOutOfRange or SightQueriesOutOfRange) */ void RecalcScore() { Score = GetAge() + Importance; } void OnProcessed() { FrameInfo.LastProcessedFrameNumber = GFrameCounter; } void ForgetPreviousResult() { LastSeenLocation = FAISystem::InvalidLocation; SetLastResult(false); } bool GetLastResult() const { return FrameInfo.bLastResult; } void SetLastResult(const bool bValue) { FrameInfo.bLastResult = bValue; } /** * Note: This only be called for pending queries because it will erase the LastProcessedFrameNumber value */ void SetTraceInfo(const FTraceHandle& TraceHandle) { check((TraceHandle._Data.Index & (static_cast(1) << 31)) == 0); TraceInfo.Index = TraceHandle._Data.Index; TraceInfo.FrameNumber = TraceHandle._Data.FrameNumber; } class FSortPredicate { public: FSortPredicate() {} bool operator()(const FAISightQuery& A, const FAISightQuery& B) const { return A.Score > B.Score; } }; }; struct FAISightQueryID { FPerceptionListenerID ObserverId; FAISightTarget::FTargetId TargetId; FAISightQueryID(FPerceptionListenerID ListenerId = FPerceptionListenerID::InvalidID(), FAISightTarget::FTargetId Target = FAISightTarget::InvalidTargetId) : ObserverId(ListenerId), TargetId(Target) { } FAISightQueryID(const FAISightQuery& Query) : ObserverId(Query.ObserverId), TargetId(Query.TargetId) { } }; DECLARE_DELEGATE_FiveParams(FOnPendingVisibilityQueryProcessedDelegate, const FAISightQueryID&, const bool, const float, const FVector&, const TOptional&); UCLASS(ClassGroup=AI, config=Game, MinimalAPI) class UAISense_Sight : public UAISense { GENERATED_UCLASS_BODY() public: struct FDigestedSightProperties { float PeripheralVisionAngleCos; float SightRadiusSq; float AutoSuccessRangeSqFromLastSeenLocation; float LoseSightRadiusSq; float PointOfViewBackwardOffset; float NearClippingRadiusSq; uint8 AffiliationFlags; FDigestedSightProperties(); FDigestedSightProperties(const UAISenseConfig_Sight& SenseConfig); }; enum class EVisibilityResult { Visible, NotVisible, Pending }; typedef TMap FTargetsContainer; FTargetsContainer ObservedTargets; TMap DigestedProperties; /** The SightQueries are a n^2 problem and to reduce the sort time, they are now split between in range and out of range */ /** Since the out of range queries only age as the distance component of the score is always 0, there is few need to sort them */ /** In the majority of the cases most of the queries are out of range, so the sort time is greatly reduced as we only sort the in range queries */ int32 NextOutOfRangeIndex = 0; bool bSightQueriesOutOfRangeDirty = true; TArray SightQueriesOutOfRange; TArray SightQueriesInRange; TArray SightQueriesPending; protected: UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) int32 MaxTracesPerTick; /** Maximum number of asynchronous traces that can be requested in a single update call*/ UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) int32 MaxAsyncTracesPerTick; UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) int32 MinQueriesPerTimeSliceCheck; UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) double MaxTimeSlicePerTick; UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) float HighImportanceQueryDistanceThreshold; float HighImportanceDistanceSquare; UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) float MaxQueryImportance; UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) float SightLimitQueryImportance; /** Defines the amount of async trace queries to prevent based on the number of pending queries at the start of an update. * 1 means that the async trace budget is slashed by the pending queries count * 0 means that the async trace budget is not impacted by the pending queries */ UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) float PendingQueriesBudgetReductionRatio; /** Defines if we are allowed to use asynchronous trace queries when there is no IAISightTargetInterface for a Target */ UPROPERTY(EditDefaultsOnly, Category = "AI Perception", config) bool bUseAsynchronousTraceForDefaultSightQueries; ECollisionChannel DefaultSightCollisionChannel; FOnPendingVisibilityQueryProcessedDelegate OnPendingCanBeSeenQueryProcessedDelegate; FTraceDelegate OnPendingTraceQueryProcessedDelegate; UE_MT_DECLARE_TS_RW_ACCESS_DETECTOR(QueriesListAccessDetector); public: AIMODULE_API virtual void PostInitProperties() override; AIMODULE_API void RegisterEvent(const FAISightEvent& Event); AIMODULE_API virtual void RegisterSource(AActor& SourceActors) override; AIMODULE_API virtual void UnregisterSource(AActor& SourceActor) override; AIMODULE_API virtual void OnListenerForgetsActor(const FPerceptionListener& Listener, AActor& ActorToForget) override; AIMODULE_API virtual void OnListenerForgetsAll(const FPerceptionListener& Listener) override; #if WITH_GAMEPLAY_DEBUGGER_MENU AIMODULE_API virtual void DescribeSelfToGameplayDebugger(const UAIPerceptionSystem& PerceptionSystem, FGameplayDebuggerCategory& DebuggerCategory) const override; #endif // WITH_GAMEPLAY_DEBUGGER_MENU protected: AIMODULE_API virtual float Update() override; AIMODULE_API EVisibilityResult ComputeVisibility(UWorld* World, FAISightQuery& SightQuery, FPerceptionListener& Listener, const AActor* ListenerActor, FAISightTarget& Target, AActor* TargetActor, const FDigestedSightProperties& PropDigest, float& OutStimulusStrength, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested) const; AIMODULE_API virtual bool ShouldAutomaticallySeeTarget(const FDigestedSightProperties& PropDigest, FAISightQuery* SightQuery, FPerceptionListener& Listener, AActor* TargetActor, float& OutStimulusStrength) const; UE_DEPRECATED(5.3, "Please use the UpdateQueryVisibilityStatus version which takes an Actor& instead.") AIMODULE_API void UpdateQueryVisibilityStatus(FAISightQuery& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor* TargetActor, const FVector& TargetLocation) const; AIMODULE_API void UpdateQueryVisibilityStatus(FAISightQuery& SightQuery, FPerceptionListener& Listener, const bool bIsVisible, const FVector& SeenLocation, const float StimulusStrength, AActor& TargetActor, const FVector& TargetLocation) const; AIMODULE_API void OnPendingCanBeSeenQueryProcessed(const FAISightQueryID& QueryID, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData); AIMODULE_API void OnPendingTraceQueryProcessed(const FTraceHandle& TraceHandle, FTraceDatum& TraceDatum); AIMODULE_API void OnPendingQueryProcessed(const int32 SightQueryIndex, const bool bIsVisible, const float StimulusStrength, const FVector& SeenLocation, const TOptional& UserData, const TOptional InTargetActor = NullOpt); AIMODULE_API void OnNewListenerImpl(const FPerceptionListener& NewListener); AIMODULE_API void OnListenerUpdateImpl(const FPerceptionListener& UpdatedListener); AIMODULE_API void OnListenerRemovedImpl(const FPerceptionListener& RemovedListener); AIMODULE_API virtual void OnListenerConfigUpdated(const FPerceptionListener& UpdatedListener) override; AIMODULE_API void GenerateQueriesForListener(const FPerceptionListener& Listener, const FDigestedSightProperties& PropertyDigest, const TFunction& OnAddedFunc = nullptr); AIMODULE_API void RemoveAllQueriesByListener(const FPerceptionListener& Listener, const TFunction& OnRemoveFunc = nullptr); AIMODULE_API void RemoveAllQueriesToTarget(const FAISightTarget::FTargetId& TargetId, const TFunction& OnRemoveFunc = nullptr); /** RemoveAllQueriesToTarget version that need to already have a write access on QueriesListAccessDetector*/ void RemoveAllQueriesToTarget_Internal(const FAISightTarget::FTargetId& TargetId, const TFunction& OnRemoveFunc = nullptr); /** returns information whether new LoS queries have been added */ AIMODULE_API bool RegisterTarget(AActor& TargetActor, const TFunction& OnAddedFunc = nullptr); AIMODULE_API float CalcQueryImportance(const FPerceptionListener& Listener, const FVector& TargetLocation, const float SightRadiusSq) const; AIMODULE_API bool RegisterNewQuery(const FPerceptionListener& Listener, const IGenericTeamAgentInterface* ListenersTeamAgent, const AActor& TargetActor, const FAISightTarget::FTargetId& TargetId, const FVector& TargetLocation, const FDigestedSightProperties& PropDigest, const TFunction& OnAddedFunc); protected: enum FQueriesOperationPostProcess { DontSort, Sort }; };