// Copyright Epic Games, Inc. All Rights Reserved. #include "Chaos/ChaosDebugDrawComponent.h" #include "Chaos/DebugDrawQueue.h" #include "ChaosLog.h" #include "Debug/DebugDrawService.h" #include "DrawDebugHelpers.h" #if WITH_EDITOR #include "UObject/UObjectIterator.h" #endif #include "Engine/Canvas.h" #include "Engine/Engine.h" #include "Engine/HitResult.h" #include "Engine/World.h" #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "Physics/Experimental/PhysScene_Chaos.h" #include "VisualLogger/VisualLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ChaosDebugDrawComponent) #if CHAOS_DEBUG_DRAW void ChaosDebugDraw_Enabled_Changed(IConsoleVariable* CVar) { Chaos::FDebugDrawQueue::GetInstance().SetEnabled(CVar->GetBool()); } bool bChaosDebugDraw_Enabled = false; FAutoConsoleVariableRef CVarChaos_DebugDraw_Enabled(TEXT("p.Chaos.DebugDraw.Enabled"), bChaosDebugDraw_Enabled, TEXT("Whether to debug draw low level physics solver information"), FConsoleVariableDelegate::CreateStatic(ChaosDebugDraw_Enabled_Changed)); int ChaosDebugDraw_MaxElements = 20000; FAutoConsoleVariableRef CVarChaos_DebugDraw_MaxElements(TEXT("p.Chaos.DebugDraw.MaxLines"), ChaosDebugDraw_MaxElements, TEXT("Set the maximum number of debug draw lines that can be rendered (to limit perf drops)")); float ChaosDebugDraw_Radius = 3000.0f; FAutoConsoleVariableRef CVarChaos_DebugDraw_Radius(TEXT("p.Chaos.DebugDraw.Radius"), ChaosDebugDraw_Radius, TEXT("Set the radius from the camera where debug draw capture stops (0 means infinite)")); bool ChaosDebugDraw_SeeThrough = true; FAutoConsoleVariableRef CVarChaos_DebugDraw_SeeThrough(TEXT("p.Chaos.DebugDraw.SeeThrough"), ChaosDebugDraw_SeeThrough, TEXT("When enabled , lines will be drawn on top of all geometry")); bool bChaosDebugDraw_SingleActor = false; FAutoConsoleVariableRef CVarChaosDebugDraw_SingleActor(TEXT("p.Chaos.DebugDraw.SingleActor"), bChaosDebugDraw_SingleActor, TEXT("If true, then we draw for the actor the camera is looking at.")); float ChaosDebugDraw_SingleActorTraceLength = 2000.0f; FAutoConsoleVariableRef CVarChaosDebugDraw_SingleActorTraceLength(TEXT("p.Chaos.DebugDraw.SingleActorTraceLength"), ChaosDebugDraw_SingleActorTraceLength, TEXT("Set the trace length from the camera that is used to select the single actor.")); float ChaosDebugDraw_SingleActorMaxRadius = 1000.0f; FAutoConsoleVariableRef CVarChaosDebugDraw_SingleActorMaxRadius(TEXT("p.Chaos.DebugDraw.SingleActorMaxRadius"), ChaosDebugDraw_SingleActorMaxRadius, TEXT("Set the max radius to draw around the single actor.")); float ChaosDebugDraw_ShowPIEServer = false; FAutoConsoleVariableRef CVarChaos_DebugDraw_ShowPIEServer(TEXT("p.Chaos.DebugDraw.ShowPIEServer"), ChaosDebugDraw_ShowPIEServer, TEXT("When running in PIE mode, show the server debug draw")); float ChaosDebugDraw_ShowPIEClient = true; FAutoConsoleVariableRef CVarChaos_DebugDraw_ShowPIEClient(TEXT("p.Chaos.DebugDraw.ShowPIEClient"), ChaosDebugDraw_ShowPIEClient, TEXT("When running in PIE mode, show the client debug draw")); int bChaosDebugDraw_DrawMode = 0; FAutoConsoleVariableRef CVarArrowSize(TEXT("p.Chaos.DebugDraw.Mode"), bChaosDebugDraw_DrawMode, TEXT("Where to send debug draw commands. 0 = UE Debug Draw; 1 = VisLog; 2 = Both")); float CommandLifeTime(const float LifeTime) { // The linebatch time handling is a bit awkward and we need to translate // Linebatcher Lifetime < 0: eternal (regardless of bPersistent flag) // Linebatcher Lifetime = 0: default lifetime (which is usually 1 second) // Linebatcher Lifetime > 0: specified duration // Whereas in our Command, // Command lifetime <= 0: 1 frame // Command lifetime > 0: specified duration if (LifeTime <= 0) { // One frame - must be non-zero but also less than the next frame time which we don't know // NOTE: this only works because UChaosDebugDrawComponent ticks after the line batcher return UE_SMALL_NUMBER; } return LifeTime; } void DebugDrawChaosCommand(const UWorld* World, const Chaos::FLatentDrawCommand& Command) { using namespace Chaos; const uint8 DepthPriority = ChaosDebugDraw_SeeThrough ? Command.DepthPriority : 0; switch (Command.Type) { case FLatentDrawCommand::EDrawType::Point: DrawDebugPoint(World, Command.LineStart, Command.Thickness, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority); break; case FLatentDrawCommand::EDrawType::Line: DrawDebugLine(World, Command.LineStart, Command.LineEnd, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness); break; case FLatentDrawCommand::EDrawType::DirectionalArrow: DrawDebugDirectionalArrow(World, Command.LineStart, Command.LineEnd, Command.ArrowSize, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness); break; case FLatentDrawCommand::EDrawType::Sphere: DrawDebugSphere(World, Command.LineStart, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness); break; case FLatentDrawCommand::EDrawType::Box: DrawDebugBox(World, Command.Center, Command.Extent, Command.Rotation, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness); break; case FLatentDrawCommand::EDrawType::String: DrawDebugString(World, Command.TextLocation, Command.Text, Command.TestBaseActor, Command.Color, CommandLifeTime(Command.LifeTime), Command.bDrawShadow, Command.FontScale); break; case FLatentDrawCommand::EDrawType::Circle: { FMatrix M = FRotationMatrix::MakeFromYZ(Command.YAxis, Command.ZAxis); M.SetOrigin(Command.Center); DrawDebugCircle(World, M, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness, Command.bDrawAxis); break; } case FLatentDrawCommand::EDrawType::Capsule: DrawDebugCapsule(World, Command.Center, Command.HalfHeight, Command.Radius, Command.Rotation, Command.Color, Command.bPersistentLines, CommandLifeTime(Command.LifeTime), DepthPriority, Command.Thickness); default: break; } } void VisLogChaosCommand(const AActor* Actor, const Chaos::FLatentDrawCommand& Command) { using namespace Chaos; switch (Command.Type) { case FLatentDrawCommand::EDrawType::Point: UE_VLOG_SEGMENT_THICK(Actor, LogChaos, Log, Command.LineStart, Command.LineStart, Command.Color, Command.Thickness, TEXT_EMPTY); break; case FLatentDrawCommand::EDrawType::Line: UE_VLOG_SEGMENT(Actor, LogChaos, Log, Command.LineStart, Command.LineEnd, Command.Color, TEXT_EMPTY); break; case FLatentDrawCommand::EDrawType::DirectionalArrow: UE_VLOG_SEGMENT(Actor, LogChaos, Log, Command.LineStart, Command.LineEnd, Command.Color, TEXT_EMPTY); break; case FLatentDrawCommand::EDrawType::Sphere: { // VLOG Capsule uses the bottom end as the origin (though the variable is named Center) FVector Base = Command.LineStart - Command.Radius * FVector::UpVector; UE_VLOG_CAPSULE(Actor, LogChaos, Log, Base, Command.Radius + KINDA_SMALL_NUMBER, Command.Radius, FQuat::Identity, Command.Color, TEXT_EMPTY); break; } case FLatentDrawCommand::EDrawType::Box: UE_VLOG_OBOX(Actor, LogChaos, Log, FBox(-Command.Extent, Command.Extent), FQuatRotationTranslationMatrix::Make(Command.Rotation, Command.Center), Command.Color, TEXT_EMPTY); break; case FLatentDrawCommand::EDrawType::String: UE_VLOG(Command.TestBaseActor, LogChaos, Log, TEXT("%s"), *Command.Text); break; case FLatentDrawCommand::EDrawType::Circle: { const FMatrix M = FRotationMatrix::MakeFromYZ(Command.YAxis, Command.ZAxis); UE_VLOG_CIRCLE(Actor, LogChaos, Log, Command.Center, M.GetUnitAxis(EAxis::X), Command.Radius, Command.Color, TEXT_EMPTY); break; } case FLatentDrawCommand::EDrawType::Capsule: { // VLOG Capsule uses the bottom end as the origin (though the variable is named Center) FVector Base = Command.Center - Command.HalfHeight * (Command.Rotation * FVector::UpVector); UE_VLOG_CAPSULE(Actor, LogChaos, Log, Base, Command.HalfHeight, Command.Radius, Command.Rotation, Command.Color, TEXT_EMPTY); break; } default: break; } } void DebugDrawChaos(const AActor* DebugDrawActor, const TArray& DrawCommands, const bool bIsPaused) { using namespace Chaos; if (DebugDrawActor == nullptr) { return; } const UWorld* World = DebugDrawActor->GetWorld(); if (World == nullptr) { return; } if (World->IsPreviewWorld()) { return; } if ((World->GetNetMode() == NM_DedicatedServer) && !ChaosDebugDraw_ShowPIEServer) { return; } if ((World->GetNetMode() != NM_DedicatedServer) && !ChaosDebugDraw_ShowPIEClient) { return; } // Draw all the captured elements in the viewport const bool bDrawUe = bChaosDebugDraw_DrawMode != 1; if (bDrawUe) { for (const FLatentDrawCommand& Command : DrawCommands) { DebugDrawChaosCommand(World, Command); } } // Draw all the captured elements in the VisLog const bool bDrawVisLog = bChaosDebugDraw_DrawMode != 0; if (bDrawVisLog) { for (const FLatentDrawCommand& Command : DrawCommands) { const AActor* Actor = (Command.TestBaseActor) ? Command.TestBaseActor : DebugDrawActor; VisLogChaosCommand(Actor, Command); } } } #endif UChaosDebugDrawComponent::UChaosDebugDrawComponent() : bInPlay(false) { #if CHAOS_DEBUG_DRAW // We must tick after anything that uses Chaos Debug Draw and also after the Line Batcher Component PrimaryComponentTick.bAllowTickOnDedicatedServer = false; PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.SetTickFunctionEnable(true); PrimaryComponentTick.TickGroup = TG_PostUpdateWork; bTickInEditor = true; #endif } void UChaosDebugDrawComponent::BeginDestroy() { Super::BeginDestroy(); #if CHAOS_DEBUG_DRAW // Don't allow new commands to be enqueued when we are paused Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, false); #endif } void UChaosDebugDrawComponent::BeginPlay() { Super::BeginPlay(); #if CHAOS_DEBUG_DRAW SetTickableWhenPaused(true); #endif bInPlay = true; #if CHAOS_DEBUG_DRAW Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, bInPlay); ChaosDebugDraw_Enabled_Changed(&(*CVarChaos_DebugDraw_Enabled)); #endif } void UChaosDebugDrawComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); #if CHAOS_DEBUG_DRAW SetTickableWhenPaused(false); #endif bInPlay = false; #if CHAOS_DEBUG_DRAW Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, bInPlay); #endif } #if WITH_EDITOR && CHAOS_DEBUG_DRAW void UChaosDebugDrawComponent::OnRegister() { Super::OnRegister(); const UWorld* World = GetWorld(); check(World); // GameWorld is handled by BeginPlay/EndPlay if (!World->IsGameWorld() && !World->IsPreviewWorld()) { Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, /*bConsumerActive*/true); } } void UChaosDebugDrawComponent::OnUnregister() { const UWorld* World = GetWorld(); check(World); // GameWorld is handled by BeginPlay/EndPlay if (!World->IsGameWorld() && !World->IsPreviewWorld()) { Chaos::FDebugDrawQueue::GetInstance().SetConsumerActive(this, /*bConsumerActive*/false); } Super::OnUnregister(); } #endif // WITH_EDITOR && CHAOS_DEBUG_DRAW void UChaosDebugDrawComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { using namespace Chaos; Super::TickComponent(DeltaTime, TickType, ThisTickFunction); #if CHAOS_DEBUG_DRAW // Update the region of interest based on camera location // @todo(chaos): this should use the view location of the primary viewport, but not sure how to get that // We're not handling multiple worlds or viewports anyway, so this is as good as it gets. UWorld* World = GetWorld(); if (World != nullptr) { FVector RegionOfInterestOrigin = FVec3(0); float RegionOfInterestRadius = ChaosDebugDraw_Radius; if (World->ViewLocationsRenderedLastFrame.Num() > 0) { RegionOfInterestOrigin = World->ViewLocationsRenderedLastFrame[0]; if (bChaosDebugDraw_SingleActor) { if (const APlayerController* Controller = GEngine->GetFirstLocalPlayerController(World)) { FVector CamLoc; FRotator CamRot; Controller->GetPlayerViewPoint(CamLoc, CamRot); FVector CamForward = CamRot.Vector(); CamForward *= ChaosDebugDraw_SingleActorTraceLength; FVector TraceStart = CamLoc; FVector TraceEnd = TraceStart + CamForward; FHitResult HitResult(ForceInit); FCollisionQueryParams TraceParams(SCENE_QUERY_STAT(ChaosDebugVisibilityTrace), true, Controller->GetPawn()); bool bHit = World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Visibility, TraceParams); if (bHit && HitResult.GetActor() != nullptr) { FVector Origin, BoxExtent; HitResult.GetActor()->GetActorBounds(true, Origin, BoxExtent); const float Radius = FMath::Min(BoxExtent.Size(), ChaosDebugDraw_SingleActorMaxRadius); RegionOfInterestOrigin = Origin; RegionOfInterestRadius = Radius; } } } } FDebugDrawQueue::GetInstance().SetRegionOfInterest(RegionOfInterestOrigin, RegionOfInterestRadius); FDebugDrawQueue::GetInstance().SetMaxCost(ChaosDebugDraw_MaxElements); // Draw any commands from threads that did not have the debug draw context initialized // @todo(chaos): we should make sure all threads initialize the context... const bool bIsPaused = World->IsPaused(); if (!bIsPaused) { FDebugDrawQueue::GetInstance().ExtractAllElements(DrawCommands); DebugDrawChaos(GetOwner(), DrawCommands, World->IsPaused()); } } #endif } void UChaosDebugDrawComponent::BindWorldDelegates() { #if CHAOS_DEBUG_DRAW FWorldDelegates::OnPostWorldInitialization.AddStatic(&HandlePostWorldInitialization); #endif } void UChaosDebugDrawComponent::HandlePostWorldInitialization(UWorld* World, const UWorld::InitializationValues) { #if CHAOS_DEBUG_DRAW if ((World != nullptr) && !IsRunningCommandlet() && !World->IsPreviewWorld()) { CreateDebugDrawActor(World); } #endif } void UChaosDebugDrawComponent::CreateDebugDrawActor(UWorld* World) { #if CHAOS_DEBUG_DRAW if ((World->GetNetMode() != ENetMode::NM_Client) && (World->GetNetMode() != ENetMode::NM_Standalone)) return; static FName NAME_ChaosDebugDrawActor = TEXT("ChaosDebugDrawActor"); FActorSpawnParameters Params; Params.Name = NAME_ChaosDebugDrawActor; Params.ObjectFlags = Params.ObjectFlags | RF_Transient; #if WITH_EDITOR Params.bHideFromSceneOutliner = true; // Make sure to not create more than one actor for non game worlds. // Those can get reinitialized and OnPostWorldInitialization is called more than once if (!World->IsGameWorld()) { for (TObjectIterator It; It; ++It) { const AActor* Actor = It->GetOwner(); if (Actor != nullptr && Actor->GetFName()== NAME_ChaosDebugDrawActor && Actor->GetWorld() == World) { return; } } } #endif // WITH_EDITOR AActor* Actor = World->SpawnActor(FVector::ZeroVector, FRotator::ZeroRotator, Params); UChaosDebugDrawComponent* Comp = NewObject(Actor); Actor->AddInstanceComponent(Comp); Comp->RegisterComponent(); // SetMaxCommands and SetRegionOfInterest here so that the first frame gets whatever cvar value we have set. // We also call it every tick (which is at the end of each frame) Chaos::FDebugDrawQueue::GetInstance().SetMaxCost(ChaosDebugDraw_MaxElements); Chaos::FDebugDrawQueue::GetInstance().SetRegionOfInterest(FVector::ZeroVector, ChaosDebugDraw_Radius); #endif }