- Created a comprehensive implementation checklist for the Planar Capture System (Systems 136-147) detailing tasks across multiple phases including C++ core, material foundation, Blueprint actors, data assets, integration, and performance testing. - Added a developer reference document outlining the architecture, data flow, state machine, budget enforcement, render target pooling, horror features, integration points, multiplayer networking, performance characteristics, debugging methods, and build order for the capture systems. - Introduced examples of capture surface usage in the Project Void horror game, including specific implementations for mirrors, monitors, portals, and fake windows, along with a checklist for integration tasks.
582 lines
17 KiB
C++
582 lines
17 KiB
C++
// Copyright Ngonart OU. All Rights Reserved.
|
|
// UE5 Modular Game Framework — BPC_PlanarCapture implementation
|
|
|
|
#include "Capture/BPC_PlanarCapture.h"
|
|
#include "Capture/BP_PlanarCaptureActor.h"
|
|
#include "Capture/SS_PlanarCaptureManager.h"
|
|
#include "Capture/PlanarCaptureCameraUtils.h"
|
|
#include "Components/SceneCaptureComponent2D.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "Engine/World.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
#include "Materials/MaterialParameterCollectionInstance.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
UBPC_PlanarCapture::UBPC_PlanarCapture()
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
PrimaryComponentTick.bStartWithTickEnabled = true;
|
|
PrimaryComponentTick.TickInterval = 0.0f; // We throttle internally via TimeSinceLastCapture
|
|
|
|
// Initialize default quality profiles
|
|
QualityProfiles.SetNum(4);
|
|
|
|
// Low — 256px, 4fps
|
|
QualityProfiles[0].RenderTargetSize = 256;
|
|
QualityProfiles[0].CaptureInterval = 0.25f;
|
|
QualityProfiles[0].bEnableShadows = false;
|
|
QualityProfiles[0].bEnablePostProcess = false;
|
|
QualityProfiles[0].bEnableFog = false;
|
|
QualityProfiles[0].bEnableBloom = false;
|
|
QualityProfiles[0].bEnableAO = false;
|
|
QualityProfiles[0].bEnableLumen = false;
|
|
QualityProfiles[0].bEnableMotionBlur = false;
|
|
QualityProfiles[0].bEnableClipPlane = false;
|
|
|
|
// Medium — 512px, 15fps
|
|
QualityProfiles[1].RenderTargetSize = 512;
|
|
QualityProfiles[1].CaptureInterval = 0.0667f;
|
|
QualityProfiles[1].bEnableShadows = true;
|
|
QualityProfiles[1].bEnablePostProcess = false;
|
|
QualityProfiles[1].bEnableFog = false;
|
|
QualityProfiles[1].bEnableBloom = false;
|
|
QualityProfiles[1].bEnableAO = false;
|
|
QualityProfiles[1].bEnableLumen = false;
|
|
QualityProfiles[1].bEnableMotionBlur = false;
|
|
QualityProfiles[1].bEnableClipPlane = true;
|
|
|
|
// High — 1024px, 30fps
|
|
QualityProfiles[2].RenderTargetSize = 1024;
|
|
QualityProfiles[2].CaptureInterval = 0.0333f;
|
|
QualityProfiles[2].bEnableShadows = true;
|
|
QualityProfiles[2].bEnablePostProcess = true;
|
|
QualityProfiles[2].bEnableFog = true;
|
|
QualityProfiles[2].bEnableBloom = false;
|
|
QualityProfiles[2].bEnableAO = true;
|
|
QualityProfiles[2].bEnableLumen = true;
|
|
QualityProfiles[2].bEnableMotionBlur = false;
|
|
QualityProfiles[2].bEnableClipPlane = true;
|
|
|
|
// Hero — 2048px, 60fps
|
|
QualityProfiles[3].RenderTargetSize = 2048;
|
|
QualityProfiles[3].CaptureInterval = 0.0167f;
|
|
QualityProfiles[3].bEnableShadows = true;
|
|
QualityProfiles[3].bEnablePostProcess = true;
|
|
QualityProfiles[3].bEnableFog = true;
|
|
QualityProfiles[3].bEnableBloom = true;
|
|
QualityProfiles[3].bEnableAO = true;
|
|
QualityProfiles[3].bEnableLumen = true;
|
|
QualityProfiles[3].bEnableMotionBlur = true;
|
|
QualityProfiles[3].bEnableClipPlane = true;
|
|
}
|
|
|
|
void UBPC_PlanarCapture::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
|
|
CachedOwningActor = Cast<ABP_PlanarCaptureActor>(GetOwner());
|
|
if (!CachedOwningActor)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("BPC_PlanarCapture: Owner is not a BP_PlanarCaptureActor! Capture disabled."));
|
|
SetComponentTickEnabled(false);
|
|
return;
|
|
}
|
|
|
|
// Cache manager reference
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
CachedManager = World->GetSubsystem<ASS_PlanarCaptureManager>();
|
|
}
|
|
|
|
ResolveSoftReferences();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|
{
|
|
ShutdownCapture();
|
|
Super::EndPlay(EndPlayReason);
|
|
}
|
|
|
|
void UBPC_PlanarCapture::TickComponent(float DeltaTime, ELevelTick TickType,
|
|
FActorComponentTickFunction* ThisTickFunction)
|
|
{
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
|
|
if (!bIsCapturing || !SceneCapture || CurrentQualityTier == EPlanarCaptureQualityTier::Off)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TimeSinceLastCapture += DeltaTime;
|
|
|
|
if (TimeSinceLastCapture >= ActiveProfile.CaptureInterval)
|
|
{
|
|
TimeSinceLastCapture = 0.0f;
|
|
|
|
// Get viewer camera for transform computation
|
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
|
if (!PC || !PC->PlayerCameraManager)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FTransform ViewerTransform = FTransform(
|
|
PC->PlayerCameraManager->GetCameraRotation(),
|
|
PC->PlayerCameraManager->GetCameraLocation()
|
|
);
|
|
|
|
// Compute capture camera position
|
|
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
|
|
SceneCapture->SetWorldTransform(CaptureTransform);
|
|
|
|
// Capture the scene
|
|
SceneCapture->CaptureScene();
|
|
|
|
// Push MPC parameters
|
|
if (CachedOwningActor && CachedOwningActor->SurfaceMPC)
|
|
{
|
|
PushMPCParameters(CachedOwningActor->SurfaceMPC);
|
|
}
|
|
|
|
OnCaptureRendered.Broadcast();
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// Public API
|
|
// ========================================================================
|
|
|
|
EPlanarCaptureInitResult UBPC_PlanarCapture::InitializeCapture()
|
|
{
|
|
if (!CachedOwningActor)
|
|
{
|
|
return EPlanarCaptureInitResult::InvalidSurfaceMesh;
|
|
}
|
|
|
|
if (CurrentQualityTier == EPlanarCaptureQualityTier::Off)
|
|
{
|
|
return EPlanarCaptureInitResult::Success; // Nothing to initialize
|
|
}
|
|
|
|
// Request render target from pool
|
|
const int32 ProfileIndex = static_cast<int32>(CurrentQualityTier) - 1; // Skip "Off" (0)
|
|
if (ProfileIndex < 0 || ProfileIndex >= QualityProfiles.Num())
|
|
{
|
|
return EPlanarCaptureInitResult::NoRenderTargetPool;
|
|
}
|
|
|
|
ActiveProfile = QualityProfiles[ProfileIndex];
|
|
|
|
if (CachedManager)
|
|
{
|
|
CaptureRenderTarget = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
|
|
}
|
|
|
|
if (!CaptureRenderTarget)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("BPC_PlanarCapture: Failed to allocate render target (%dx%d)"),
|
|
ActiveProfile.RenderTargetSize, ActiveProfile.RenderTargetSize);
|
|
return EPlanarCaptureInitResult::NoRenderTargetPool;
|
|
}
|
|
|
|
CreateSceneCaptureComponent();
|
|
|
|
bIsCapturing = true;
|
|
OnCaptureInitialized.Broadcast(EPlanarCaptureInitResult::Success);
|
|
return EPlanarCaptureInitResult::Success;
|
|
}
|
|
|
|
void UBPC_PlanarCapture::ShutdownCapture()
|
|
{
|
|
bIsCapturing = false;
|
|
|
|
if (SceneCapture)
|
|
{
|
|
SceneCapture->DestroyComponent();
|
|
SceneCapture = nullptr;
|
|
}
|
|
|
|
if (CaptureRenderTarget && CachedManager)
|
|
{
|
|
CachedManager->ReleaseRenderTarget(CaptureRenderTarget);
|
|
CaptureRenderTarget = nullptr;
|
|
}
|
|
|
|
// Clean up frame ring buffer
|
|
for (UTextureRenderTarget2D* RT : FrameRingBuffer)
|
|
{
|
|
if (RT && CachedManager)
|
|
{
|
|
CachedManager->ReleaseRenderTarget(RT);
|
|
}
|
|
}
|
|
FrameRingBuffer.Empty();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::ApplyQualityTier(EPlanarCaptureQualityTier Tier)
|
|
{
|
|
const EPlanarCaptureQualityTier OldTier = CurrentQualityTier;
|
|
CurrentQualityTier = Tier;
|
|
|
|
if (Tier == EPlanarCaptureQualityTier::Off)
|
|
{
|
|
ShutdownCapture();
|
|
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
|
|
return;
|
|
}
|
|
|
|
// If transitioning from Off to active, initialize
|
|
if (OldTier == EPlanarCaptureQualityTier::Off)
|
|
{
|
|
InitializeCapture();
|
|
}
|
|
else
|
|
{
|
|
// Update active profile and resize render target if needed
|
|
const int32 ProfileIndex = static_cast<int32>(Tier) - 1;
|
|
if (ProfileIndex >= 0 && ProfileIndex < QualityProfiles.Num())
|
|
{
|
|
ActiveProfile = QualityProfiles[ProfileIndex];
|
|
ApplyShowFlags();
|
|
}
|
|
}
|
|
|
|
OnCaptureQualityChanged.Broadcast(OldTier, Tier);
|
|
}
|
|
|
|
void UBPC_PlanarCapture::CaptureNow()
|
|
{
|
|
if (!SceneCapture)
|
|
{
|
|
return;
|
|
}
|
|
|
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
|
if (!PC || !PC->PlayerCameraManager)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FTransform ViewerTransform = FTransform(
|
|
PC->PlayerCameraManager->GetCameraRotation(),
|
|
PC->PlayerCameraManager->GetCameraLocation()
|
|
);
|
|
|
|
const FTransform CaptureTransform = ComputeCaptureCameraTransform(ViewerTransform);
|
|
SceneCapture->SetWorldTransform(CaptureTransform);
|
|
SceneCapture->CaptureScene();
|
|
|
|
TimeSinceLastCapture = 0.0f;
|
|
OnCaptureRendered.Broadcast();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::ActivateHorrorReflection()
|
|
{
|
|
// Save current ShowOnly list
|
|
SavedShowOnlyActors.Empty();
|
|
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
|
|
{
|
|
SavedShowOnlyActors.Add(Entry.Actor);
|
|
}
|
|
|
|
// Clear and set wrong reflection actor
|
|
ShowOnlyActors.Empty();
|
|
if (WrongReflectionActor.IsValid())
|
|
{
|
|
FPlanarCaptureActorListEntry NewEntry;
|
|
NewEntry.Actor = WrongReflectionActor;
|
|
NewEntry.bActive = true;
|
|
ShowOnlyActors.Add(NewEntry);
|
|
}
|
|
|
|
UpdateActorLists();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::DeactivateHorrorReflection()
|
|
{
|
|
ShowOnlyActors.Empty();
|
|
for (const TSoftObjectPtr<AActor>& SavedActor : SavedShowOnlyActors)
|
|
{
|
|
FPlanarCaptureActorListEntry NewEntry;
|
|
NewEntry.Actor = SavedActor;
|
|
NewEntry.bActive = true;
|
|
ShowOnlyActors.Add(NewEntry);
|
|
}
|
|
|
|
SavedShowOnlyActors.Empty();
|
|
UpdateActorLists();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::PushDelayedFrame()
|
|
{
|
|
if (ActiveProfile.DelayedFrameCount <= 0 || !CaptureRenderTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Ensure ring buffer is properly sized
|
|
while (FrameRingBuffer.Num() < ActiveProfile.DelayedFrameCount)
|
|
{
|
|
UTextureRenderTarget2D* NewRT = nullptr;
|
|
if (CachedManager)
|
|
{
|
|
NewRT = CachedManager->RequestRenderTarget(ActiveProfile.RenderTargetSize);
|
|
}
|
|
FrameRingBuffer.Add(NewRT);
|
|
}
|
|
|
|
// Copy current render target to ring buffer slot
|
|
RingBufferWriteIndex = (RingBufferWriteIndex + 1) % FrameRingBuffer.Num();
|
|
|
|
// Push the oldest frame to the material as the delayed reflection
|
|
if (FrameRingBuffer.IsValidIndex(RingBufferWriteIndex) && FrameRingBuffer[RingBufferWriteIndex])
|
|
{
|
|
// The material samples the delayed frame via the MPC texture parameter
|
|
// This is set in PushMPCParameters
|
|
}
|
|
}
|
|
|
|
void UBPC_PlanarCapture::SetScriptedPriority(float Priority)
|
|
{
|
|
ScriptedPriorityOverride = FMath::Clamp(Priority, 0.0f, 1.0f);
|
|
}
|
|
|
|
void UBPC_PlanarCapture::PushMPCParameters(UMaterialParameterCollection* MPC)
|
|
{
|
|
if (!MPC)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMaterialParameterCollectionInstance* MPCInstance = GetWorld()->GetParameterCollectionInstance(MPC);
|
|
if (!MPCInstance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Push standard parameters
|
|
// These are used by M_CaptureSurface_Master to drive steam, dirt, horror effects
|
|
MPCInstance->SetScalarParameterValue(FName("SteamIntensity"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("DirtOpacity"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("CondensationFlow"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("DistortionAmplitude"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("MirrorDarkness"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("WrongReflectionBlend"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("TextRevealProgress"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("SteamEmissiveIntensity"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("DelayedReflectionBlend"), 0.0f);
|
|
MPCInstance->SetScalarParameterValue(FName("SurfaceAge"), 0.0f);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Compute
|
|
// ========================================================================
|
|
|
|
FTransform UBPC_PlanarCapture::ComputeCaptureCameraTransform(const FTransform& ViewerCameraTransform) const
|
|
{
|
|
switch (CaptureMode)
|
|
{
|
|
case EPlanarCaptureMode::Mirror:
|
|
case EPlanarCaptureMode::HorrorMirror:
|
|
{
|
|
const FTransform SurfaceTransform = CachedOwningActor
|
|
? CachedOwningActor->GetActorTransform()
|
|
: FTransform::Identity;
|
|
return UPlanarCaptureCameraUtils::ComputeMirroredTransform(ViewerCameraTransform, SurfaceTransform);
|
|
}
|
|
|
|
case EPlanarCaptureMode::Portal:
|
|
case EPlanarCaptureMode::HorrorPortal:
|
|
{
|
|
if (LinkedTargetSurface.IsValid())
|
|
{
|
|
const FTransform SourceTransform = CachedOwningActor
|
|
? CachedOwningActor->GetActorTransform()
|
|
: FTransform::Identity;
|
|
const FTransform TargetTransform = LinkedTargetSurface->GetActorTransform();
|
|
return UPlanarCaptureCameraUtils::ComputePortalTransform(
|
|
ViewerCameraTransform, SourceTransform, TargetTransform);
|
|
}
|
|
return ViewerCameraTransform;
|
|
}
|
|
|
|
case EPlanarCaptureMode::Monitor:
|
|
{
|
|
if (FixedCameraActor.IsValid())
|
|
{
|
|
return FixedCameraActor->GetActorTransform();
|
|
}
|
|
return ViewerCameraTransform;
|
|
}
|
|
|
|
case EPlanarCaptureMode::FakeWindow:
|
|
{
|
|
// Fake windows use a fixed offset from the surface
|
|
const FTransform SurfaceTransform = CachedOwningActor
|
|
? CachedOwningActor->GetActorTransform()
|
|
: FTransform::Identity;
|
|
const FVector SurfaceNormal = SurfaceTransform.GetUnitAxis(EAxis::Z);
|
|
return FTransform(SurfaceTransform.GetRotation(),
|
|
SurfaceTransform.GetLocation() + SurfaceNormal * 200.0f);
|
|
}
|
|
|
|
default:
|
|
return ViewerCameraTransform;
|
|
}
|
|
}
|
|
|
|
FPlanarCaptureScore UBPC_PlanarCapture::GetCurrentScore() const
|
|
{
|
|
FPlanarCaptureScore Score;
|
|
|
|
APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0);
|
|
if (!PC || !PC->PlayerCameraManager || !CachedOwningActor)
|
|
{
|
|
return Score;
|
|
}
|
|
|
|
const FVector ViewerPos = PC->PlayerCameraManager->GetCameraLocation();
|
|
const FTransform ViewerTransform = FTransform(
|
|
PC->PlayerCameraManager->GetCameraRotation(),
|
|
ViewerPos
|
|
);
|
|
|
|
const FVector SurfacePos = CachedOwningActor->GetActorLocation();
|
|
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
|
|
|
|
Score.DistanceToViewer = FVector::Dist(ViewerPos, SurfacePos);
|
|
Score.FacingAngle = FVector::DotProduct(
|
|
PC->PlayerCameraManager->GetCameraRotation().Vector(),
|
|
SurfaceNormal
|
|
);
|
|
|
|
// Compute screen coverage using bounding box
|
|
const FBox SurfaceBounds = CachedOwningActor->GetComponentsBoundingBox();
|
|
Score.ScreenCoverage = UPlanarCaptureCameraUtils::ComputeScreenCoverage(
|
|
SurfaceBounds, ViewerTransform, CaptureFOV, 1920, 1080);
|
|
|
|
Score.bInFrustum = UPlanarCaptureCameraUtils::IsSurfaceVisibleToViewer(
|
|
SurfaceBounds, ViewerTransform, CaptureFOV, 1.777f, 10.0f, MaxViewDistance);
|
|
|
|
Score.ScriptedPriority = ScriptedPriorityOverride;
|
|
|
|
Score.CompositeScore = UPlanarCaptureCameraUtils::ComputeCompositeScore(
|
|
Score.ScreenCoverage, Score.FacingAngle, Score.DistanceToViewer,
|
|
CachedManager ? CachedManager->MaxCaptureDistance : 10000.0f,
|
|
Score.ScriptedPriority);
|
|
|
|
return Score;
|
|
}
|
|
|
|
// ========================================================================
|
|
// Internal Methods
|
|
// ========================================================================
|
|
|
|
void UBPC_PlanarCapture::CreateSceneCaptureComponent()
|
|
{
|
|
if (!GetOwner())
|
|
{
|
|
return;
|
|
}
|
|
|
|
SceneCapture = NewObject<USceneCaptureComponent2D>(GetOwner(), USceneCaptureComponent2D::StaticClass());
|
|
if (!SceneCapture)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SceneCapture->RegisterComponent();
|
|
SceneCapture->AttachToComponent(GetOwner()->GetRootComponent(),
|
|
FAttachmentTransformRules::SnapToTargetNotIncludingScale);
|
|
|
|
SceneCapture->TextureTarget = CaptureRenderTarget;
|
|
SceneCapture->FOVAngle = CaptureFOV;
|
|
SceneCapture->bCaptureEveryFrame = false; // We control capture timing
|
|
SceneCapture->bCaptureOnMovement = false;
|
|
|
|
// Configure for planar surface capture
|
|
SceneCapture->ProjectionType = ECameraProjectionMode::Perspective;
|
|
SceneCapture->PrimitiveRenderMode = ESceneCapturePrimitiveRenderMode::PRM_UseShowOnlyList;
|
|
|
|
ApplyShowFlags();
|
|
UpdateActorLists();
|
|
}
|
|
|
|
void UBPC_PlanarCapture::ApplyShowFlags()
|
|
{
|
|
if (!SceneCapture)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Apply show flags based on active quality profile
|
|
SceneCapture->ShowFlags.SetShadows(ActiveProfile.bEnableShadows);
|
|
SceneCapture->ShowFlags.SetFog(ActiveProfile.bEnableFog);
|
|
SceneCapture->ShowFlags.SetBloom(ActiveProfile.bEnableBloom);
|
|
SceneCapture->ShowFlags.SetAmbientOcclusion(ActiveProfile.bEnableAO);
|
|
SceneCapture->ShowFlags.SetMotionBlur(ActiveProfile.bEnableMotionBlur);
|
|
|
|
// Lumen is controlled via post-process settings on the capture component
|
|
SceneCapture->PostProcessSettings.bOverride_DynamicGlobalIlluminationMethod = true;
|
|
SceneCapture->PostProcessSettings.DynamicGlobalIlluminationMethod = ActiveProfile.bEnableLumen
|
|
? EDynamicGlobalIlluminationMethod::Lumen
|
|
: EDynamicGlobalIlluminationMethod::None;
|
|
|
|
// Post-process toggle
|
|
SceneCapture->PostProcessSettings.bOverride_BloomIntensity = !ActiveProfile.bEnablePostProcess;
|
|
SceneCapture->PostProcessBlendWeight = ActiveProfile.bEnablePostProcess ? 1.0f : 0.0f;
|
|
|
|
// Disable Lumen reflections on the capture itself to prevent double-rendering
|
|
SceneCapture->PostProcessSettings.bOverride_ReflectionMethod = true;
|
|
SceneCapture->PostProcessSettings.ReflectionMethod = EReflectionMethod::None;
|
|
}
|
|
|
|
void UBPC_PlanarCapture::UpdateActorLists()
|
|
{
|
|
if (!SceneCapture)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SceneCapture->ShowOnlyActors.Empty();
|
|
SceneCapture->HiddenActors.Empty();
|
|
|
|
for (const FPlanarCaptureActorListEntry& Entry : ShowOnlyActors)
|
|
{
|
|
if (Entry.bActive && Entry.Actor.IsValid())
|
|
{
|
|
SceneCapture->ShowOnlyActors.Add(Entry.Actor.Get());
|
|
}
|
|
}
|
|
|
|
for (const FPlanarCaptureActorListEntry& Entry : HiddenActors)
|
|
{
|
|
if (Entry.bActive && Entry.Actor.IsValid())
|
|
{
|
|
SceneCapture->HiddenActors.Add(Entry.Actor.Get());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UBPC_PlanarCapture::ResolveSoftReferences()
|
|
{
|
|
if (SurfaceMeshComponent.IsValid())
|
|
{
|
|
// Surface mesh is resolved — ready for capture
|
|
}
|
|
}
|
|
|
|
FPlane UBPC_PlanarCapture::GetSurfacePlane() const
|
|
{
|
|
if (!CachedOwningActor)
|
|
{
|
|
return FPlane(FVector::ZeroVector, FVector::UpVector);
|
|
}
|
|
|
|
const FVector SurfaceLocation = CachedOwningActor->GetActorLocation();
|
|
const FVector SurfaceNormal = CachedOwningActor->GetActorUpVector();
|
|
|
|
return FPlane(SurfaceLocation, SurfaceNormal);
|
|
}
|