// Copyright Epic Games, Inc. All Rights Reserved. #include "EnvironmentQuery/EQSRenderingComponent.h" #include "EnvironmentQuery/Items/EnvQueryItemType_VectorBase.h" #include "Engine/Canvas.h" #include "EnvironmentQuery/EQSQueryResultSourceInterface.h" #include "SceneInterface.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(EQSRenderingComponent) static constexpr int32 EQSMaxItemsDrawn = 10000; namespace FEQSRenderingHelper { FVector ExtractLocation(TSubclassOf ItemType, const TArray& RawData, const TArray& Items, int32 Index) { if (Items.IsValidIndex(Index) && ItemType->IsChildOf(UEnvQueryItemType_VectorBase::StaticClass())) { const UEnvQueryItemType_VectorBase* DefTypeOb = ItemType->GetDefaultObject(); return DefTypeOb->GetItemLocation(RawData.GetData() + Items[Index].DataOffset); } return FVector::ZeroVector; } } //----------------------------------------------------------------------// // FEQSSceneProxy //----------------------------------------------------------------------// const FVector3f FEQSSceneProxy::ItemDrawRadius(30.f,30.f,30.f); SIZE_T FEQSSceneProxy::GetTypeHash() const { static size_t UniquePointer; return reinterpret_cast(&UniquePointer); } FEQSSceneProxy::FEQSSceneProxy(const UPrimitiveComponent& InComponent, const FString& InViewFlagName, const TArray& InSpheres, const TArray& InTexts) : FDebugRenderSceneProxy(&InComponent) , ActorOwner(nullptr) , QueryDataSource(nullptr) { DrawType = SolidAndWireMeshes; TextWithoutShadowDistance = 1500; ViewFlagIndex = uint32(FEngineShowFlags::FindIndexByName(*InViewFlagName)); ViewFlagName = InViewFlagName; bWantsSelectionOutline = false; Spheres = InSpheres; Texts = InTexts; const UEQSRenderingComponent* MyRenderComp = Cast(&InComponent); bDrawOnlyWhenSelected = MyRenderComp && MyRenderComp->bDrawOnlyWhenSelected; ActorOwner = InComponent.GetOwner(); QueryDataSource = Cast(ActorOwner); if (QueryDataSource == nullptr) { QueryDataSource = Cast(&InComponent); } #if USE_EQS_DEBUGGER if (Spheres.Num() == 0 && Texts.Num() == 0 && QueryDataSource != nullptr) { TArray DebugItems; FEQSSceneProxy::CollectEQSData(&InComponent, QueryDataSource, Spheres, Texts, DebugItems); } #endif } #if USE_EQS_DEBUGGER void FEQSSceneProxy::CollectEQSData(const UPrimitiveComponent* InComponent, const IEQSQueryResultSourceInterface* InQueryDataSource, TArray& Spheres, TArray& Texts, TArray& DebugItems) { AActor* ActorOwner = InComponent ? InComponent->GetOwner() : nullptr; IEQSQueryResultSourceInterface* QueryDataSource = const_cast(InQueryDataSource); if (QueryDataSource == nullptr) { QueryDataSource = Cast(ActorOwner); if (QueryDataSource == nullptr) { QueryDataSource = Cast(const_cast(InComponent)); if (QueryDataSource == nullptr) { return; } } } const FEnvQueryResult* ResultItems = QueryDataSource->GetQueryResult(); const FEnvQueryInstance* QueryInstance = QueryDataSource->GetQueryInstance(); FEQSSceneProxy::CollectEQSData(ResultItems, QueryInstance, QueryDataSource->GetHighlightRangePct(), QueryDataSource->GetShouldDrawFailedItems(), Spheres, Texts, DebugItems); if (ActorOwner && Spheres.Num() > EQSMaxItemsDrawn) { UE_VLOG(ActorOwner, LogEQS, Warning, TEXT("EQS drawing: too much items to draw! Drawing first %d from set of %d") , EQSMaxItemsDrawn , Spheres.Num() ); } } void FEQSSceneProxy::CollectEQSData(const FEnvQueryResult* ResultItems, const FEnvQueryInstance* QueryInstance, float HighlightRangePct, bool ShouldDrawFailedItems, TArray& Spheres, TArray& Texts, TArray& DebugItems) { if (ResultItems == nullptr) { if (QueryInstance == nullptr) { return; } else { ResultItems = QueryInstance; } } // using "mid-results" requires manual normalization const bool bUseMidResults = QueryInstance && (QueryInstance->Items.Num() < QueryInstance->DebugData.DebugItems.Num()); // no point in checking if QueryInstance != null since bUseMidResults == true guarantees that, PVS-Studio doesn't // understand that invariant though, and we need to disable V595: const TArray& Items = bUseMidResults ? QueryInstance->DebugData.DebugItems : ResultItems->Items; //-V595 const TArray& RawData = bUseMidResults ? QueryInstance->DebugData.RawData : ResultItems->RawData; const int32 ItemCountLimit = FMath::Clamp(Items.Num(), 0, EQSMaxItemsDrawn); const bool bNoTestsPerformed = QueryInstance != nullptr && QueryInstance->CurrentTest <= 0; const bool bSingleItemResult = QueryInstance != nullptr && QueryInstance->DebugData.bSingleItemResult; float MinScore = 0.f; float MaxScore = -BIG_NUMBER; if (bUseMidResults || HighlightRangePct < 1.0f) { const FEnvQueryItem* ItemInfo = Items.GetData(); for (int32 ItemIndex = 0; ItemIndex < Items.Num(); ItemIndex++, ItemInfo++) { if (ItemInfo->IsValid()) { MinScore = FMath::Min(MinScore, ItemInfo->Score); MaxScore = FMath::Max(MaxScore, ItemInfo->Score); } } } const float ScoreNormalizer = bUseMidResults && (MaxScore != MinScore) ? (1.f / (MaxScore - MinScore)) : 1.f; const float HighlightThreshold = (HighlightRangePct < 1.0f) ? (MaxScore * HighlightRangePct) : FLT_MAX; if (bSingleItemResult == false) { for (int32 ItemIndex = 0; ItemIndex < ItemCountLimit; ++ItemIndex) { if (Items[ItemIndex].IsValid()) { const float NormalizedScore = bNoTestsPerformed ? 1 : (Items[ItemIndex].Score - MinScore) * ScoreNormalizer; const bool bLowRadius = (HighlightThreshold < FLT_MAX) && (bNoTestsPerformed || (Items[ItemIndex].Score < HighlightThreshold)); const float Radius = ItemDrawRadius.X * (bLowRadius ? 0.2f : 1.0f); const FVector Loc = FEQSRenderingHelper::ExtractLocation(ResultItems->ItemType, RawData, Items, ItemIndex); Spheres.Add(FSphere(Radius, Loc, bNoTestsPerformed == false ? FLinearColor(FColor::MakeRedToGreenColorFromScalar(NormalizedScore)) : FLinearColor(0.2, 1.0, 1.0, 1))); DebugItems.Add(EQSDebug::FDebugHelper(Loc, Radius)); const FString Label = bNoTestsPerformed ? TEXT("") : FString::Printf(TEXT("%.2f"), Items[ItemIndex].Score); Texts.Add(FText3d(Label, Loc, FLinearColor::White)); } } } else if (ItemCountLimit > 0) { if (Items[0].IsValid()) { const float Score = Items[0].Score; constexpr bool bLowRadius = false; const float Radius = ItemDrawRadius.X * (bLowRadius ? 0.2f : 1.0f); const FVector Loc = FEQSRenderingHelper::ExtractLocation(ResultItems->ItemType, RawData, Items, 0); Spheres.Add(FSphere(Radius, Loc, FLinearColor(0.0, 1.0, 0.12, 1))); DebugItems.Add(EQSDebug::FDebugHelper(Loc, Radius)); const FString Label = FString::Printf(TEXT("Winner %.2f"), Score); Texts.Add(FText3d(Label, Loc, FLinearColor::White)); } for (int32 ItemIndex = 1; ItemIndex < ItemCountLimit; ++ItemIndex) { if (Items[ItemIndex].IsValid()) { const float Score = bNoTestsPerformed ? 1 : Items[ItemIndex].Score; const bool bLowRadius = (HighlightThreshold < FLT_MAX) && (bNoTestsPerformed || (Items[ItemIndex].Score < HighlightThreshold)); const float Radius = ItemDrawRadius.X * (bLowRadius ? 0.2f : 1.0f); const FVector Loc = FEQSRenderingHelper::ExtractLocation(ResultItems->ItemType, RawData, Items, ItemIndex); Spheres.Add(FSphere(Radius, Loc, FLinearColor(0.0, 0.2, 0.025, 1))); DebugItems.Add(EQSDebug::FDebugHelper(Loc, Radius)); const FString Label = bNoTestsPerformed ? TEXT("") : FString::Printf(TEXT("%.2f"), Score); Texts.Add(FText3d(Label, Loc, FLinearColor::White)); } } } if (ShouldDrawFailedItems && QueryInstance) { const FEnvQueryDebugData& InstanceDebugData = QueryInstance->DebugData; const TArray& DebugQueryItems = InstanceDebugData.DebugItems; const TArray& Details = InstanceDebugData.DebugItemDetails; const int32 DebugItemCountLimit = DebugQueryItems.Num() == Details.Num() ? FMath::Clamp(DebugQueryItems.Num(), 0, EQSMaxItemsDrawn) : 0; for (int32 ItemIndex = 0; ItemIndex < DebugItemCountLimit; ++ItemIndex) { if (DebugQueryItems[ItemIndex].IsValid()) { continue; } const bool bLowRadius = (HighlightThreshold < FLT_MAX) && (bNoTestsPerformed || (Items[ItemIndex].Score < HighlightThreshold)); const float Radius = ItemDrawRadius.X * (bLowRadius ? 0.2f : 1.0f); const FVector Loc = FEQSRenderingHelper::ExtractLocation(QueryInstance->ItemType, InstanceDebugData.RawData, DebugQueryItems, ItemIndex); Spheres.Add(FSphere(Radius, Loc, FLinearColor(0.0, 0.0, 0.6, 0.6))); auto& DebugHelper = DebugItems[DebugItems.Add(EQSDebug::FDebugHelper(Loc, Radius))]; DebugHelper.AdditionalInformation = Details[ItemIndex].FailedDescription; if (Details[ItemIndex].FailedTestIndex != INDEX_NONE) { DebugHelper.FailedTestIndex = Details[ItemIndex].FailedTestIndex; DebugHelper.FailedScore = Details[ItemIndex].TestResults[DebugHelper.FailedTestIndex]; const FString Label = InstanceDebugData.PerformedTestNames[DebugHelper.FailedTestIndex] + FString::Printf(TEXT("(%d)"), DebugHelper.FailedTestIndex); Texts.Add(FText3d(Label, Loc, FLinearColor::White)); } } } } void FEQSRenderingDebugDrawDelegateHelper::DrawDebugLabels(UCanvas* Canvas, APlayerController* PC) { if ( !ActorOwner || (ActorOwner->IsSelected() == false && bDrawOnlyWhenSelected == true) || (QueryDataSource && QueryDataSource->GetShouldDebugDrawLabels() == false)) { return; } FDebugDrawDelegateHelper::DrawDebugLabels(Canvas, PC); } #endif //USE_EQS_DEBUGGER bool FEQSSceneProxy::SafeIsActorSelected() const { if(ActorOwner) { return ActorOwner->IsSelected(); } return false; } FPrimitiveViewRelevance FEQSSceneProxy::GetViewRelevance(const FSceneView* View) const { FPrimitiveViewRelevance Result; Result.bDrawRelevance = View->Family->EngineShowFlags.GetSingleFlag(ViewFlagIndex) && IsShown(View) && (!bDrawOnlyWhenSelected || SafeIsActorSelected()); Result.bDynamicRelevance = true; // ideally the TranslucencyRelevance should be filled out by the material, here we do it conservative Result.bSeparateTranslucency = Result.bNormalTranslucency = IsShown(View); return Result; } //----------------------------------------------------------------------// // UEQSRenderingComponent //----------------------------------------------------------------------// UEQSRenderingComponent::UEQSRenderingComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , DrawFlagName("GameplayDebug") , bDrawOnlyWhenSelected(true) { } #if UE_ENABLE_DEBUG_DRAWING && USE_EQS_DEBUGGER FDebugRenderSceneProxy* UEQSRenderingComponent::CreateDebugSceneProxy() { FEQSSceneProxy* NewSceneProxy = new FEQSSceneProxy(*this, DrawFlagName, DebugDataSolidSpheres, DebugDataTexts); EQSRenderingDebugDrawDelegateHelper.SetupFromProxy(NewSceneProxy); return NewSceneProxy; } #endif void UEQSRenderingComponent::ClearStoredDebugData() { DebugDataSolidSpheres.Reset(); DebugDataTexts.Reset(); #if USE_EQS_DEBUGGER EQSRenderingDebugDrawDelegateHelper.Reset(); #endif MarkRenderStateDirty(); } #if USE_EQS_DEBUGGER || ENABLE_VISUAL_LOG void UEQSRenderingComponent::StoreDebugData(const EQSDebug::FQueryData& DebugData) { DebugDataSolidSpheres = DebugData.SolidSpheres; DebugDataTexts = DebugData.Texts; MarkRenderStateDirty(); } #endif // USE_EQS_DEBUGGER || ENABLE_VISUAL_LOG FBoxSphereBounds UEQSRenderingComponent::CalcBounds(const FTransform& LocalToWorld) const { #if USE_EQS_DEBUGGER TArray Spheres; Spheres.Append(DebugDataSolidSpheres); const IEQSQueryResultSourceInterface* QueryDataSource = Cast(GetOwner()); if (QueryDataSource == nullptr) { QueryDataSource = Cast(this); } if (QueryDataSource != nullptr) { TArray DebugItems; TArray Texts; FEQSSceneProxy::CollectEQSData(this, QueryDataSource, Spheres, Texts, DebugItems); } if (Spheres.Num()) { FBoxSphereBounds::Builder DebugBoundsBuilder; for (int32 Index = 0; Index < Spheres.Num(); ++Index) { DebugBoundsBuilder += FSphere(Spheres[Index].Location, Spheres[Index].Radius); } return DebugBoundsBuilder; } #endif static FSphere BoundingSphere(FVector::ZeroVector, 0.f); return FBoxSphereBounds(BoundingSphere).TransformBy(LocalToWorld); } //----------------------------------------------------------------------// // UEQSQueryResultSourceInterface //----------------------------------------------------------------------// UEQSQueryResultSourceInterface::UEQSQueryResultSourceInterface(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { }