83 changed files with 6810 additions and 0 deletions
@ -0,0 +1,57 @@ |
|||||
|
{ |
||||
|
"FileVersion": 3, |
||||
|
"Version": 1, |
||||
|
"VersionName": "1.6.5", |
||||
|
"FriendlyName": "ArchVis Tools", |
||||
|
"Description": "Provides Camera Actors with added functionality useful for architecture rendering like an automatic vertical lines correction for 2-point perspectives.", |
||||
|
"Category": "Architecture", |
||||
|
"CreatedBy": "Voulz", |
||||
|
"CreatedByURL": "https://github.com/Voulz/", |
||||
|
"DocsURL": "https://github.com/Voulz/ArchVisTools/wiki", |
||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/6641a667740b41289ae35a030ab6514a", |
||||
|
"SupportURL": "https://github.com/Voulz/ArchVisTools/issues", |
||||
|
"EngineVersion": "5.6.0", |
||||
|
"CanContainContent": true, |
||||
|
"IsBetaVersion": true, |
||||
|
"Installed": true, |
||||
|
"Modules": [ |
||||
|
{ |
||||
|
"Name": "ArchVisTools", |
||||
|
"Type": "Runtime", |
||||
|
"LoadingPhase": "Default", |
||||
|
"PlatformAllowList": [ |
||||
|
"Win64" |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "ArchVisToolsEditor", |
||||
|
"Type": "Editor", |
||||
|
"LoadingPhase": "PostEngineInit", |
||||
|
"PlatformAllowList": [ |
||||
|
"Win64" |
||||
|
] |
||||
|
} |
||||
|
], |
||||
|
"Plugins": [ |
||||
|
{ |
||||
|
"Name": "MovieRenderPipeline", |
||||
|
"Enabled": true |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "DataprepEditor", |
||||
|
"Enabled": true |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "ActorLayerUtilities", |
||||
|
"Enabled": true |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "MoviePipelineMaskRenderPass", |
||||
|
"Enabled": true |
||||
|
}, |
||||
|
{ |
||||
|
"Name": "LevelSequenceEditor", |
||||
|
"Enabled": true |
||||
|
} |
||||
|
] |
||||
|
} |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,56 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
using UnrealBuildTool; |
||||
|
|
||||
|
|
||||
|
public class ArchVisTools : ModuleRules |
||||
|
{ |
||||
|
public ArchVisTools(ReadOnlyTargetRules Target) : base(Target) |
||||
|
{ |
||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; |
||||
|
|
||||
|
PublicIncludePaths.AddRange(new string[] { |
||||
|
// ... add public include paths required here ...
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PrivateIncludePaths.AddRange(new string[] { |
||||
|
// ... add other private include paths required here ...
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PublicDependencyModuleNames.AddRange(new string[] { |
||||
|
"Core", |
||||
|
"CoreUObject", |
||||
|
"Engine", |
||||
|
"InputCore", |
||||
|
"RHI", |
||||
|
// ... add other public dependencies that you statically link with here ...
|
||||
|
"CinematicCamera", |
||||
|
"MovieRenderPipelineCore", |
||||
|
"MovieRenderPipelineRenderPasses", |
||||
|
// "RenderCore",
|
||||
|
//"DataprepCore",
|
||||
|
//"UnrealEd",
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PrivateDependencyModuleNames.AddRange(new string[] { |
||||
|
"CoreUObject", |
||||
|
"Engine", |
||||
|
"Slate", |
||||
|
"SlateCore", |
||||
|
// ... add private dependencies that you statically link with here ...
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
DynamicallyLoadedModuleNames.AddRange(new string[] { |
||||
|
// ... add any modules that your module loads dynamically here ...
|
||||
|
}); |
||||
|
|
||||
|
if (Target.bBuildEditor) |
||||
|
{ |
||||
|
PrivateDependencyModuleNames.Add("UnrealEd"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
|
||||
|
#include "ArchVisCineCameraActor.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
|
||||
|
|
||||
|
#define LOCTEXT_NAMESPACE "ArchVisCineCameraActor" |
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
|
||||
|
AArchVisCineCameraActor::AArchVisCineCameraActor(const FObjectInitializer& ObjectInitializer) |
||||
|
: Super(ObjectInitializer.SetDefaultSubobjectClass<UArchVisCineCameraComponent>(TEXT("CameraComponent"))) |
||||
|
{ |
||||
|
ArchVisCineCameraComponent = Cast<UArchVisCineCameraComponent>(GetCameraComponent()); |
||||
|
} |
||||
|
|
||||
|
#undef LOCTEXT_NAMESPACE |
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
@ -0,0 +1,141 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
|
||||
|
#include "DrawDebugHelpers.h" |
||||
|
#include "UnrealClient.h" |
||||
|
#include "Engine/Engine.h" |
||||
|
#include "Engine/GameViewportClient.h" |
||||
|
#include "Misc/ConfigCacheIni.h" |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
#include "LevelEditorViewport.h" |
||||
|
#include "Math/Vector.h" |
||||
|
#include "Misc/AssertionMacros.h" |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
|
||||
|
/** Set to true to print debug logs in the console */ |
||||
|
#define U_ARCH_VIS_CINE_CAMERA_COMPONENT_DEBUG 0 |
||||
|
|
||||
|
#undef HERE_D |
||||
|
#if U_ARCH_VIS_CINE_CAMERA_COMPONENT_DEBUG |
||||
|
/** Prints the current function name in the Output if U_ARCH_VIS_CINE_CAMERA_COMPONENT_DEBUG is set */ |
||||
|
#define HERE_D LOGV(" ==== [%s] ==== [ %s ]", __FUNCTIONW__, GetOwner() ? *GetOwner()->GetName() : *FString("")); |
||||
|
#define LOG_D(Format, ...) LOG(Format,__VA_ARGS__); |
||||
|
#define WARN_D(Format, ...) WARN(Format,__VA_ARGS__); |
||||
|
#else |
||||
|
/** Prints the current function name in the Output if U_ARCH_VIS_CINE_CAMERA_COMPONENT_DEBUG is set */ |
||||
|
#define HERE_D {} |
||||
|
#define LOG_D(Format, ...) {} |
||||
|
#define WARN_D(Format, ...) {} |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
|
||||
|
void UArchVisCineCameraComponent::InitFromCameraComponent(const UCameraComponent* CameraComponent, const bool bCopyTransform) |
||||
|
{ |
||||
|
if (!IsValid(CameraComponent)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// -- UActorComponent
|
||||
|
ComponentTags = TArray<FName>(CameraComponent->ComponentTags); |
||||
|
|
||||
|
|
||||
|
// -- USceneComponent
|
||||
|
if (bCopyTransform) |
||||
|
{ |
||||
|
SetWorldLocationAndRotationNoPhysics(CameraComponent->GetComponentLocation(), CameraComponent->GetComponentRotation()); |
||||
|
SetWorldScale3D(CameraComponent->GetComponentScale()); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// -- UCameraComponent
|
||||
|
FieldOfView = CameraComponent->FieldOfView; |
||||
|
OrthoWidth = CameraComponent->OrthoWidth; |
||||
|
OrthoNearClipPlane = CameraComponent->OrthoNearClipPlane; |
||||
|
OrthoFarClipPlane = CameraComponent->OrthoFarClipPlane; |
||||
|
AspectRatio = CameraComponent->AspectRatio; |
||||
|
bConstrainAspectRatio = CameraComponent->bConstrainAspectRatio; |
||||
|
bUseFieldOfViewForLOD = CameraComponent->bUseFieldOfViewForLOD; |
||||
|
#if WITH_EDITORONLY_DATA |
||||
|
bCameraMeshHiddenInGame = CameraComponent->bCameraMeshHiddenInGame; |
||||
|
#endif |
||||
|
bLockToHmd = CameraComponent->bLockToHmd; |
||||
|
bUsePawnControlRotation = CameraComponent->bUsePawnControlRotation; |
||||
|
ProjectionMode = CameraComponent->ProjectionMode; |
||||
|
PostProcessBlendWeight = CameraComponent->PostProcessBlendWeight; |
||||
|
|
||||
|
ClearAdditiveOffset(); |
||||
|
FTransform OutAdditiveOffset; |
||||
|
float OutAdditiveFOVOffset; |
||||
|
CameraComponent->GetAdditiveOffset(OutAdditiveOffset, OutAdditiveFOVOffset); |
||||
|
AddAdditiveOffset(OutAdditiveOffset, OutAdditiveFOVOffset); |
||||
|
|
||||
|
ClearExtraPostProcessBlends(); |
||||
|
TArray<FPostProcessSettings> OutSettings; |
||||
|
TArray<float> OutWeights; |
||||
|
CameraComponent->GetExtraPostProcessBlends(OutSettings, OutWeights); |
||||
|
for (int i = 0; i < FMath::Min(OutSettings.Num(), OutWeights.Num()); ++i) |
||||
|
{ |
||||
|
AddExtraPostProcessBlend(OutSettings[i], OutWeights[i]); |
||||
|
} |
||||
|
PostProcessSettings = CameraComponent->PostProcessSettings; |
||||
|
|
||||
|
|
||||
|
// -- UCineCameraComponent
|
||||
|
if (const UCineCameraComponent* CineCameraComponent = Cast<UCineCameraComponent>(CameraComponent)) |
||||
|
{ |
||||
|
Filmback = CineCameraComponent->Filmback; |
||||
|
LensSettings = CineCameraComponent->LensSettings; |
||||
|
FocusSettings = CineCameraComponent->FocusSettings; |
||||
|
SetCurrentFocalLength(CineCameraComponent->CurrentFocalLength); |
||||
|
CurrentAperture = CineCameraComponent->CurrentAperture; |
||||
|
CurrentFocusDistance = CineCameraComponent->CurrentFocusDistance; |
||||
|
CustomNearClippingPlane = CineCameraComponent->CustomNearClippingPlane; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// -- UArchVisCineCameraComponent
|
||||
|
if (const UArchVisCineCameraComponent* ArchVisCineCameraComponent = Cast<UArchVisCineCameraComponent>(CameraComponent)) |
||||
|
{ |
||||
|
ProjectionOffset = ArchVisCineCameraComponent->ProjectionOffset; |
||||
|
bCorrectPerspective = ArchVisCineCameraComponent->bCorrectPerspective; |
||||
|
CorrectionStrength = ArchVisCineCameraComponent->CorrectionStrength; |
||||
|
MaxPitch = ArchVisCineCameraComponent->MaxPitch; |
||||
|
bAllowRoll = ArchVisCineCameraComponent->bAllowRoll; |
||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS |
||||
|
bAutomaticNearClipPlaneHandling = false; // deactivate the automatic near clipping plane handling first
|
||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void UArchVisCineCameraComponent::GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView) |
||||
|
{ |
||||
|
Super::GetCameraView(DeltaTime, DesiredView); |
||||
|
|
||||
|
if (!bAllowRoll) |
||||
|
{ |
||||
|
DesiredView.Rotation.Roll = 0; |
||||
|
} |
||||
|
DesiredView.OffCenterProjectionOffset += ProjectionOffset; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
void UArchVisCineCameraComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) |
||||
|
{ |
||||
|
HERE_D |
||||
|
const FName PropertyChangedName = PropertyChangedEvent.GetPropertyName(); |
||||
|
LOG_D("PostEditChangeProperty: [ %s ]", *PropertyChangedName.ToString()); |
||||
|
|
||||
|
Super::PostEditChangeProperty(PropertyChangedEvent); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
|
#undef U_ARCH_VIS_CINE_CAMERA_COMPONENT_DEBUG |
||||
@ -0,0 +1,61 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisMoviePipelineDeferredPass.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
#include "ArchVisSceneViewExtension.h" |
||||
|
#include "MoviePipeline.h" |
||||
|
|
||||
|
|
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
#define LOCTEXT_NAMESPACE "FArchVisToolsModule" |
||||
|
|
||||
|
|
||||
|
UE::MoviePipeline::FImagePassCameraViewData UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(UE::MoviePipeline::FImagePassCameraViewData CameraInfo, FMoviePipelineRenderPassMetrics& InOutSampleState, UMoviePipeline* Pipeline, int32 NumCamerasToRender) |
||||
|
{ |
||||
|
// here, the CameraInfo.ViewActor doesn't always return the right actor for "consistency", see UMoviePipelineDeferredPassBase::GetCameraInfo
|
||||
|
// so we need to check the SidecarCameraData if we have more than one camera
|
||||
|
UArchVisCineCameraComponent* ArchVisComponent; |
||||
|
|
||||
|
UMoviePipelineExecutorShot* CurrentShot = Pipeline->GetActiveShotList()[Pipeline->GetCurrentShotIndex()]; |
||||
|
if (NumCamerasToRender == 1) |
||||
|
{ |
||||
|
ArchVisComponent = CameraInfo.ViewActor ? CameraInfo.ViewActor->FindComponentByClass<UArchVisCineCameraComponent>() : nullptr; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FMinimalViewInfo SideCarViewInfo; |
||||
|
UCameraComponent* OutCamera = nullptr; |
||||
|
Pipeline->GetSidecarCameraData(CurrentShot, InOutSampleState.OutputState.CameraIndex, SideCarViewInfo, &OutCamera); |
||||
|
ArchVisComponent = Cast<UArchVisCineCameraComponent>(OutCamera); |
||||
|
} |
||||
|
|
||||
|
if (!ArchVisComponent) |
||||
|
{ |
||||
|
return CameraInfo; |
||||
|
} |
||||
|
|
||||
|
const UWorld* World = Pipeline->GetWorld(); |
||||
|
const FMinimalViewInfo ViewInfo = FArchVisSceneViewExtension::GetCorrectedViewInfo(World, ArchVisComponent, InOutSampleState.OverscannedResolution, CameraInfo.ViewInfo); |
||||
|
CameraInfo.ViewInfo = ViewInfo; |
||||
|
|
||||
|
return CameraInfo; |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(FSceneViewFamilyContext& InContext, FSceneInterface* Scene) |
||||
|
{ |
||||
|
// This part is a hack to disable the ArchVisScene. We want to be sure it is only disabled for MRQ, so we use this callback to remove it from the active extensions
|
||||
|
const FArchVisSceneViewExtensionContextDisableOnce Context {Scene}; |
||||
|
for (auto It = InContext.ViewExtensions.CreateIterator(); It; ++It ) |
||||
|
{ |
||||
|
if (!(*It)->IsActiveThisFrame(Context)) |
||||
|
{ |
||||
|
It.RemoveCurrent(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
|
#undef LOCTEXT_NAMESPACE |
||||
|
|
||||
@ -0,0 +1,17 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisSceneCapture2D.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
#include "ArchVisSceneCaptureComponent2D.h" |
||||
|
|
||||
|
|
||||
|
AArchVisSceneCapture2D::AArchVisSceneCapture2D(const FObjectInitializer& ObjectInitializer) |
||||
|
: Super(ObjectInitializer.SetDefaultSubobjectClass<UArchVisSceneCaptureComponent2D>(TEXT("NewSceneCaptureComponent2D"))) |
||||
|
{ |
||||
|
ArchVisCineCameraComponent = CreateDefaultSubobject<UArchVisCineCameraComponent>(TEXT("CameraComponent")); |
||||
|
ArchVisCineCameraComponent->SetupAttachment(RootComponent); |
||||
|
|
||||
|
ArchVisSceneCaptureComponent2D = Cast<UArchVisSceneCaptureComponent2D>(GetCaptureComponent2D()); |
||||
|
ArchVisSceneCaptureComponent2D->ArchVisCineCameraComponent = ArchVisCineCameraComponent; |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisSceneCaptureComponent2D.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraActor.h" |
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
|
||||
|
|
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
|
||||
|
const AActor* UArchVisSceneCaptureComponent2D::GetViewOwner() const |
||||
|
{ |
||||
|
return GetOwner(); // Needed for the View Extension to find the ArchVisCineCameraComponent
|
||||
|
} |
||||
|
|
||||
|
void UArchVisSceneCaptureComponent2D::GetCameraView(float DeltaTime, FMinimalViewInfo& OutMinimalViewInfo) |
||||
|
{ |
||||
|
if (UArchVisCineCameraComponent* CameraComponent = FindCameraComponent()) |
||||
|
{ |
||||
|
CameraComponent->GetCameraView(DeltaTime, OutMinimalViewInfo); |
||||
|
SetCameraView(OutMinimalViewInfo); //to ensure its location matches the given view
|
||||
|
return; |
||||
|
} |
||||
|
Super::GetCameraView(DeltaTime, OutMinimalViewInfo); |
||||
|
} |
||||
|
|
||||
|
UArchVisCineCameraComponent* UArchVisSceneCaptureComponent2D::FindCameraComponent() const |
||||
|
{ |
||||
|
UArchVisCineCameraComponent* CameraComponent = nullptr; |
||||
|
if (ArchVisCineCameraComponent.IsValid()) |
||||
|
{ |
||||
|
CameraComponent = ArchVisCineCameraComponent.Get(); |
||||
|
} |
||||
|
else if (const AArchVisCineCameraActor* CameraActor = ArchVisCineCameraActor.Get()) |
||||
|
{ |
||||
|
CameraComponent = CameraActor->GetArchVisCineCameraComponent(); |
||||
|
} |
||||
|
else if (UArchVisCineCameraComponent* Parent = Cast<UArchVisCineCameraComponent>(GetAttachParent())) |
||||
|
{ |
||||
|
CameraComponent = Parent; |
||||
|
} |
||||
|
else if (const AArchVisCineCameraActor* Owner = Cast<AArchVisCineCameraActor>(GetOwner())) |
||||
|
{ |
||||
|
CameraComponent = Owner->GetArchVisCineCameraComponent(); |
||||
|
} |
||||
|
return CameraComponent; |
||||
|
} |
||||
|
|
||||
|
void UArchVisSceneCaptureComponent2D::UpdateSceneCaptureContents(FSceneInterface* Scene, ISceneRenderBuilder& SceneRenderBuilder) |
||||
|
{ |
||||
|
bUseCustomProjectionMatrix = false; // need to ensure this is reset to false as we forced it to be true previously
|
||||
|
FMinimalViewInfo ViewInfo; |
||||
|
GetCameraView(0.0f, ViewInfo); |
||||
|
|
||||
|
if (const UArchVisCineCameraComponent* CameraComponent = FindCameraComponent()) |
||||
|
{ |
||||
|
CustomNearClippingPlane = CameraComponent->CustomNearClippingPlane; |
||||
|
} |
||||
|
|
||||
|
Super::UpdateSceneCaptureContents(Scene, SceneRenderBuilder); |
||||
|
} |
||||
|
|
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
@ -0,0 +1,230 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisSceneViewExtension.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
#include "ArchVisSubsystem.h" |
||||
|
#include "ArchVisToolsDefinitions.h" |
||||
|
#include "SceneView.h" |
||||
|
#include "Engine/LocalPlayer.h" |
||||
|
#include "GameFramework/PlayerController.h" |
||||
|
|
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
#include "LevelEditorViewport.h" |
||||
|
#endif |
||||
|
|
||||
|
FArchVisSceneViewExtension::FArchVisSceneViewExtension(const FAutoRegister& AutoRegister, UArchVisSubsystem* InArchVisSubsystem) : |
||||
|
FSceneViewExtensionBase(AutoRegister), ArchVisSubsystem(InArchVisSubsystem) |
||||
|
{ |
||||
|
UE_LOG(LogArchVisTools, Log, TEXT("FArchVisSceneViewExtension: Custom SceneViewExtension registered")); |
||||
|
} |
||||
|
|
||||
|
void FArchVisSceneViewExtension::SetupViewFamily(FSceneViewFamily& InViewFamily) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void FArchVisSceneViewExtension::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) |
||||
|
{ |
||||
|
UArchVisCineCameraComponent* ArchVisComponent = InView.ViewActor ? InView.ViewActor->FindComponentByClass<UArchVisCineCameraComponent>() : nullptr; |
||||
|
#if WITH_EDITOR |
||||
|
if (!IsValid(ArchVisComponent)) |
||||
|
{ |
||||
|
if (const FLevelEditorViewportClient* ViewportClient = static_cast<FLevelEditorViewportClient*>(InView.SceneViewInitOptions.ViewElementDrawer)) |
||||
|
{ |
||||
|
ArchVisComponent = Cast<UArchVisCineCameraComponent>(ViewportClient->GetCameraComponentForView()); |
||||
|
} |
||||
|
} |
||||
|
#endif |
||||
|
if (!IsValid(ArchVisComponent)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
InView.PreviousViewTransform.Reset(); |
||||
|
|
||||
|
const UWorld* World = InViewFamily.Scene ? InViewFamily.Scene->GetWorld() : nullptr; |
||||
|
FMinimalViewInfo CurrentPOV; |
||||
|
CurrentPOV.Location = InView.ViewLocation; |
||||
|
CurrentPOV.Rotation = InView.ViewRotation; |
||||
|
CurrentPOV.FOV = InView.FOV; |
||||
|
FMinimalViewInfo ViewInfo = GetCorrectedViewInfo(World, ArchVisComponent, InView.UnconstrainedViewRect.Size(), CurrentPOV); |
||||
|
|
||||
|
// as per ULocalPlayer::GetProjectionData and UMoviePipelineImagePassBase::GetSceneViewForSampleState
|
||||
|
FSceneViewProjectionData& ProjectionData = InView.SceneViewInitOptions; |
||||
|
ProjectionData.ViewOrigin = ViewInfo.Location; |
||||
|
ProjectionData.ViewRotationMatrix = FInverseRotationMatrix(ViewInfo.Rotation) * FMatrix( |
||||
|
FPlane(0, 0, 1, 0), |
||||
|
FPlane(1, 0, 0, 0), |
||||
|
FPlane(0, 1, 0, 0), |
||||
|
FPlane(0, 0, 0, 1)); |
||||
|
|
||||
|
const FIntRect ConstrainedViewRect = CalculateViewExtents(ViewInfo.AspectRatio, InView.SceneViewInitOptions.GetViewRect()); //this only affects the ProjectionData which is InView.SceneViewInitOptions
|
||||
|
FMinimalViewInfo::CalculateProjectionMatrixGivenViewRectangle(ViewInfo, ViewInfo.AspectRatioAxisConstraint.Get(AspectRatio_MaintainXFOV), ConstrainedViewRect, ProjectionData); |
||||
|
|
||||
|
InView.UpdateProjectionMatrix(ProjectionData.ProjectionMatrix); |
||||
|
} |
||||
|
|
||||
|
FMinimalViewInfo FArchVisSceneViewExtension::GetCorrectedViewInfo(const UWorld* World, UArchVisCineCameraComponent* ArchVisComponent, const FIntPoint& UnconstrainedViewSize, const FMinimalViewInfo& CurrentViewInfo) |
||||
|
{ |
||||
|
if (!ArchVisComponent) |
||||
|
{ |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
// --- Then we find the cached ViewInfo that we will adjust to regenerate the Projection Matrix;
|
||||
|
FMinimalViewInfo ViewInfo; |
||||
|
ArchVisComponent->GetCameraView(0.f, ViewInfo); |
||||
|
// The current issue is that a Camera Shake applied via the camera manager has been baked in the CurrentViewInfo in APlayerCameraManager::ApplyCameraModifiers and UCameraModifier_CameraShake::ModifyCamera on Tick
|
||||
|
// So to keep the adjustments made by the camera shakes, we reuse the position, rotation and FOV of the CurrentViewInfo, inspired by UE::MovieScene::FEvaluateCameraShake::EvaluateCameraComponentShake
|
||||
|
ViewInfo.Location = CurrentViewInfo.Location; |
||||
|
ViewInfo.Rotation = CurrentViewInfo.Rotation; |
||||
|
ViewInfo.FOV = CurrentViewInfo.FOV; |
||||
|
|
||||
|
// We fix the aspect ratio constraint
|
||||
|
if (!ViewInfo.bConstrainAspectRatio) |
||||
|
{ |
||||
|
#if WITH_EDITOR |
||||
|
if (!ViewInfo.AspectRatioAxisConstraint.IsSet()) |
||||
|
{ |
||||
|
ViewInfo.AspectRatioAxisConstraint = GetDefault<ULevelEditorViewportSettings>()->AspectRatioAxisConstraint; |
||||
|
} |
||||
|
#endif |
||||
|
if (!ViewInfo.AspectRatioAxisConstraint.IsSet() || ViewInfo.AspectRatioAxisConstraint == EAspectRatioAxisConstraint::AspectRatio_MajorAxisFOV) |
||||
|
{ |
||||
|
ViewInfo.AspectRatioAxisConstraint = UnconstrainedViewSize.X >= UnconstrainedViewSize.Y ? AspectRatio_MaintainXFOV : AspectRatio_MaintainYFOV; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
float UnconstrainedAspectRatio = static_cast<float>(UnconstrainedViewSize.X) / static_cast<float>(UnconstrainedViewSize.Y); |
||||
|
float ViewportAspectRatio = ViewInfo.bConstrainAspectRatio ? ViewInfo.AspectRatio : UnconstrainedAspectRatio; |
||||
|
|
||||
|
// At this point, InView.ViewRotation contains the already adjusted rotation, and ViewInfo.Rotation the original View rotation
|
||||
|
// --- We are now ready to compute the automatic correction
|
||||
|
if (ArchVisComponent->ProjectionMode == ECameraProjectionMode::Perspective && ArchVisComponent->bCorrectPerspective) |
||||
|
{ |
||||
|
float HorFov = ArchVisComponent->GetHorizontalFieldOfView(); // The Angle of the Horizontal FOV for the Constrained Viewport
|
||||
|
float VertFov = ArchVisComponent->GetVerticalFieldOfView(); // The Angle of the Vertical FOV for the Constrained Viewport
|
||||
|
|
||||
|
float Camera_Width, Camera_Height; |
||||
|
if (ViewportAspectRatio > UnconstrainedAspectRatio || |
||||
|
(ViewportAspectRatio == UnconstrainedAspectRatio && ViewInfo.AspectRatioAxisConstraint.Get(AspectRatio_MaintainXFOV) == AspectRatio_MaintainXFOV)) |
||||
|
{ |
||||
|
// because the FOV returned by GetHorizontalFieldOfView is the FOV of the unconstrained viewport, we can use it to find the height of the viewport
|
||||
|
// only when the constrained viewport would have the same width as the unconstrained viewport (black bands top and bottom)
|
||||
|
Camera_Width = FMath::Tan(FMath::DegreesToRadians(HorFov) / 2) * 2; |
||||
|
Camera_Height = Camera_Width / ViewportAspectRatio; // (bIsMRQWorld ? UnconstrainedAspectRatio : ViewportAspectRatio);
|
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// because the FOV returned by GetVerticalFieldOfView is the FOV of the unconstrained viewport, we can use it to find the width of the viewport
|
||||
|
// only when the constrained viewport would have the same height as the unconstrained viewport (black bands left and right)
|
||||
|
Camera_Height = FMath::Tan(FMath::DegreesToRadians(VertFov) / 2) * 2; |
||||
|
Camera_Width = Camera_Height * ViewportAspectRatio; //(bIsMRQWorld ? UnconstrainedAspectRatio : ViewportAspectRatio);;
|
||||
|
} |
||||
|
|
||||
|
float MaxPitch = FMath::Clamp(FMath::Abs(ArchVisComponent->MaxPitch), 0.0F, 89.99F); |
||||
|
float Target_Pitch = FMath::DegreesToRadians(FMath::Clamp(ViewInfo.Rotation.Pitch, -MaxPitch, MaxPitch)); //ensure the pitch is not 90 degrees
|
||||
|
float Final_Camera_Angle = Target_Pitch * (1 - ArchVisComponent->CorrectionStrength); // apply the strength to get the real angle of the final camera
|
||||
|
float Vert_Dist_Final_Camera = FMath::Tan(Final_Camera_Angle); // *hor_dist; // vertical distance of the final camera rotation
|
||||
|
|
||||
|
// float Camera_Width = FMath::Tan(FMath::DegreesToRadians(HorFov) / 2) * 2;
|
||||
|
// float Camera_Height = FMath::Tan(FMath::DegreesToRadians(VertFov) / 2) * 2;
|
||||
|
// float CameraAspectRatio = Camera_Width / Camera_Height;
|
||||
|
// float Camera_Height = Camera_Width / ViewportAspectRatio;
|
||||
|
|
||||
|
//TODO: need to handle the constraint type
|
||||
|
float Screen_Height = Camera_Height; // ViewInfo.bConstrainAspectRatio ? Camera_Height : Camera_Width / ViewportAspectRatio;
|
||||
|
float Vert_Dist_To_Move = FMath::Tan(Target_Pitch - Final_Camera_Angle); // *hor_dist; // distance we should move vertically to see the same point
|
||||
|
float Ratio_To_Move = Vert_Dist_To_Move / Screen_Height * 2; // the ratio of the screen we will need to offset. A *2 is applied as a projection offset of 1.0 moves the projection half
|
||||
|
ViewInfo.Rotation.Pitch = FMath::RadiansToDegrees(Final_Camera_Angle); |
||||
|
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("ViewportAspectRatio: %f"), ViewportAspectRatio)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("HorFov: %f"), HorFov)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("VertFov: %f"), VertFov)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Target_Pitch: %f"), Target_Pitch)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Final_Camera_Angle: %f"), Final_Camera_Angle)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Vert_Dist_Final_Camera: %f"), Vert_Dist_Final_Camera)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Camera_Width: %f"), Camera_Width)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Camera_Height: %f"), Camera_Height)
|
||||
|
// // UE_LOG(LogTemp, Log, TEXT("CameraAspectRatio: %f"), CameraAspectRatio)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Screen_Height: %f"), Screen_Height)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Vert_Dist_To_Move: %f"), Vert_Dist_To_Move)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Ratio_To_Move: %f"), Ratio_To_Move)
|
||||
|
// UE_LOG(LogTemp, Log, TEXT("Pitch: %f"), ViewInfo.Rotation.Pitch)
|
||||
|
|
||||
|
FVector2D FinalProjectionOffset; |
||||
|
if (ArchVisComponent->bAllowRoll && ViewInfo.Rotation.Roll != 0.0f) |
||||
|
{ |
||||
|
// FinalProjectionOffset = (FVector2D(0, Ratio_To_Move) + ViewInfo.OffCenterProjectionOffset).GetRotated(ViewInfo.Rotation.Roll);
|
||||
|
// FinalProjectionOffset.X /= ViewportAspectRatio;
|
||||
|
FinalProjectionOffset = FVector2D(0, Ratio_To_Move).GetRotated(ViewInfo.Rotation.Roll); |
||||
|
if (!UArchVisSubsystem::IsMoviePipelineWorld(World)) |
||||
|
{ |
||||
|
FinalProjectionOffset.X /= ViewportAspectRatio; //to compensate as an offset of 1 moves by one half screen, 1 horizontal and 1 vertical are not the same
|
||||
|
// This is not the case in Movie Pipeline as it works differently
|
||||
|
} |
||||
|
FinalProjectionOffset += ViewInfo.OffCenterProjectionOffset; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FinalProjectionOffset = FVector2D(0, Ratio_To_Move) + ViewInfo.OffCenterProjectionOffset; |
||||
|
} |
||||
|
ViewInfo.OffCenterProjectionOffset = FinalProjectionOffset; |
||||
|
} |
||||
|
|
||||
|
if (!ArchVisComponent->bAllowRoll) |
||||
|
{ |
||||
|
ViewInfo.Rotation.Roll = 0; |
||||
|
} |
||||
|
|
||||
|
return ViewInfo; |
||||
|
} |
||||
|
|
||||
|
FIntRect FArchVisSceneViewExtension::CalculateViewExtents(float AspectRatio, const FIntRect& ViewRect) |
||||
|
{ |
||||
|
// Adjustment of FViewport::CalculateViewExtents to remove the need from the FViewport
|
||||
|
|
||||
|
FIntRect Result = ViewRect; |
||||
|
|
||||
|
const float CurrentSizeX = ViewRect.Width(); |
||||
|
const float CurrentSizeY = ViewRect.Height(); |
||||
|
const float DesiredAspectRatio = CurrentSizeX / CurrentSizeY; |
||||
|
|
||||
|
// the viewport's SizeX/SizeY may not always match the GetDesiredAspectRatio(), so adjust the requested AspectRatio to compensate
|
||||
|
const float AdjustedAspectRatio = AspectRatio; // / (GetDesiredAspectRatio() / ((float)GetSizeXY().X / (float)GetSizeXY().Y));
|
||||
|
|
||||
|
// If desired, enforce a particular aspect ratio for the render of the scene.
|
||||
|
// Results in black bars at top/bottom etc.
|
||||
|
const float AspectRatioDifference = AdjustedAspectRatio - (CurrentSizeX / CurrentSizeY); |
||||
|
|
||||
|
if( FMath::Abs( AspectRatioDifference ) > 0.01f ) |
||||
|
{ |
||||
|
// If desired aspect ratio is bigger than current - we need black bars on top and bottom.
|
||||
|
if( AspectRatioDifference > 0.0f ) |
||||
|
{ |
||||
|
// Calculate desired Y size.
|
||||
|
const int32 NewSizeY = FMath::Max(1, FMath::RoundToInt( CurrentSizeX / AdjustedAspectRatio ) ); |
||||
|
Result.Min.Y = FMath::RoundToInt( 0.5f * (CurrentSizeY - NewSizeY) ); |
||||
|
Result.Max.Y = Result.Min.Y + NewSizeY; |
||||
|
Result.Min.Y += ViewRect.Min.Y; |
||||
|
Result.Max.Y += ViewRect.Min.Y; |
||||
|
} |
||||
|
// Otherwise - will place bars on the sides.
|
||||
|
else |
||||
|
{ |
||||
|
const int32 NewSizeX = FMath::Max(1, FMath::RoundToInt( CurrentSizeY * AdjustedAspectRatio ) ); |
||||
|
Result.Min.X = FMath::RoundToInt( 0.5f * (CurrentSizeX - NewSizeX) ); |
||||
|
Result.Max.X = Result.Min.X + NewSizeX; |
||||
|
Result.Min.X += ViewRect.Min.X; |
||||
|
Result.Max.X += ViewRect.Min.X; |
||||
|
} |
||||
|
} |
||||
|
return Result; |
||||
|
} |
||||
|
|
||||
|
bool FArchVisSceneViewExtension::IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const |
||||
|
{ |
||||
|
// Always enabled unless specifically requested via UArchVisMoviePipelineDeferredPass::AddViewExtensions
|
||||
|
return !Context.IsA(FArchVisSceneViewExtensionContextDisableOnce{}); |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisSubsystem.h" |
||||
|
|
||||
|
#include "ArchVisSceneViewExtension.h" |
||||
|
#include "MoviePipelineBase.h" |
||||
|
#include "SceneViewExtension.h" |
||||
|
|
||||
|
|
||||
|
void UArchVisSubsystem::Initialize(FSubsystemCollectionBase& Collection) |
||||
|
{ |
||||
|
ArchVisSceneViewExtension = FSceneViewExtensions::NewExtension<FArchVisSceneViewExtension>(this); |
||||
|
UE_LOG(LogTemp, Log, TEXT("SceneViewExtensionTemplate: Subsystem initialized & SceneViewExtension created")); |
||||
|
} |
||||
|
|
||||
|
void UArchVisSubsystem::Deinitialize() |
||||
|
{ |
||||
|
{ |
||||
|
ArchVisSceneViewExtension->IsActiveThisFrameFunctions.Empty(); |
||||
|
|
||||
|
FSceneViewExtensionIsActiveFunctor IsActiveFunctor; |
||||
|
|
||||
|
IsActiveFunctor.IsActiveFunction = [](const ISceneViewExtension* SceneViewExtension, const FSceneViewExtensionContext& Context) |
||||
|
{ |
||||
|
return TOptional<bool>(false); |
||||
|
}; |
||||
|
|
||||
|
ArchVisSceneViewExtension->IsActiveThisFrameFunctions.Add(IsActiveFunctor); |
||||
|
} |
||||
|
|
||||
|
ArchVisSceneViewExtension.Reset(); |
||||
|
ArchVisSceneViewExtension = nullptr; |
||||
|
} |
||||
|
|
||||
|
bool UArchVisSubsystem::IsMoviePipelineWorld(const UWorld* World) |
||||
|
{ |
||||
|
if (!IsValid(World)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
//todo: if this does not work, check UWorld::PerModuleDataObjects and set it in OnMoviePipelineInitialized
|
||||
|
bool bHasMoviePipelineBase = false; |
||||
|
ForEachObjectWithOuterBreakable(World, [&bHasMoviePipelineBase](UObject* Object) |
||||
|
{ |
||||
|
bHasMoviePipelineBase = Object->IsA<UMoviePipelineBase>(); |
||||
|
return !bHasMoviePipelineBase; // break if we found it
|
||||
|
}, false); |
||||
|
return bHasMoviePipelineBase; |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Subsystems/EngineSubsystem.h" |
||||
|
#include "ArchVisSubsystem.generated.h" |
||||
|
|
||||
|
|
||||
|
UCLASS() |
||||
|
class UArchVisSubsystem : public UEngineSubsystem |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
|
||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override; |
||||
|
virtual void Deinitialize() override; |
||||
|
|
||||
|
/**
|
||||
|
* Check if the given world is a Movie Pipeline World by checking its GameMode |
||||
|
*/ |
||||
|
static bool IsMoviePipelineWorld(const UWorld* World); |
||||
|
private: |
||||
|
TSharedPtr<class FArchVisSceneViewExtension, ESPMode::ThreadSafe> ArchVisSceneViewExtension; |
||||
|
}; |
||||
@ -0,0 +1,28 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisTools.h" |
||||
|
|
||||
|
#include "ArchVisToolsDefinitions.h" |
||||
|
#include "Modules/ModuleManager.h" |
||||
|
|
||||
|
|
||||
|
#define LOCTEXT_NAMESPACE "FArchVisToolsModule" |
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
|
||||
|
void FArchVisToolsModule::StartupModule() |
||||
|
{ |
||||
|
HERE; |
||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
|
} |
||||
|
|
||||
|
void FArchVisToolsModule::ShutdownModule() |
||||
|
{ |
||||
|
HERE; |
||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
|
// we call this function before unloading the module.
|
||||
|
} |
||||
|
|
||||
|
#undef LOCTEXT_NAMESPACE |
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
|
|
||||
|
IMPLEMENT_MODULE(FArchVisToolsModule, ArchVisTools) |
||||
@ -0,0 +1,6 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisToolsDefinitions.h" |
||||
|
|
||||
|
|
||||
|
DEFINE_LOG_CATEGORY(LogArchVisTools); |
||||
@ -0,0 +1,68 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "FaceCameraComponent.h" |
||||
|
|
||||
|
#include "Engine/World.h" |
||||
|
#include "Kismet/GameplayStatics.h" |
||||
|
#include "Kismet/KismetMathLibrary.h" |
||||
|
|
||||
|
|
||||
|
// Sets default values for this component's properties
|
||||
|
UFaceCameraComponent::UFaceCameraComponent() |
||||
|
{ |
||||
|
PrimaryComponentTick.bCanEverTick = true; |
||||
|
bTickInEditor = true; |
||||
|
bAutoActivate = true; |
||||
|
PrimaryComponentTick.bStartWithTickEnabled = true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void UFaceCameraComponent::MakeActorFaceCamera() |
||||
|
{ |
||||
|
#if WITH_EDITOR |
||||
|
if (GetWorld() != nullptr && (GetWorld()->WorldType == EWorldType::Editor || GetWorld()->WorldType == EWorldType::EditorPreview)) |
||||
|
{ |
||||
|
if (AActor* Owner = GetOwner()) |
||||
|
{ |
||||
|
const FRotator Rotation = UKismetMathLibrary::FindLookAtRotation(GetEditorCameraLocation(), Owner->GetActorLocation()); |
||||
|
Owner->SetActorRotation(FRotator(0.f, Rotation.Yaw, bAllowRoll ? Rotation.Pitch : 0.f) + AddedRotation); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
#endif |
||||
|
if (AActor* Owner = GetOwner()) |
||||
|
{ |
||||
|
if (const APlayerCameraManager* Manager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0)) |
||||
|
{ |
||||
|
const FRotator Rotation = UKismetMathLibrary::FindLookAtRotation(Manager->GetCameraLocation(), Owner->GetActorLocation()); |
||||
|
Owner->SetActorRotation(FRotator(0.f, Rotation.Yaw, bAllowRoll ? Rotation.Pitch : 0.f) + AddedRotation); |
||||
|
} |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
}; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
// Called every frame
|
||||
|
void UFaceCameraComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) |
||||
|
{ |
||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction); |
||||
|
|
||||
|
if (bFaceCameraActive) |
||||
|
{ |
||||
|
MakeActorFaceCamera(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
FVector UFaceCameraComponent::GetEditorCameraLocation() const |
||||
|
{ |
||||
|
if (const UWorld* World = GetWorld()) |
||||
|
{ |
||||
|
if (World->ViewLocationsRenderedLastFrame.Num() > 0) |
||||
|
{ |
||||
|
return World->ViewLocationsRenderedLastFrame[0]; |
||||
|
} |
||||
|
} |
||||
|
return FVector(); |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "MovieGraphArchVisDeferredPass.h" |
||||
|
|
||||
|
|
||||
|
void FMovieGraphArchVisDeferredPass::Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) |
||||
|
{ |
||||
|
FMovieGraphDeferredPass::Render(InFrameTraversalContext, InTimeData); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void FMovieGraphArchVisPathTracerPass::Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) |
||||
|
{ |
||||
|
FMovieGraphDeferredPass::Render(InFrameTraversalContext, InTimeData); |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "MovieGraphArchVisDeferredRenderPassNode.h" |
||||
|
|
||||
|
#include "MovieGraphArchVisDeferredPass.h" |
||||
|
|
||||
|
|
||||
|
#define LOCTEXT_NAMESPACE "FArchVisToolsModule" |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
FText UMovieGraphArchVisDeferredRenderPassNode::GetNodeTitle(const bool bGetDescriptive) const |
||||
|
{ |
||||
|
return LOCTEXT("ArchVisDeferredRenderPassGraphNode_Description", "[ArchVis] Deferred Renderer"); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
FString UMovieGraphArchVisDeferredRenderPassNode::GetRendererNameImpl() const |
||||
|
{ |
||||
|
static const FString RendererNameImpl(TEXT("[ArchVis] Deferred")); |
||||
|
return RendererNameImpl; |
||||
|
} |
||||
|
|
||||
|
TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> UMovieGraphArchVisDeferredRenderPassNode::CreateInstance() const |
||||
|
{ |
||||
|
return MakeUnique<FMovieGraphArchVisDeferredPass>(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
FText UMovieGraphArchVisPathTracerRenderPassNode::GetNodeTitle(const bool bGetDescriptive) const |
||||
|
{ |
||||
|
return LOCTEXT("ArchVisPathTracedRenderPassGraphNode_Description", "[ArchVis] Path Traced Renderer"); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
FString UMovieGraphArchVisPathTracerRenderPassNode::GetRendererNameImpl() const |
||||
|
{ |
||||
|
static const FString RendererNameImpl(TEXT("[ArchVis] PathTraced")); |
||||
|
return RendererNameImpl; |
||||
|
} |
||||
|
|
||||
|
TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> UMovieGraphArchVisPathTracerRenderPassNode::CreateInstance() const |
||||
|
{ |
||||
|
return MakeUnique<FMovieGraphArchVisPathTracerPass>(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#undef LOCTEXT_NAMESPACE |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "CineCameraActor.h" |
||||
|
#include "ArchVisCineCameraActor.generated.h" |
||||
|
|
||||
|
/**
|
||||
|
* An ArchVisCineCameraActor is an advanced CineCameraActor with additional controls regarding projection (like 2-Point Perspective) and NearClipPlane override |
||||
|
*/ |
||||
|
UCLASS(ClassGroup = Common, hideCategories = (Input, Rendering, AutoPlayerActivation), meta = (DisplayName = "ArchVis Cine Camera"), showcategories = ("Input|MouseInput", "Input|TouchInput"), Blueprintable) |
||||
|
class ARCHVISTOOLS_API AArchVisCineCameraActor : public ACineCameraActor |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
AArchVisCineCameraActor(const FObjectInitializer& ObjectInitializer); |
||||
|
|
||||
|
/** Returns the ArchVisCineCameraComponent of this ArchVisCineCamera */ |
||||
|
UFUNCTION(BlueprintCallable, Category = "Camera") |
||||
|
class UArchVisCineCameraComponent* GetArchVisCineCameraComponent() const { return ArchVisCineCameraComponent; } |
||||
|
|
||||
|
private: |
||||
|
/** Returns the ArchVisCineCameraComponent of this ArchVisCineCamera */ |
||||
|
UPROPERTY() |
||||
|
class UArchVisCineCameraComponent* ArchVisCineCameraComponent; |
||||
|
}; |
||||
@ -0,0 +1,103 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "CineCameraComponent.h" |
||||
|
#include "ArchVisCineCameraComponent.generated.h" |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(HideCategories = (CameraSettings), HideFunctions = (SetFieldOfView, SetAspectRatio), Blueprintable, ClassGroup = Camera, meta = (BlueprintSpawnableComponent, DisplayName = "ArchVis Cine Camera Component"), Config = Engine) |
||||
|
class ARCHVISTOOLS_API UArchVisCineCameraComponent : public UCineCameraComponent |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
/** Set to True to activate a 2-Point Perspective mode which corrects the vertical lines to ensure they are actually vertical. The strength of the correction can be adjusted. */ |
||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "ArchVis Cine Camera Settings", meta = (DisplayName = "Correct Perspective (Auto)", InlineEditConditionToggle)) |
||||
|
bool bCorrectPerspective = true; |
||||
|
|
||||
|
/** The Strength of the correction. Set to 0.0 to disable the correction and to 1.0 for it to be full strength. */ |
||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "ArchVis Cine Camera Settings", |
||||
|
meta = (ClampMin = 0.00, EditCondition = "bCorrectPerspective", UIMax = 1.00, UIMin = 0.00)) |
||||
|
float CorrectionStrength = 1.0F; |
||||
|
|
||||
|
/** The Projection Offset of the Camera. Allows viewing beyond the screen while keeping the same projection and vanishing points. */ |
||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Interp, Category = "ArchVis Cine Camera Settings", meta = (DisplayName = "Manual Correction (Shift)")) |
||||
|
FVector2D ProjectionOffset; |
||||
|
|
||||
|
/** The final Projection Offset computed internally */ |
||||
|
UPROPERTY(meta = (DeprecatedProperty)) |
||||
|
FVector2D FinalProjectionOffset; |
||||
|
|
||||
|
/** Set to True to allow the camera to roll */ |
||||
|
UPROPERTY(AdvancedDisplay, BlueprintReadWrite, EditAnywhere, Interp, Category = "ArchVis Cine Camera Settings") |
||||
|
bool bAllowRoll = false; |
||||
|
|
||||
|
/** The Maximum Pitch of the Camera. The camera will not rotate beyond this angle. Need to be less than 90. */ |
||||
|
UPROPERTY(AdvancedDisplay, BlueprintReadWrite, EditAnywhere, Interp, Category = "ArchVis Cine Camera Settings", meta = (ClampMax = 89.99, ClampMin = 0.00, UIMax = 89.50, UIMin = 0.00)) |
||||
|
float MaxPitch = 85.0F; |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* [IN 5.1, use `Custom Near Clippling Plane` instead, but you can also animate this property] |
||||
|
* Allows the overriding of the Near Clipping Plane if the value is bigger than 0. To handle it automatically, you need to set bAutomaticNearClipPlaneHandling to true. |
||||
|
* Outside of the Movie Render Queue, the Near Clip Plane default settings is set in the Project Settings > Engine - General Settings > Settings > Near Clip Plane and |
||||
|
* can also be set with the command r.SetNearClipPlane |
||||
|
* If `bAutomaticNearClipPlaneHandling` is set to false, it will only change the value of `OverrideNearClipPlane` but not actually set the Clipping Plane. |
||||
|
*/ |
||||
|
UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "Use `Custom Near Clippling Plane` instead")) |
||||
|
float OverrideNearClipPlane = -1.0; |
||||
|
|
||||
|
UE_DEPRECATED(5.1, "Cinematic Cameras now have a property `Custom Near Clippling Plane` or you can set `OverrideNearClipPlane` directly.") |
||||
|
/**
|
||||
|
* [DEPRECATED IN 5.1, you can set `OverrideNearClipPlane` directly] |
||||
|
* If set to true, the Global Near Clipping Plane will automatically try to adjust to the value of `OverrideNearClipPlane`. If set to false, the component will not alter the Global Near Clipping Plane even if OverrideNearClipPlane is set |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
bool bAutomaticNearClipPlaneHandling = false; |
||||
|
|
||||
|
|
||||
|
UE_DEPRECATED(4.27, "Only use for internal Debugging. You can achieve a similar outcome by setting `bCorrectPerspective` instead. ") |
||||
|
/** Circuit Breaker used for debugging. Set to False to force disable the perspective correction. Will be Deprecated in the future */ |
||||
|
UPROPERTY(meta=(DeprecatedProperty, DeprecationMessage="Only use for internal Debugging. You can achieve a similar outcome by setting `bCorrectPerspective` instead. ")) |
||||
|
bool bGetCorrectedView = true; |
||||
|
|
||||
|
UE_DEPRECATED(5.2, "Not used anymore.") |
||||
|
/**
|
||||
|
* If true, will use `CameraComponent::AddAdditiveOffset` to handle the projection. |
||||
|
* This is the solution that works best, especially if trying to pilot the camera in the editor, but needs to be turned off if AdditiveOffset is used (when using Camera Shakes for example). |
||||
|
* In case of issue or conflicting use of the AdditiveOffset, set to false and let me know. |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
bool bUseAdditiveOffset = true; |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Initialise this Component to align with the given CameraComponent or CineCameraComponent |
||||
|
* @param CameraComponent the CameraComponent or CineCameraComponent to copy properties from |
||||
|
* @param bCopyTransform If set to true, will copy the world transform of the given CameraComponent and apply it to this Component |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category = "ArchVis Cine Camera Settings") |
||||
|
void InitFromCameraComponent(const UCameraComponent* CameraComponent, bool bCopyTransform = false); |
||||
|
|
||||
|
#if WITH_EDITORONLY_DATA |
||||
|
UE_DEPRECATED(5.2, "Not used anymore.") |
||||
|
/** Draw Debug Lines to understand how the Correction is computed */ |
||||
|
UPROPERTY() |
||||
|
bool bDrawDebugLines = false; |
||||
|
#endif |
||||
|
|
||||
|
/**
|
||||
|
* @brief Gets the MinimalViewInfo of the current view as per the current settings. Will call GetCameraViewAndClipPlaneCorrected or GetCameraViewAndClipPlaneUncorrected |
||||
|
* @param DeltaTime Time used for some interpolation function within UCineCameraComponent |
||||
|
* @param DesiredView Returns the camera's Point of View. |
||||
|
*/ |
||||
|
virtual void GetCameraView(float DeltaTime, FMinimalViewInfo& DesiredView) override; |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; |
||||
|
|
||||
|
#endif |
||||
|
}; |
||||
@ -0,0 +1,194 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "MoviePipeline.h" |
||||
|
#include "MoviePipelineDeferredPasses.h" |
||||
|
#include "ArchVisMoviePipelineDeferredPass.generated.h" |
||||
|
|
||||
|
|
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass : public UMoviePipelineDeferredPassBase |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass() : UMoviePipelineDeferredPassBase() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("FinalImage[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Deferred Rendering"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
|
||||
|
public: |
||||
|
static UE::MoviePipeline::FImagePassCameraViewData GetArchVisCameraInfo(UE::MoviePipeline::FImagePassCameraViewData CameraInfo, FMoviePipelineRenderPassMetrics& InOutSampleState, UMoviePipeline* Pipeline, int32 NumCamerasToRender); |
||||
|
static void RemoveArchVisViewExtension(FSceneViewFamilyContext& InContext, FSceneInterface* Scene); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass_Unlit : public UMoviePipelineDeferredPass_Unlit |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass_Unlit() : UMoviePipelineDeferredPass_Unlit() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("Unlit[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Deferred Rendering (Unlit)"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass_DetailLighting : public UMoviePipelineDeferredPass_DetailLighting |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass_DetailLighting() : UMoviePipelineDeferredPass_DetailLighting() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("DetailLightingOnly[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Deferred Rendering (Detail Lighting)"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass_LightingOnly : public UMoviePipelineDeferredPass_LightingOnly |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass_LightingOnly() : UMoviePipelineDeferredPass_LightingOnly() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("LightingOnly[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Deferred Rendering (Lighting Only)"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass_ReflectionsOnly : public UMoviePipelineDeferredPass_ReflectionsOnly |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass_ReflectionsOnly() : UMoviePipelineDeferredPass_ReflectionsOnly() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("ReflectionsOnly[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Deferred Rendering (Reflections Only)"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLS_API UArchVisMoviePipelineDeferredPass_PathTracer : public UMoviePipelineDeferredPass_PathTracer |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineDeferredPass_PathTracer() : UMoviePipelineDeferredPass_PathTracer() |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("PathTracer[ArchVis]"); |
||||
|
} |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Path Tracer"); |
||||
|
} |
||||
|
#endif |
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override |
||||
|
{ |
||||
|
return UArchVisMoviePipelineDeferredPass::GetArchVisCameraInfo(Super::GetCameraInfo(InOutSampleState, OptPayload), InOutSampleState, GetPipeline(), GetNumCamerasToRender()); |
||||
|
} |
||||
|
protected: |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
@ -0,0 +1,35 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Engine/SceneCapture2D.h" |
||||
|
#include "ArchVisSceneCapture2D.generated.h" |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(hidecategories=(Collision, Material, Attachment, Actor, CaptureComponent2D)) |
||||
|
class ARCHVISTOOLS_API AArchVisSceneCapture2D : public ASceneCapture2D |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
private: |
||||
|
/** Scene capture component. */ |
||||
|
UPROPERTY(Category = DecalActor, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true")) |
||||
|
class UArchVisSceneCaptureComponent2D* ArchVisSceneCaptureComponent2D; |
||||
|
|
||||
|
/** Returns the ArchVisCineCameraComponent of this ArchVisCineCamera */ |
||||
|
UPROPERTY(Category = DecalActor, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true")) |
||||
|
class UArchVisCineCameraComponent* ArchVisCineCameraComponent; |
||||
|
|
||||
|
public: |
||||
|
AArchVisSceneCapture2D(const FObjectInitializer& ObjectInitializer); |
||||
|
|
||||
|
/** Returns ArchVisCaptureComponent2D sub object **/ |
||||
|
UFUNCTION(BlueprintCallable, Category = "ArchVisSceneCapture2D") |
||||
|
class UArchVisSceneCaptureComponent2D* GetArchVisCaptureComponent2D() const { return ArchVisSceneCaptureComponent2D; } |
||||
|
|
||||
|
UFUNCTION(BlueprintCallable, Category = "ArchVisSceneCapture2D") |
||||
|
UArchVisCineCameraComponent* GetArchVisCineCameraComponent() const { return ArchVisCineCameraComponent; } |
||||
|
}; |
||||
@ -0,0 +1,36 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Components/SceneCaptureComponent2D.h" |
||||
|
#include "ArchVisSceneCaptureComponent2D.generated.h" |
||||
|
|
||||
|
/**
|
||||
|
* An experimental SceneCaptureComponent working with an ArchVisCamera. |
||||
|
* Works a bit differently than a regular SceneCaptureComponent as you have to link the camera to render via the ArchVisCineCameraActor or the ArchVisCineCameraComponent properties |
||||
|
*/ |
||||
|
UCLASS(hidecategories = (Collision, Object, Physics, SceneComponent, CaptureComponent2D), ClassGroup = Rendering, EditInlineNew, meta = (BlueprintSpawnableComponent)) |
||||
|
class ARCHVISTOOLS_API UArchVisSceneCaptureComponent2D : public USceneCaptureComponent2D |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
/** The Component to get the view from. If not set, it will look if the property AArchVisCineCameraActor is set. */ |
||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="ArchVis") |
||||
|
TSoftObjectPtr<class UArchVisCineCameraComponent> ArchVisCineCameraComponent; |
||||
|
|
||||
|
/** If the property ArchVisCineCameraComponent is not set, it will access this property to get the view from. If not set, it will look at the AttachedParent of the component. */ |
||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="ArchVis") |
||||
|
TSoftObjectPtr<class AArchVisCineCameraActor> ArchVisCineCameraActor; |
||||
|
|
||||
|
virtual const AActor* GetViewOwner() const override; |
||||
|
|
||||
|
protected: |
||||
|
virtual void GetCameraView(float DeltaTime, FMinimalViewInfo& OutMinimalViewInfo) override; |
||||
|
|
||||
|
/** The function used internally by GetCameraView to find the ArchVisCameraComponent to render. You can override it to fit your needs */ |
||||
|
virtual class UArchVisCineCameraComponent* FindCameraComponent() const; |
||||
|
|
||||
|
virtual void UpdateSceneCaptureContents(FSceneInterface* Scene, class ISceneRenderBuilder& SceneRenderBuilder) override; |
||||
|
}; |
||||
@ -0,0 +1,49 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "SceneViewExtension.h" |
||||
|
|
||||
|
class UArchVisCineCameraComponent; |
||||
|
class UArchVisSubsystem; |
||||
|
|
||||
|
/**
|
||||
|
* A hack class used by ArchVisMoviePipelineDeferredPass to force a specific scene should not be rendered for this view |
||||
|
*/ |
||||
|
struct FArchVisSceneViewExtensionContextDisableOnce : public FSceneViewExtensionContext |
||||
|
{ |
||||
|
private: |
||||
|
//~ FSceneViewExtensionContext Interface
|
||||
|
virtual FName GetRTTI() const override { return TEXT("FArchVisSceneViewExtensionContext"); } |
||||
|
public: |
||||
|
FArchVisSceneViewExtensionContextDisableOnce() { } |
||||
|
FArchVisSceneViewExtensionContextDisableOnce(FSceneInterface* InScene) |
||||
|
: FSceneViewExtensionContext(InScene) |
||||
|
{ } |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
class ARCHVISTOOLS_API FArchVisSceneViewExtension : public FSceneViewExtensionBase |
||||
|
{ |
||||
|
public: |
||||
|
FArchVisSceneViewExtension(const FAutoRegister& AutoRegister, UArchVisSubsystem* InArchVisSubsystem); |
||||
|
|
||||
|
//~ Begin FSceneViewExtensionBase Interface
|
||||
|
virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override; |
||||
|
virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override; |
||||
|
virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override {}; |
||||
|
virtual void PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessingInputs& Inputs) override {}; |
||||
|
//~ End FSceneViewExtensionBase Interface
|
||||
|
|
||||
|
static FMinimalViewInfo GetCorrectedViewInfo(const UWorld* World, UArchVisCineCameraComponent* ArchVisComponent, const FIntPoint& UnconstrainedViewSize, const FMinimalViewInfo& CurrentViewInfo); |
||||
|
// Adjustment of FViewport::CalculateViewExtents to remove the need from the FViewport
|
||||
|
static FIntRect CalculateViewExtents(float AspectRatio, const FIntRect& ViewRect); |
||||
|
|
||||
|
protected: |
||||
|
// Disable the View Extension in MRQ
|
||||
|
virtual bool IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const override; |
||||
|
|
||||
|
private: |
||||
|
UArchVisSubsystem* ArchVisSubsystem = nullptr; |
||||
|
}; |
||||
@ -0,0 +1,16 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Modules/ModuleInterface.h" |
||||
|
|
||||
|
class FArchVisToolsModule : public IModuleInterface |
||||
|
{ |
||||
|
public: |
||||
|
|
||||
|
/** IModuleInterface implementation */ |
||||
|
virtual void StartupModule() override; |
||||
|
virtual void ShutdownModule() override; |
||||
|
virtual bool SupportsDynamicReloading() override { return true; } |
||||
|
}; |
||||
@ -0,0 +1,116 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Runtime/Launch/Resources/Version.h" |
||||
|
|
||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogArchVisTools, Log, All); |
||||
|
|
||||
|
#ifndef LOG |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Display, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define LOG(Format, ...) __LOG__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOG__(LogCategory, Format, ...) UE_LOG(LogCategory, Display, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOG_H |
||||
|
/**
|
||||
|
* @brief LOG + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Display, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOG_H(Format, ...) __LOGH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGH__(LogCategory, Format, ...) UE_LOG(LogCategory, Display, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOGV |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Log, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define LOGV(Format, ...) __LOGV__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGV__(LogCategory, Format, ...) UE_LOG(LogCategory, Log, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOGV_H |
||||
|
/**
|
||||
|
* @brief LOG VERBOSE + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Log, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOGV_H(Format, ...) __LOGVH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGVH__(LogCategory, Format, ...) UE_LOG(LogCategory, Log, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WARN |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Warning, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define WARN(Format, ...) __WARN__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __WARN__(LogCategory, Format, ...) UE_LOG(LogCategory, Warning, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WARN_H |
||||
|
/**
|
||||
|
* @brief WARN + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Warning, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define WARN_H(Format, ...) __WARNH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __WARNH__(LogCategory, Format, ...) UE_LOG(LogCategory, Warning, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef ERROR |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Error, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define ERROR(Format, ...) __ERROR__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __ERROR__(LogCategory, Format, ...) UE_LOG(LogCategory, Error, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef ERROR_H |
||||
|
/**
|
||||
|
* @brief ERROR + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Error, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define ERROR_H(Format, ...) __ERRORH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __ERRORH__(LogCategory, Format, ...) UE_LOG(LogCategory, Error, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef __FILENAME__ |
||||
|
/**
|
||||
|
* @brief Returns the current Filename |
||||
|
*/ |
||||
|
#define __FILENAME__ (wcsrchr(TEXT(__FILE__), '\\') ? wcsrchr(TEXT(__FILE__), '\\') + 1 : TEXT(__FILE__)) |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WHERE |
||||
|
/**
|
||||
|
* @brief Logs where this function has been called. Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define WHERE LOG("%s - R%i - %hs", __FILENAME__, __LINE__, __FUNCTION__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef HERE |
||||
|
/**
|
||||
|
* @brief Prints the current function name in the Output. Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisTools |
||||
|
*/ |
||||
|
#define HERE LOGV(" ==== [%hs] ==== ", __FUNCTION__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef BtoS |
||||
|
/**
|
||||
|
* @brief Converts a bool to *CHAR for logging |
||||
|
*/ |
||||
|
#define BtoS(var) var ? TEXT("True") : TEXT("False") |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#define DEFINITION_FILE "ArchVisToolsDefinitions" |
||||
|
#pragma message(" [" DEFINITION_FILE "] COMPILING ON UE `" PREPROCESSOR_TO_STRING(ENGINE_MAJOR_VERSION) "`.`" PREPROCESSOR_TO_STRING(ENGINE_MINOR_VERSION) "`.`" PREPROCESSOR_TO_STRING(ENGINE_PATCH_VERSION) "`") |
||||
|
|
||||
|
#undef DEFINITION_FILE |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Components/ActorComponent.h" |
||||
|
#include "FaceCameraComponent.generated.h" |
||||
|
|
||||
|
|
||||
|
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) |
||||
|
class ARCHVISTOOLS_API UFaceCameraComponent : public UActorComponent |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
// Sets default values for this component's properties
|
||||
|
UFaceCameraComponent(); |
||||
|
|
||||
|
/** If set to true, the Component will force the Actor to Face the Camera on Tick */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Face Camera") |
||||
|
bool bFaceCameraActive = true; |
||||
|
|
||||
|
/** If set to true, the Component will also rotate its Roll to align to the camera, otherwise it will stay vertical */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Face Camera") |
||||
|
bool bAllowRoll = false; |
||||
|
|
||||
|
/** The added rotation required for the right part of the Actor to face the Camera. The default will align the Y Axis of the Actor to the Camera */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Face Camera") |
||||
|
FRotator AddedRotation = FRotator(0.f, 90.f, 0.f); |
||||
|
|
||||
|
/** The function called to make the Actor face the Camera */ |
||||
|
UFUNCTION(BlueprintCallable, Category="Face Camera") |
||||
|
void MakeActorFaceCamera(); |
||||
|
|
||||
|
// Called every frame
|
||||
|
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; |
||||
|
|
||||
|
/** Gets the location of the Camera in the editor, it should still work even in play mode */ |
||||
|
UFUNCTION(BlueprintPure, Category="Face Camera") |
||||
|
FVector GetEditorCameraLocation() const; |
||||
|
}; |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Graph/Renderers/MovieGraphDeferredPass.h" |
||||
|
#include "Graph/Renderers/MovieGraphImagePassBase.h" |
||||
|
#include "Graph/Renderers/MovieGraphPathTracerPass.h" |
||||
|
#include "UObject/Object.h" |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
struct ARCHVISTOOLS_API FMovieGraphArchVisDeferredPass : public UE::MovieGraph::Rendering::FMovieGraphDeferredPass |
||||
|
{ |
||||
|
virtual void Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) override; |
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
struct ARCHVISTOOLS_API FMovieGraphArchVisPathTracerPass : public UE::MovieGraph::Rendering::FMovieGraphPathTracerPass |
||||
|
{ |
||||
|
virtual void Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) override; |
||||
|
}; |
||||
@ -0,0 +1,48 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Graph/Nodes/MovieGraphDeferredPassNode.h" |
||||
|
#include "Graph/Nodes/MovieGraphPathTracerPassNode.h" |
||||
|
#include "MovieGraphArchVisDeferredRenderPassNode.generated.h" |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS() |
||||
|
class ARCHVISTOOLS_API UMovieGraphArchVisDeferredRenderPassNode : public UMovieGraphDeferredRenderPassNode |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetNodeTitle(const bool bGetDescriptive = false) const override; |
||||
|
#endif |
||||
|
|
||||
|
protected: |
||||
|
// UMovieGraphRenderPassNode Interface
|
||||
|
virtual FString GetRendererNameImpl() const override; |
||||
|
virtual TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> CreateInstance() const override; |
||||
|
// ~UMovieGraphRenderPassNode Interface
|
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS() |
||||
|
class ARCHVISTOOLS_API UMovieGraphArchVisPathTracerRenderPassNode : public UMovieGraphPathTracerRenderPassNode |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetNodeTitle(const bool bGetDescriptive = false) const override; |
||||
|
#endif |
||||
|
|
||||
|
protected: |
||||
|
// UMovieGraphRenderPassNode Interface
|
||||
|
virtual FString GetRendererNameImpl() const override; |
||||
|
virtual TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> CreateInstance() const override; |
||||
|
// ~UMovieGraphRenderPassNode Interface
|
||||
|
}; |
||||
@ -0,0 +1,75 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
using UnrealBuildTool; |
||||
|
|
||||
|
|
||||
|
public class ArchVisToolsEditor : ModuleRules |
||||
|
{ |
||||
|
public ArchVisToolsEditor(ReadOnlyTargetRules Target) : base(Target) |
||||
|
{ |
||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; |
||||
|
|
||||
|
PublicIncludePaths.AddRange(new string[] { |
||||
|
// ... add public include paths required here ...
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PrivateIncludePaths.AddRange(new string[] { |
||||
|
// ... add other private include paths required here ...
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PublicDependencyModuleNames.AddRange(new string[] { |
||||
|
"Core", |
||||
|
"CoreUObject", |
||||
|
"Engine", |
||||
|
"InputCore", |
||||
|
"RHI", |
||||
|
// ... add other public dependencies that you statically link with here ...
|
||||
|
"UnrealEd", |
||||
|
"Blutility", |
||||
|
"UMG", |
||||
|
"LevelEditor", |
||||
|
"ArchVisTools", |
||||
|
"DataprepLibraries", |
||||
|
"MeshMergeUtilities", |
||||
|
"DataprepCore", |
||||
|
|
||||
|
"AssetRegistry", |
||||
|
"ContentBrowser", |
||||
|
|
||||
|
"CinematicCamera", |
||||
|
"MovieRenderPipelineCore", |
||||
|
"MovieRenderPipelineRenderPasses", |
||||
|
"PlacementMode", |
||||
|
"LevelSequenceEditor", |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
PrivateDependencyModuleNames.AddRange(new string[] { |
||||
|
"CoreUObject", |
||||
|
"Engine", |
||||
|
"Slate", |
||||
|
"SlateCore", |
||||
|
// ... add private dependencies that you statically link with here ...
|
||||
|
// "ArchVisTools",
|
||||
|
"DataprepLibraries", |
||||
|
"DatasmithContent", |
||||
|
"Json", |
||||
|
"RenderCore", |
||||
|
"MoviePipelineMaskRenderPass", |
||||
|
//"PlacementMode",
|
||||
|
// "LevelSequenceEditor",
|
||||
|
}); |
||||
|
|
||||
|
|
||||
|
DynamicallyLoadedModuleNames.AddRange(new string[] { |
||||
|
// ... add any modules that your module loads dynamically here ...
|
||||
|
// "PlacementMode",
|
||||
|
// "MeshMergeUtilities",
|
||||
|
}); |
||||
|
|
||||
|
PrivateDefinitions.Add(string.Format("BRANCH_NAME=\"{0}\"", Target.Version.BranchName)); |
||||
|
PrivateDefinitions.Add(string.Format("IS_UE5_EARLY_ACCESS={0}", Target.Version.BranchName == "++UE5+Release-5.0-EarlyAccess" ? 1 : 0)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,460 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisMoviePipelineDeferredPassObjectId.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraComponent.h" |
||||
|
#include "ArchVisMoviePipelineDeferredPass.h" |
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.h" |
||||
|
#include "ArchVisMovieRenderOverlappedMask.h" |
||||
|
#include "ArchVisSceneViewExtension.h" |
||||
|
#include "CanvasTypes.h" |
||||
|
#include "EngineModule.h" |
||||
|
#include "EngineUtils.h" |
||||
|
#include "MoviePipelineCameraSetting.h" |
||||
|
#include "MoviePipelineHashUtils.h" |
||||
|
#include "MoviePipelineOutputBuilder.h" |
||||
|
#include "MoviePipelineQueue.h" |
||||
|
#include "MoviePipelineSurfaceReader.h" |
||||
|
#include "MoviePipelineTelemetry.h" |
||||
|
#include "RendererInterface.h" |
||||
|
#include "RenderingThread.h" |
||||
|
#include "SceneView.h" |
||||
|
#include "TextureResource.h" |
||||
|
#include "Components/InstancedStaticMeshComponent.h" |
||||
|
#include "Engine/TextureRenderTarget2D.h" |
||||
|
#include "Materials/Material.h" |
||||
|
#include "Materials/MaterialInstanceDynamic.h" |
||||
|
#include "Serialization/JsonSerializer.h" |
||||
|
#include "UObject/Package.h" |
||||
|
#include "UObject/UObjectAnnotation.h" |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
#include "Editor/EditorPerProjectUserSettings.h" |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
|
||||
|
|
||||
|
UE::MoviePipeline::FImagePassCameraViewData UArchVisMoviePipelineObjectIdRenderPass::GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload) const |
||||
|
{ |
||||
|
UE::MoviePipeline::FImagePassCameraViewData CameraInfo = Super::GetCameraInfo(InOutSampleState, OptPayload); |
||||
|
|
||||
|
// here, the CameraInfo.ViewActor doesn't always return the right actor for "consistency", see UMoviePipelineDeferredPassBase::GetCameraInfo
|
||||
|
// so we need to check the SidecarCameraData if we have more than one camera
|
||||
|
UArchVisCineCameraComponent* ArchVisComponent; |
||||
|
|
||||
|
UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; |
||||
|
const int32 NumCameras = GetNumCamerasToRender(); |
||||
|
if (NumCameras == 1) |
||||
|
{ |
||||
|
ArchVisComponent = CameraInfo.ViewActor ? CameraInfo.ViewActor->FindComponentByClass<UArchVisCineCameraComponent>() : nullptr; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FMinimalViewInfo SideCarViewInfo; |
||||
|
UCameraComponent* OutCamera = nullptr; |
||||
|
GetPipeline()->GetSidecarCameraData(CurrentShot, InOutSampleState.OutputState.CameraIndex, SideCarViewInfo, &OutCamera); |
||||
|
ArchVisComponent = Cast<UArchVisCineCameraComponent>(OutCamera); |
||||
|
} |
||||
|
|
||||
|
if (!ArchVisComponent) |
||||
|
{ |
||||
|
return CameraInfo; |
||||
|
} |
||||
|
|
||||
|
const UWorld* World = GetPipeline()->GetWorld(); |
||||
|
const FMinimalViewInfo ViewInfo = FArchVisSceneViewExtension::GetCorrectedViewInfo(World, ArchVisComponent, InOutSampleState.OverscannedResolution, CameraInfo.ViewInfo); |
||||
|
CameraInfo.ViewInfo = ViewInfo; |
||||
|
|
||||
|
return CameraInfo; |
||||
|
} |
||||
|
|
||||
|
int32 UArchVisMoviePipelineObjectIdRenderPass::GetNumCamerasToRender() const |
||||
|
{ |
||||
|
UMoviePipelineExecutorShot* CurrentShot = GetPipeline()->GetActiveShotList()[GetPipeline()->GetCurrentShotIndex()]; |
||||
|
UMoviePipelineCameraSetting* CameraSettings = GetPipeline()->FindOrAddSettingForShot<UMoviePipelineCameraSetting>(CurrentShot); |
||||
|
|
||||
|
return CameraSettings->bRenderAllCameras ? CurrentShot->SidecarCameras.Num() : 1; |
||||
|
} |
||||
|
|
||||
|
// -------------------------------------------------------------------------
|
||||
|
// ------------------------- Object Ids ---------------------------------
|
||||
|
// C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\MovieScene\MoviePipelineMaskRenderPass\Source\MoviePipelineMaskRenderPass\Private\MoviePipelineObjectIdPass.cpp
|
||||
|
// -------------------------------------------------------------------------
|
||||
|
|
||||
|
DECLARE_CYCLE_STAT(TEXT("STAT_MoviePipeline_AccumulateMaskSample_TT"), STAT_AccumulateMaskSample_TaskThread, STATGROUP_MoviePipeline); |
||||
|
|
||||
|
static FUObjectAnnotationSparse<ArchVisMoviePipeline::FObjectIdAccelerationData, true> ArchVisMoviePipelineManifestAnnotation; |
||||
|
|
||||
|
// Forward Declare
|
||||
|
namespace ArchVisMoviePipeline |
||||
|
{ |
||||
|
static void AccumulateSampleObjectId_TaskThread(TUniquePtr<FImagePixelData>&& InPixelData, const ArchVisMoviePipeline::FObjectIdMaskSampleAccumulationArgs& InParams); |
||||
|
} |
||||
|
extern const TSparseArray<HHitProxy*>& GetAllHitProxies(); |
||||
|
|
||||
|
|
||||
|
UArchVisMoviePipelineObjectIdRenderPass::UArchVisMoviePipelineObjectIdRenderPass() |
||||
|
: UMoviePipelineImagePassBase() |
||||
|
, IdType(EArchVisMoviePipelineObjectIdPassIdType::Full) |
||||
|
{ |
||||
|
PassIdentifier = FMoviePipelinePassIdentifier("ActorHitProxyMask[ArchVis]"); |
||||
|
|
||||
|
// We output three layers which is 6 total influences per pixel.
|
||||
|
for (int32 Index = 0; Index < 3; Index++) |
||||
|
{ |
||||
|
ExpectedPassIdentifiers.Add(FMoviePipelinePassIdentifier(PassIdentifier.Name + FString::Printf(TEXT("%02d"), Index))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const |
||||
|
{ |
||||
|
InTelemetry->bUsesObjectID = true; |
||||
|
} |
||||
|
|
||||
|
TWeakObjectPtr<UTextureRenderTarget2D> UArchVisMoviePipelineObjectIdRenderPass::CreateViewRenderTargetImpl(const FIntPoint& InSize, IViewCalcPayload* OptPayload) const |
||||
|
{ |
||||
|
// Crete the render target with the correct bit depth.
|
||||
|
TWeakObjectPtr<UTextureRenderTarget2D> TileRenderTarget = NewObject<UTextureRenderTarget2D>(GetTransientPackage()); |
||||
|
TileRenderTarget->ClearColor = FLinearColor(0.0f, 0.0f, 0.0f, 0.0f); |
||||
|
|
||||
|
// Ensure there's no gamma in the RT otherwise the HitProxy color ids don't round trip properly.
|
||||
|
TileRenderTarget->TargetGamma = 0.f; |
||||
|
TileRenderTarget->InitCustomFormat(InSize.X, InSize.Y, EPixelFormat::PF_B8G8R8A8, true); |
||||
|
TileRenderTarget->AddToRoot(); |
||||
|
|
||||
|
return TileRenderTarget; |
||||
|
} |
||||
|
|
||||
|
TSharedPtr<FMoviePipelineSurfaceQueue, ESPMode::ThreadSafe> UArchVisMoviePipelineObjectIdRenderPass::CreateSurfaceQueueImpl(const FIntPoint& InSize, IViewCalcPayload* OptPayload) const |
||||
|
{ |
||||
|
TSharedPtr<FMoviePipelineSurfaceQueue, ESPMode::ThreadSafe> SurfaceQueue = MakeShared<FMoviePipelineSurfaceQueue, ESPMode::ThreadSafe>(InSize, EPixelFormat::PF_B8G8R8A8, 3, false); |
||||
|
|
||||
|
return SurfaceQueue; |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::GatherOutputPassesImpl(TArray<FMoviePipelinePassIdentifier>& ExpectedRenderPasses) |
||||
|
{ |
||||
|
// Don't call the super which adds the generic PassIdentifier, which in this case is numberless and incorrect for the final output spec.
|
||||
|
// Super::GatherOutputPassesImpl(ExpectedRenderPasses);
|
||||
|
ExpectedRenderPasses.Append(ExpectedPassIdentifiers); |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) |
||||
|
{ |
||||
|
Super::SetupImpl(InPassInitSettings); |
||||
|
|
||||
|
// Re-initialize the render target and surface queue
|
||||
|
GetOrCreateViewRenderTarget(InPassInitSettings.BackbufferResolution); |
||||
|
GetOrCreateSurfaceQueue(InPassInitSettings.BackbufferResolution); |
||||
|
|
||||
|
AccumulatorPool = MakeShared<TAccumulatorPool<ArchVisMoviePipeline::FMaskOverlappedAccumulator>, ESPMode::ThreadSafe>(6); |
||||
|
|
||||
|
ArchVisMoviePipeline::FObjectIdAccelerationData AccelData = ArchVisMoviePipeline::FObjectIdAccelerationData(); |
||||
|
|
||||
|
// Static metadata needed for Cryptomatte
|
||||
|
uint32 NameHash = MoviePipeline::HashNameToId(TCHAR_TO_UTF8(*PassIdentifier.Name)); |
||||
|
FString PassIdentifierHashAsShortString = FString::Printf(TEXT("%08x"), NameHash); |
||||
|
PassIdentifierHashAsShortString.LeftInline(7); |
||||
|
|
||||
|
AccelData.PassIdentifierHashAsShortString = PassIdentifierHashAsShortString; |
||||
|
|
||||
|
|
||||
|
AccelData.JsonManifest = MakeShared<FJsonObject>(); |
||||
|
|
||||
|
AccelData.Cache = MakeShared<TMap<int32, ArchVisMoviePipeline::FMoviePipelineHitProxyCacheValue>>(); |
||||
|
AccelData.Cache->Reserve(1000); |
||||
|
|
||||
|
{ |
||||
|
// Add our default to the manifest.
|
||||
|
static const uint32 DefaultHash = MoviePipeline::HashNameToId(TCHAR_TO_UTF8(TEXT("default"))); |
||||
|
AccelData.JsonManifest->SetStringField(TEXT("default"), FString::Printf(TEXT("%08x"), DefaultHash)); |
||||
|
} |
||||
|
ArchVisMoviePipelineManifestAnnotation.AddAnnotation(this, AccelData); |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
UEditorPerProjectUserSettings* EditorSettings = GetMutableDefault<UEditorPerProjectUserSettings>(); |
||||
|
bPrevAllowSelectTranslucent = EditorSettings->bAllowSelectTranslucent; |
||||
|
EditorSettings->bAllowSelectTranslucent = bIncludeTranslucentObjects; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::TeardownImpl() |
||||
|
{ |
||||
|
ArchVisMoviePipelineManifestAnnotation.RemoveAnnotation(this); |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
UEditorPerProjectUserSettings* EditorSettings = GetMutableDefault<UEditorPerProjectUserSettings>(); |
||||
|
EditorSettings->bAllowSelectTranslucent = bPrevAllowSelectTranslucent; |
||||
|
#endif |
||||
|
|
||||
|
// Preserve our view state until the rendering thread has been flushed.
|
||||
|
Super::TeardownImpl(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const |
||||
|
{ |
||||
|
OutShowFlag = FEngineShowFlags(EShowFlagInitMode::ESFIM_Game); |
||||
|
OutShowFlag.DisableAdvancedFeatures(); |
||||
|
OutShowFlag.SetPostProcessing(false); |
||||
|
OutShowFlag.SetPostProcessMaterial(false); |
||||
|
|
||||
|
// Screen-percentage scaling mixes IDs when doing downsampling, so it is disabled.
|
||||
|
OutShowFlag.SetScreenPercentage(false); |
||||
|
OutShowFlag.SetHitProxies(true); |
||||
|
OutViewModeIndex = EViewModeIndex::VMI_Unlit; |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) |
||||
|
{ |
||||
|
// Object Ids have no history buffer so no need to render when we're going to discard.
|
||||
|
if (InSampleState.bDiscardResult) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Wait for a surface to be available to write to. This will stall the game thread while the RHI/Render Thread catch up.
|
||||
|
Super::RenderSample_GameThreadImpl(InSampleState); |
||||
|
|
||||
|
FMoviePipelineRenderPassMetrics InOutSampleState = InSampleState; |
||||
|
|
||||
|
TSharedPtr<FSceneViewFamilyContext> ViewFamily = CalculateViewFamily(InOutSampleState); |
||||
|
|
||||
|
// Submit to be rendered. Main render pass always uses target 0. We do this before making the Hitproxy cache because
|
||||
|
// BeginRenderingViewFamily ensures render state for things are created.
|
||||
|
TWeakObjectPtr<UTextureRenderTarget2D> ViewRenderTarget = GetOrCreateViewRenderTarget(InSampleState.BackbufferSize); |
||||
|
check(ViewRenderTarget.IsValid()); |
||||
|
|
||||
|
FRenderTarget* RenderTarget = ViewRenderTarget->GameThread_GetRenderTargetResource(); |
||||
|
check(RenderTarget); |
||||
|
|
||||
|
FCanvas Canvas = FCanvas(RenderTarget, nullptr, GetPipeline()->GetWorld(), ViewFamily->GetFeatureLevel(), FCanvas::CDM_DeferDrawing, 1.0f); |
||||
|
GetRendererModule().BeginRenderingViewFamily(&Canvas, ViewFamily.Get()); |
||||
|
|
||||
|
ENQUEUE_RENDER_COMMAND(TransitionTextureSRVState)( |
||||
|
[RenderTarget](FRHICommandListImmediate& RHICmdList) mutable |
||||
|
{ |
||||
|
// Transition our render target from a render target view to a shader resource view to allow the UMG preview material to read from this Render Target.
|
||||
|
RHICmdList.Transition(FRHITransitionInfo(RenderTarget->GetRenderTargetTexture(), ERHIAccess::RTV, ERHIAccess::SRVGraphicsPixel)); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
// Update the data in place, no need to copy back to the annotation.
|
||||
|
ArchVisMoviePipeline::FObjectIdAccelerationData AccelData = ArchVisMoviePipelineManifestAnnotation.GetAnnotation(this); |
||||
|
ArchVisMoviePipeline::UpdateManifestAccelerationData(AccelData, IdType); |
||||
|
ArchVisMoviePipelineManifestAnnotation.AddAnnotation(this, AccelData); |
||||
|
|
||||
|
// Update ObjectID-related file metadata
|
||||
|
UpdateCryptomatteMetadata(AccelData, GetTypenameHash(), PassIdentifier.Name, InOutSampleState.OutputState.FileMetadata); |
||||
|
|
||||
|
// Main Render Pass
|
||||
|
{ |
||||
|
// Readback + Accumulate.
|
||||
|
TSharedRef<FImagePixelDataPayload, ESPMode::ThreadSafe> FramePayload = MakeShared<FImagePixelDataPayload, ESPMode::ThreadSafe>(); |
||||
|
FramePayload->PassIdentifier = PassIdentifier; |
||||
|
FramePayload->SampleState = InOutSampleState; |
||||
|
FramePayload->SortingOrder = GetOutputFileSortingOrder(); |
||||
|
|
||||
|
TSharedPtr<FAccumulatorPool::FAccumulatorInstance, ESPMode::ThreadSafe> SampleAccumulator = nullptr; |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_MoviePipeline_WaitForAvailableAccumulator); |
||||
|
SampleAccumulator = AccumulatorPool->BlockAndGetAccumulator_GameThread(InOutSampleState.OutputState.OutputFrameNumber, FramePayload->PassIdentifier); |
||||
|
} |
||||
|
|
||||
|
ArchVisMoviePipeline::FObjectIdMaskSampleAccumulationArgs AccumulationArgs; |
||||
|
{ |
||||
|
AccumulationArgs.OutputMerger = GetPipeline()->OutputBuilder; |
||||
|
AccumulationArgs.Accumulator = StaticCastSharedPtr<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(SampleAccumulator->Accumulator); |
||||
|
AccumulationArgs.NumOutputLayers = ExpectedPassIdentifiers.Num(); |
||||
|
// Create a copy of our hash map and shuffle it along with the readback data so they stay in sync.
|
||||
|
AccumulationArgs.CacheData = MakeShared<TMap<int32, ArchVisMoviePipeline::FMoviePipelineHitProxyCacheValue>>(*AccelData.Cache); |
||||
|
} |
||||
|
|
||||
|
auto Callback = [this, FramePayload, AccumAgs = MoveTemp(AccumulationArgs), SampleAccumulator](TUniquePtr<FImagePixelData>&& InPixelData) mutable |
||||
|
{ |
||||
|
bool bFinalSample = FramePayload->IsLastTile() && FramePayload->IsLastTemporalSample(); |
||||
|
|
||||
|
FMoviePipelineBackgroundAccumulateTask Task; |
||||
|
Task.LastCompletionEvent = SampleAccumulator->TaskPrereq; |
||||
|
|
||||
|
FGraphEventRef Event = Task.Execute([PixelData = MoveTemp(InPixelData), AccumArgsInner = MoveTemp(AccumAgs), bFinalSample, SampleAccumulator]() mutable |
||||
|
{ |
||||
|
// Enqueue a encode for this frame onto our worker thread.
|
||||
|
ArchVisMoviePipeline::AccumulateSampleObjectId_TaskThread(MoveTemp(PixelData), MoveTemp(AccumArgsInner)); |
||||
|
if (bFinalSample) |
||||
|
{ |
||||
|
SampleAccumulator->bIsActive = false; |
||||
|
SampleAccumulator->TaskPrereq = nullptr; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
SampleAccumulator->TaskPrereq = Event; |
||||
|
this->OutstandingTasks.Add(Event); |
||||
|
}; |
||||
|
|
||||
|
TSharedPtr<FMoviePipelineSurfaceQueue, ESPMode::ThreadSafe> LocalSurfaceQueue = GetOrCreateSurfaceQueue(InSampleState.BackbufferSize, (IViewCalcPayload*)(&FramePayload.Get())); |
||||
|
|
||||
|
ENQUEUE_RENDER_COMMAND(CanvasRenderTargetResolveCommand)( |
||||
|
[LocalSurfaceQueue, FramePayload, Callback, RenderTarget](FRHICommandListImmediate& RHICmdList) mutable |
||||
|
{ |
||||
|
// Enqueue a encode for this frame onto our worker thread.
|
||||
|
LocalSurfaceQueue->OnRenderTargetReady_RenderThread(RenderTarget->GetRenderTargetTexture(), FramePayload, MoveTemp(Callback)); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void UArchVisMoviePipelineObjectIdRenderPass::AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) |
||||
|
{ |
||||
|
UArchVisMoviePipelineDeferredPass::RemoveArchVisViewExtension(InContext, GetWorld()->Scene); |
||||
|
Super::AddViewExtensions(InContext, InOutSampleState); |
||||
|
} |
||||
|
|
||||
|
FString UArchVisMoviePipelineObjectIdRenderPass::GetTypenameHash() const |
||||
|
{ |
||||
|
const uint32 NameHash = MoviePipeline::HashNameToId(TCHAR_TO_UTF8(*PassIdentifier.Name)); |
||||
|
FString TypenameHashString = FString::Printf(TEXT("%08x"), NameHash); |
||||
|
TypenameHashString.LeftInline(7); |
||||
|
|
||||
|
return TypenameHashString; |
||||
|
} |
||||
|
|
||||
|
namespace ArchVisMoviePipeline |
||||
|
{ |
||||
|
static void AccumulateSampleObjectId_TaskThread(TUniquePtr<FImagePixelData>&& InPixelData, const ArchVisMoviePipeline::FObjectIdMaskSampleAccumulationArgs& InParams) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_AccumulateMaskSample_TaskThread); |
||||
|
const double TotalSampleBeginTime = FPlatformTime::Seconds(); |
||||
|
|
||||
|
bool bIsWellFormed = InPixelData->IsDataWellFormed(); |
||||
|
|
||||
|
if (!bIsWellFormed) |
||||
|
{ |
||||
|
// figure out why it is not well formed, and print a warning.
|
||||
|
int64 RawSize = InPixelData->GetRawDataSizeInBytes(); |
||||
|
|
||||
|
int64 SizeX = InPixelData->GetSize().X; |
||||
|
int64 SizeY = InPixelData->GetSize().Y; |
||||
|
int64 ByteDepth = int64(InPixelData->GetBitDepth() / 8); |
||||
|
int64 NumChannels = int64(InPixelData->GetNumChannels()); |
||||
|
int64 ExpectedTotalSize = SizeX * SizeY * ByteDepth * NumChannels; |
||||
|
int64 ActualTotalSize = InPixelData->GetRawDataSizeInBytes(); |
||||
|
|
||||
|
UE_LOG(LogMovieRenderPipeline, Log, TEXT("MaskPassAccumulateSample_TaskThread: Data is not well formed.")); |
||||
|
UE_LOG(LogMovieRenderPipeline, Log, TEXT("Image dimension: %lldx%lld, %lld, %lld"), SizeX, SizeY, ByteDepth, NumChannels); |
||||
|
UE_LOG(LogMovieRenderPipeline, Log, TEXT("Expected size: %lld"), ExpectedTotalSize); |
||||
|
UE_LOG(LogMovieRenderPipeline, Log, TEXT("Actual size: %lld"), ActualTotalSize); |
||||
|
} |
||||
|
|
||||
|
check(bIsWellFormed); |
||||
|
|
||||
|
FImagePixelDataPayload* FramePayload = InPixelData->GetPayload<FImagePixelDataPayload>(); |
||||
|
check(FramePayload); |
||||
|
|
||||
|
// Writing tiles can be useful for debug reasons. These get passed onto the output every frame.
|
||||
|
if (FramePayload->SampleState.bWriteSampleToDisk) |
||||
|
{ |
||||
|
// Send the data to the Output Builder. This has to be a copy of the pixel data from the GPU, since
|
||||
|
// it enqueues it onto the game thread and won't be read/sent to write to disk for another frame.
|
||||
|
// The extra copy is unfortunate, but is only the size of a single sample (ie: 1920x1080 -> 17mb)
|
||||
|
TUniquePtr<FImagePixelData> SampleData = InPixelData->CopyImageData(); |
||||
|
InParams.OutputMerger->OnSingleSampleDataAvailable_AnyThread(MoveTemp(SampleData)); |
||||
|
} |
||||
|
|
||||
|
// For the first sample in a new output, we allocate memory
|
||||
|
if (FramePayload->IsFirstTile() && FramePayload->IsFirstTemporalSample()) |
||||
|
{ |
||||
|
InParams.Accumulator->InitMemory(FIntPoint(FramePayload->SampleState.TileSize.X * FramePayload->SampleState.TileCounts.X, FramePayload->SampleState.TileSize.Y * FramePayload->SampleState.TileCounts.Y)); |
||||
|
InParams.Accumulator->ZeroPlanes(); |
||||
|
} |
||||
|
|
||||
|
// Accumulate the new sample to our target
|
||||
|
{ |
||||
|
const double RemapBeginTime = FPlatformTime::Seconds(); |
||||
|
|
||||
|
FIntPoint RawSize = InPixelData->GetSize(); |
||||
|
|
||||
|
check(FramePayload->SampleState.TileSize.X + 2 * FramePayload->SampleState.OverlappedPad.X == RawSize.X); |
||||
|
check(FramePayload->SampleState.TileSize.Y + 2 * FramePayload->SampleState.OverlappedPad.Y == RawSize.Y); |
||||
|
|
||||
|
const void* RawData; |
||||
|
int64 TotalSize; |
||||
|
InPixelData->GetRawData(RawData, TotalSize); |
||||
|
|
||||
|
const FColor* RawDataPtr = static_cast<const FColor*>(RawData); |
||||
|
TArray64<float> IdData; |
||||
|
IdData.SetNumUninitialized(RawSize.X * RawSize.Y); |
||||
|
|
||||
|
// Remap hitproxy id into precalculated Cryptomatte hash
|
||||
|
RemapHitProxyIdToCryptomatteHash(RawSize, RawDataPtr, InParams.CacheData, IdData); |
||||
|
const double RemapEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedRemapMs = float((RemapEndTime - RemapBeginTime) * 1000.0f); |
||||
|
|
||||
|
const double AccumulateBeginTime = FPlatformTime::Seconds(); |
||||
|
MoviePipeline::FTileWeight1D WeightFunctionX; |
||||
|
MoviePipeline::FTileWeight1D WeightFunctionY; |
||||
|
FramePayload->GetWeightFunctionParams(/*Out*/ WeightFunctionX, /*Out*/ WeightFunctionY); |
||||
|
{ |
||||
|
InParams.Accumulator->AccumulatePixelData((float*)(IdData.GetData()), RawSize, FramePayload->SampleState.OverlappedOffset, FramePayload->SampleState.OverlappedSubpixelShift, |
||||
|
WeightFunctionX, WeightFunctionY); |
||||
|
} |
||||
|
|
||||
|
const double AccumulateEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedAccumulateMs = float((AccumulateEndTime - AccumulateBeginTime) * 1000.0f); |
||||
|
|
||||
|
UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("Remap Time: %8.2fms Accumulation time: %8.2fms"), ElapsedRemapMs, ElapsedAccumulateMs); |
||||
|
} |
||||
|
|
||||
|
if (FramePayload->IsLastTile() && FramePayload->IsLastTemporalSample()) |
||||
|
{ |
||||
|
const double FetchBeginTime = FPlatformTime::Seconds(); |
||||
|
|
||||
|
int32 FullSizeX = InParams.Accumulator->PlaneSize.X; |
||||
|
int32 FullSizeY = InParams.Accumulator->PlaneSize.Y; |
||||
|
|
||||
|
// Now that a tile is fully built and accumulated we can notify the output builder that the
|
||||
|
// data is ready so it can pass that onto the output containers (if needed).
|
||||
|
// 32 bit FLinearColor
|
||||
|
TArray<TArray64<FLinearColor>> OutputLayers; |
||||
|
for (int32 Index = 0; Index < InParams.NumOutputLayers; Index++) |
||||
|
{ |
||||
|
OutputLayers.Add(TArray64<FLinearColor>()); |
||||
|
} |
||||
|
|
||||
|
InParams.Accumulator->FetchFinalPixelDataLinearColor(OutputLayers); |
||||
|
|
||||
|
for (int32 Index = 0; Index < InParams.NumOutputLayers; Index++) |
||||
|
{ |
||||
|
// We unfortunately can't share ownership of the payload from the last sample due to the changed pass identifiers.
|
||||
|
TSharedRef<FImagePixelDataPayload, ESPMode::ThreadSafe> NewPayload = FramePayload->Copy(); |
||||
|
NewPayload->PassIdentifier = FMoviePipelinePassIdentifier(FramePayload->PassIdentifier.Name + FString::Printf(TEXT("%02d"), Index)); |
||||
|
|
||||
|
TUniquePtr<TImagePixelData<FLinearColor>> FinalPixelData = MakeUnique<TImagePixelData<FLinearColor>>(FIntPoint(FullSizeX, FullSizeY), MoveTemp(OutputLayers[Index]), NewPayload); |
||||
|
|
||||
|
// Send each layer to the Output Builder
|
||||
|
InParams.OutputMerger->OnCompleteRenderPassDataAvailable_AnyThread(MoveTemp(FinalPixelData)); |
||||
|
} |
||||
|
|
||||
|
// Free the memory in the accumulator now that we've extracted all
|
||||
|
InParams.Accumulator->Reset(); |
||||
|
|
||||
|
const double FetchEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedFetchMs = float((FetchEndTime - FetchBeginTime) * 1000.0f); |
||||
|
|
||||
|
UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("Final Frame Fetch Time: %8.2fms"), ElapsedFetchMs); |
||||
|
} |
||||
|
const double TotalSampleEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedTotalSampleMs = float((TotalSampleEndTime - TotalSampleBeginTime) * 1000.0f); |
||||
|
UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("Total Sample Time: %8.2fms"), ElapsedTotalSampleMs); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
|
|
||||
@ -0,0 +1,541 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisMovieRenderOverlappedMask.h" |
||||
|
|
||||
|
#include "MovieRenderPipelineCoreModule.h" |
||||
|
|
||||
|
|
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_AccumulateSinglePlane"), STAT_AccumulateSinglePlane, STATGROUP_MoviePipeline); |
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_AccumulateMultiplePlanes"), STAT_AccumulateMultiplePlanes, STATGROUP_MoviePipeline); |
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_InitMemory"), STAT_InitMemory, STATGROUP_MoviePipeline); |
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_ZeroPlanes"), STAT_ZeroPlanes, STATGROUP_MoviePipeline); |
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_AccumulatePixelData"), STAT_AccumulatePixelData, STATGROUP_MoviePipeline); |
||||
|
DECLARE_CYCLE_STAT(TEXT("MaskedAccumulator_FetchPixelData32"), STAT_FetchFinalPixelData32bit, STATGROUP_MoviePipeline); |
||||
|
|
||||
|
namespace ArchVisMoviePipeline |
||||
|
{ |
||||
|
void FMaskOverlappedAccumulator::InitMemory(FIntPoint InPlaneSize) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_InitMemory); |
||||
|
PlaneSize.X = InPlaneSize.X; |
||||
|
PlaneSize.Y = InPlaneSize.Y; |
||||
|
|
||||
|
ImageData.SetNumUninitialized(InPlaneSize.X * InPlaneSize.Y); |
||||
|
WeightPlane.Init(PlaneSize); |
||||
|
} |
||||
|
|
||||
|
void FMaskOverlappedAccumulator::ZeroPlanes() |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_ZeroPlanes); |
||||
|
|
||||
|
for (int64 Index = 0; Index < ImageData.Num(); Index++) |
||||
|
{ |
||||
|
for (int32 Plane = 0; Plane < 6; Plane++) |
||||
|
{ |
||||
|
ImageData[Index].Id[Plane] = -1; |
||||
|
ImageData[Index].Weight[Plane] = 0.f; |
||||
|
ImageData[Index].ExtraDataOffset = -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
SparsePixelData.Reset(); |
||||
|
WeightPlane.ZeroPlane(); |
||||
|
} |
||||
|
|
||||
|
void FMaskOverlappedAccumulator::Reset() |
||||
|
{ |
||||
|
PlaneSize.X = 0; |
||||
|
PlaneSize.Y = 0; |
||||
|
|
||||
|
// Let the desctructor clean up
|
||||
|
ImageData.Reset(); |
||||
|
SparsePixelData.Reset(); |
||||
|
WeightPlane.Reset(); |
||||
|
} |
||||
|
|
||||
|
void FMaskOverlappedAccumulator::AccumulatePixelData(const TArray<float>& InLayerIds, const FColor* InPixelWeights, FIntPoint InPixelDataSize, FIntPoint InTileOffset, FVector2D InSubpixelOffset, const MoviePipeline::FTileWeight1D& WeightX, const MoviePipeline::FTileWeight1D& WeightY) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_AccumulatePixelData); |
||||
|
|
||||
|
FIntPoint RawSize = InPixelDataSize; |
||||
|
{ |
||||
|
|
||||
|
TArray<float> WeightDataX; |
||||
|
TArray<float> WeightDataY; |
||||
|
WeightX.CalculateArrayWeight(WeightDataX, RawSize.X); |
||||
|
WeightY.CalculateArrayWeight(WeightDataY, RawSize.Y); |
||||
|
|
||||
|
FIntPoint SubRectOffset(WeightX.X0, WeightY.X0); |
||||
|
FIntPoint SubRectSize(WeightX.X3 - WeightX.X0, WeightY.X3 - WeightY.X0); |
||||
|
|
||||
|
// For a normal RGBA image we can add each channel in isolation and then divide by the total weight at the end.
|
||||
|
// Accumulation for masks are special, because we can't just blend each RGBA channel, as they're actually IDs and a blended ID changes which
|
||||
|
// object that pixel belongs to in the mask. So instead we need to store every influence per pixel, and the weight for that influence. This is
|
||||
|
// accomplished through a mix of dense data and sparse data storage. We store the first 6 unique influences per pixel, and then if there are
|
||||
|
// more than that we track those influences in a different array.
|
||||
|
AccumulateMultipleRanks(InLayerIds, InPixelWeights, RawSize, InTileOffset, InSubpixelOffset.X, InSubpixelOffset.Y, |
||||
|
SubRectOffset, SubRectSize, WeightDataX, WeightDataY); |
||||
|
|
||||
|
// Accumulate weights as well.
|
||||
|
{ |
||||
|
TArray64<float> VecOne; |
||||
|
VecOne.SetNum(RawSize.X * RawSize.Y); |
||||
|
for (int32 Index = 0; Index < VecOne.Num(); Index++) |
||||
|
{ |
||||
|
VecOne[Index] = 1.0f; |
||||
|
} |
||||
|
|
||||
|
WeightPlane.AccumulateSinglePlane(VecOne, RawSize, InTileOffset, InSubpixelOffset.X, InSubpixelOffset.Y, |
||||
|
SubRectOffset, SubRectSize, WeightDataX, WeightDataY); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void FMaskOverlappedAccumulator::AccumulatePixelData(float* InPixelData, FIntPoint InPixelDataSize, FIntPoint InTileOffset, FVector2D InSubpixelOffset, const MoviePipeline::FTileWeight1D& WeightX, const MoviePipeline::FTileWeight1D& WeightY) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_AccumulatePixelData); |
||||
|
|
||||
|
FIntPoint RawSize = InPixelDataSize; |
||||
|
|
||||
|
{ |
||||
|
|
||||
|
TArray<float> WeightDataX; |
||||
|
TArray<float> WeightDataY; |
||||
|
WeightX.CalculateArrayWeight(WeightDataX, RawSize.X); |
||||
|
WeightY.CalculateArrayWeight(WeightDataY, RawSize.Y); |
||||
|
|
||||
|
FIntPoint SubRectOffset(WeightX.X0, WeightY.X0); |
||||
|
FIntPoint SubRectSize(WeightX.X3 - WeightX.X0, WeightY.X3 - WeightY.X0); |
||||
|
|
||||
|
// For a normal RGBA image we can add each channel in isolation and then divide by the total weight at the end.
|
||||
|
// Accumulation for masks are special, because we can't just blend each RGBA channel, as they're actually IDs and a blended ID changes which
|
||||
|
// object that pixel belongs to in the mask. So instead we need to store every influence per pixel, and the weight for that influence. This is
|
||||
|
// accomplished through a mix of dense data and sparse data storage. We store the first 6 unique influences per pixel, and then if there are
|
||||
|
// more than that we track those influences in a different array.
|
||||
|
for (int32 RankIter = 0; RankIter < 1; RankIter += 2) |
||||
|
{ |
||||
|
AccumulateSingleRank(InPixelData, RawSize, InTileOffset, InSubpixelOffset.X, InSubpixelOffset.Y, |
||||
|
SubRectOffset, SubRectSize, WeightDataX, WeightDataY); |
||||
|
} |
||||
|
|
||||
|
// Accumulate weights as well.
|
||||
|
{ |
||||
|
TArray64<float> VecOne; |
||||
|
VecOne.SetNum(RawSize.X * RawSize.Y); |
||||
|
for (int32 Index = 0; Index < VecOne.Num(); Index++) |
||||
|
{ |
||||
|
VecOne[Index] = 1.0f; |
||||
|
} |
||||
|
|
||||
|
WeightPlane.AccumulateSinglePlane(VecOne, RawSize, InTileOffset, InSubpixelOffset.X, InSubpixelOffset.Y, |
||||
|
SubRectOffset, SubRectSize, WeightDataX, WeightDataY); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
void FMaskOverlappedAccumulator::AccumulateMultipleRanks(const TArray<float>& InRankIds, const FColor* InPixelWeights, FIntPoint InSize, FIntPoint InOffset, |
||||
|
float SubpixelOffsetX, float SubpixelOffsetY, |
||||
|
FIntPoint SubRectOffset, |
||||
|
FIntPoint SubRectSize, |
||||
|
const TArray<float>& WeightDataX, |
||||
|
const TArray<float>& WeightDataY) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_AccumulateMultiplePlanes); |
||||
|
|
||||
|
check(WeightDataX.Num() == InSize.X); |
||||
|
check(WeightDataY.Num() == InSize.Y); |
||||
|
|
||||
|
check(SubpixelOffsetX >= 0.0f); |
||||
|
check(SubpixelOffsetX <= 1.0f); |
||||
|
check(SubpixelOffsetY >= 0.0f); |
||||
|
check(SubpixelOffsetY <= 1.0f); |
||||
|
|
||||
|
check(SubRectSize.X >= 1); |
||||
|
check(SubRectSize.Y >= 1); |
||||
|
|
||||
|
// if the subpixel offset is less than 0.5, go back one pixel
|
||||
|
int32 StartX = (SubpixelOffsetX >= 0.5 ? InOffset.X : InOffset.X - 1); |
||||
|
int32 StartY = (SubpixelOffsetY >= 0.5 ? InOffset.Y : InOffset.Y - 1); |
||||
|
|
||||
|
// Build a weight table for each influence. The subpixel offset means that the new sample is added to four pixels, each with
|
||||
|
// a weight based on how far from the center on the x/y axis.
|
||||
|
float PixelWeight[2][2]; |
||||
|
{ |
||||
|
// make sure that the equal sign is correct, if the subpixel offset is 0.5, the weightX is 0 and it starts on the center pixel
|
||||
|
float WeightX = FMath::Frac(SubpixelOffsetX + 0.5f); |
||||
|
float WeightY = FMath::Frac(SubpixelOffsetY + 0.5f); |
||||
|
|
||||
|
// row, column
|
||||
|
PixelWeight[0][0] = (1.0f - WeightX) * (1.0f - WeightY); |
||||
|
PixelWeight[0][1] = (WeightX) * (1.0f - WeightY); |
||||
|
PixelWeight[1][0] = (1.0f - WeightX) * (WeightY); |
||||
|
PixelWeight[1][1] = (WeightX) * (WeightY); |
||||
|
} |
||||
|
|
||||
|
// This is the reversed reference. Instead of taking a tile and applying it to the accumulation, we start from the
|
||||
|
// accumulation point and figure out the source pixels that affect it. I.e. all the math is backwards, but it should
|
||||
|
// be faster.
|
||||
|
//
|
||||
|
// Given a position on the current tile (x,y), we apply the sum of the 4 points (x+0,y+0), (x+1,y+0), (x+0,y+1), (x+1,y+1) to the destination index.
|
||||
|
// So in reverse, given a destination position, our source sample positions are (x-1,y-1), (x+0,y-1), (x-1,y+0), (x+0,y+0).
|
||||
|
// That's why we make sure to start at a minimum of index 1, instead of 0.
|
||||
|
for (int32 CurrY = 0; CurrY < InSize.Y; CurrY++) |
||||
|
{ |
||||
|
for (int32 CurrX = 0; CurrX < InSize.X; CurrX++) |
||||
|
{ |
||||
|
int32 DstY = StartY + CurrY; |
||||
|
int32 DstX = StartX + CurrX; |
||||
|
|
||||
|
if (DstX >= 0 && DstY >= 0 && |
||||
|
DstX < PlaneSize.X && DstY < PlaneSize.Y) |
||||
|
{ |
||||
|
for (int32 OffsetY = 0; OffsetY < 2; OffsetY++) |
||||
|
{ |
||||
|
for (int32 OffsetX = 0; OffsetX < 2; OffsetX++) |
||||
|
{ |
||||
|
int32 SrcX = FMath::Max<int32>(CurrX - 1 + OffsetX, 0); |
||||
|
int32 SrcY = FMath::Max<int32>(CurrY - 1 + OffsetY, 0); |
||||
|
|
||||
|
for (int32 InRankIndex = 0; InRankIndex < InRankIds.Num(); InRankIndex++) |
||||
|
{ |
||||
|
// The id value comes from the weight id table,
|
||||
|
float Val = InRankIds.GetData()[InRankIndex]; |
||||
|
|
||||
|
float WX = WeightDataX[SrcX]; |
||||
|
float WY = WeightDataY[SrcY]; |
||||
|
float BaseWeight = WX * WY; |
||||
|
|
||||
|
float SampleWeight = 0.f; |
||||
|
switch (InRankIndex) |
||||
|
{ |
||||
|
case 0: SampleWeight = InPixelWeights[SrcY * InSize.X + SrcX].R / 255.f; break; |
||||
|
case 1: SampleWeight = InPixelWeights[SrcY * InSize.X + SrcX].G / 255.f; break; |
||||
|
case 2: SampleWeight = InPixelWeights[SrcY * InSize.X + SrcX].B / 255.f; break; |
||||
|
default: |
||||
|
checkNoEntry(); |
||||
|
} |
||||
|
|
||||
|
// To support falloff for overlapped images, two 1D masks are provided to this function which gives a [0,1] weight value for each pixel. Thus,
|
||||
|
// we add the full RGBA value to the total, and accumulate a weight plane as well which specifies how much influence each pixel actually recieves
|
||||
|
// as there are edge cases (such as the outer edges of the image) where they don't recieve a full weight due to no overlap.
|
||||
|
float Weight = BaseWeight * PixelWeight[1 - OffsetY][1 - OffsetX] * SampleWeight; |
||||
|
|
||||
|
if (Weight < SMALL_NUMBER) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
int32 TargetIndex = DstY * PlaneSize.X + DstX; |
||||
|
FMaskPixelSamples* CurrentSampleStack = &ImageData[TargetIndex]; |
||||
|
bool bSuccess = false; |
||||
|
while (!bSuccess) |
||||
|
{ |
||||
|
for (int32 SampleIndex = 0; SampleIndex < 6; SampleIndex++) |
||||
|
{ |
||||
|
if (CurrentSampleStack->Id[SampleIndex] == *(int32*)(&Val)) |
||||
|
{ |
||||
|
// The ID has already been recorded on this pixel, so we just increase the weight.
|
||||
|
CurrentSampleStack->Weight[SampleIndex] += Weight; |
||||
|
bSuccess = true; |
||||
|
break; |
||||
|
} |
||||
|
else if (CurrentSampleStack->Id[SampleIndex] == -1) |
||||
|
{ |
||||
|
// If this slot hasn't been used before, we can assume control.
|
||||
|
CurrentSampleStack->Weight[SampleIndex] = Weight; |
||||
|
CurrentSampleStack->Id[SampleIndex] = *(int32*)(&Val); |
||||
|
bSuccess = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If we didn't succeed in putting the pixel into the stack above then it is full. Now we need to search the sparse data.
|
||||
|
if (!bSuccess) |
||||
|
{ |
||||
|
if (CurrentSampleStack->ExtraDataOffset < 0) |
||||
|
{ |
||||
|
// Allocate a new sparse data block to hold onto the data for us.
|
||||
|
int32 NewIndex = SparsePixelData.AddDefaulted(); |
||||
|
CurrentSampleStack->ExtraDataOffset = NewIndex; |
||||
|
} |
||||
|
|
||||
|
// Repeat the for loop searching this new sparse block.
|
||||
|
CurrentSampleStack = SparsePixelData[CurrentSampleStack->ExtraDataOffset].Get(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
void FMaskOverlappedAccumulator::AccumulateSingleRank(const float* InRawData, FIntPoint InSize, FIntPoint InOffset, |
||||
|
float SubpixelOffsetX, float SubpixelOffsetY, |
||||
|
FIntPoint SubRectOffset, |
||||
|
FIntPoint SubRectSize, |
||||
|
const TArray<float>& WeightDataX, |
||||
|
const TArray<float>& WeightDataY) |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_AccumulateSinglePlane); |
||||
|
|
||||
|
check(WeightDataX.Num() == InSize.X); |
||||
|
check(WeightDataY.Num() == InSize.Y); |
||||
|
|
||||
|
check(SubpixelOffsetX >= 0.0f); |
||||
|
check(SubpixelOffsetX <= 1.0f); |
||||
|
check(SubpixelOffsetY >= 0.0f); |
||||
|
check(SubpixelOffsetY <= 1.0f); |
||||
|
|
||||
|
check(SubRectSize.X >= 1); |
||||
|
check(SubRectSize.Y >= 1); |
||||
|
|
||||
|
// if the subpixel offset is less than 0.5, go back one pixel
|
||||
|
int32 StartX = (SubpixelOffsetX >= 0.5 ? InOffset.X : InOffset.X - 1); |
||||
|
int32 StartY = (SubpixelOffsetY >= 0.5 ? InOffset.Y : InOffset.Y - 1); |
||||
|
|
||||
|
// Build a weight table for each influence. The subpixel offset means that the new sample is added to four pixels, each with
|
||||
|
// a weight based on how far from the center on the x/y axis.
|
||||
|
float PixelWeight[2][2]; |
||||
|
{ |
||||
|
// make sure that the equal sign is correct, if the subpixel offset is 0.5, the weightX is 0 and it starts on the center pixel
|
||||
|
float WeightX = FMath::Frac(SubpixelOffsetX + 0.5f); |
||||
|
float WeightY = FMath::Frac(SubpixelOffsetY + 0.5f); |
||||
|
|
||||
|
// row, column
|
||||
|
PixelWeight[0][0] = (1.0f - WeightX) * (1.0f - WeightY); |
||||
|
PixelWeight[0][1] = (WeightX) * (1.0f - WeightY); |
||||
|
PixelWeight[1][0] = (1.0f - WeightX) * (WeightY); |
||||
|
PixelWeight[1][1] = (WeightX) * (WeightY); |
||||
|
} |
||||
|
|
||||
|
// For each pixel in the final image, we read the incoming image 4 times. This lets us do the accumulation in scanlines
|
||||
|
// because each destination pixel is only touched once.
|
||||
|
int32 ActualDstX0 = StartX + SubRectOffset.X; |
||||
|
int32 ActualDstY0 = StartY + SubRectOffset.Y; |
||||
|
int32 ActualDstX1 = StartX + SubRectOffset.X + SubRectSize.X; |
||||
|
int32 ActualDstY1 = StartY + SubRectOffset.Y + SubRectSize.Y; |
||||
|
|
||||
|
ActualDstX0 = FMath::Clamp<int32>(ActualDstX0, 0, PlaneSize.X); |
||||
|
ActualDstX1 = FMath::Clamp<int32>(ActualDstX1, 0, PlaneSize.X); |
||||
|
|
||||
|
ActualDstY0 = FMath::Clamp<int32>(ActualDstY0, 0, PlaneSize.Y); |
||||
|
ActualDstY1 = FMath::Clamp<int32>(ActualDstY1, 0, PlaneSize.Y); |
||||
|
|
||||
|
const float PixelWeight00 = PixelWeight[0][0]; |
||||
|
const float PixelWeight01 = PixelWeight[0][1]; |
||||
|
const float PixelWeight10 = PixelWeight[1][0]; |
||||
|
const float PixelWeight11 = PixelWeight[1][1]; |
||||
|
|
||||
|
// Mutex to prevent multiple threads from trying to allocate SparsePixelData at the same time.
|
||||
|
FCriticalSection SparsePixelDataCS; |
||||
|
int32 NumScanlines = ActualDstY1 - ActualDstY0; |
||||
|
|
||||
|
ParallelFor(NumScanlines, [&](int32 InScanlineIndex = 0) |
||||
|
{ |
||||
|
int32 DstY = InScanlineIndex + ActualDstY0; |
||||
|
|
||||
|
int32 CurrY = DstY - StartY; |
||||
|
int32 SrcY0 = FMath::Max<int32>(0, CurrY + (0 - 1)); |
||||
|
int32 SrcY1 = FMath::Max<int32>(0, CurrY + (1 - 1)); |
||||
|
|
||||
|
const float* SrcLineWeight = WeightDataX.GetData(); |
||||
|
|
||||
|
const float RowWeight0 = WeightDataY[SrcY0]; |
||||
|
const float RowWeight1 = WeightDataY[SrcY1]; |
||||
|
|
||||
|
const float* SrcLineRaw0 = &InRawData[SrcY0 * InSize.X]; |
||||
|
const float* SrcLineRaw1 = &InRawData[SrcY1 * InSize.X]; |
||||
|
|
||||
|
for (int32 DstX = ActualDstX0; DstX < ActualDstX1; DstX++) |
||||
|
{ |
||||
|
// For each X pixel in the destination image, we need to get the four ids that will contribute to this pixel from our incoming image.
|
||||
|
struct FRank |
||||
|
{ |
||||
|
float Id; |
||||
|
float Weight; |
||||
|
}; |
||||
|
|
||||
|
int32 CurrX = DstX - StartX; |
||||
|
int32 X0 = FMath::Max<int32>(0, CurrX - 1 + 0); |
||||
|
int32 X1 = FMath::Max<int32>(0, CurrX - 1 + 1); |
||||
|
|
||||
|
FRank NewData[4]; |
||||
|
NewData[0].Id = SrcLineRaw0[X0]; |
||||
|
NewData[0].Weight = RowWeight0 * SrcLineWeight[X0] * PixelWeight[1][1]; |
||||
|
NewData[1].Id = SrcLineRaw0[X1]; |
||||
|
NewData[1].Weight = RowWeight0 * SrcLineWeight[X1] * PixelWeight[1][0]; |
||||
|
NewData[2].Id = SrcLineRaw1[X0]; |
||||
|
NewData[2].Weight = RowWeight1 * SrcLineWeight[X0] * PixelWeight[0][1]; |
||||
|
NewData[3].Id = SrcLineRaw1[X1]; |
||||
|
NewData[3].Weight = RowWeight1 * SrcLineWeight[X1] * PixelWeight[0][0]; |
||||
|
|
||||
|
// Now that we have all 4, we can find their ids in our destination pixel and add them + the correct weight.
|
||||
|
for (int32 NewDataIndex = 0; NewDataIndex < UE_ARRAY_COUNT(NewData); NewDataIndex++) |
||||
|
{ |
||||
|
if (NewData[NewDataIndex].Weight < SMALL_NUMBER) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const int32 TargetIndex = DstY * PlaneSize.X + DstX; |
||||
|
FMaskPixelSamples* CurrentSampleStack = &ImageData[TargetIndex]; |
||||
|
bool bSuccess = false; |
||||
|
while (!bSuccess) |
||||
|
{ |
||||
|
for (int32 SampleIndex = 0; SampleIndex < 6; SampleIndex++) |
||||
|
{ |
||||
|
if (CurrentSampleStack->Id[SampleIndex] == *(int32*)(&NewData[NewDataIndex].Id)) |
||||
|
{ |
||||
|
// The ID has already been recorded on this pixel, so we just increase the weight.
|
||||
|
CurrentSampleStack->Weight[SampleIndex] += NewData[NewDataIndex].Weight; |
||||
|
bSuccess = true; |
||||
|
break; |
||||
|
} |
||||
|
else if (CurrentSampleStack->Id[SampleIndex] == -1) |
||||
|
{ |
||||
|
// If this slot hasn't been used before, we can assume control.
|
||||
|
CurrentSampleStack->Weight[SampleIndex] = NewData[NewDataIndex].Weight; |
||||
|
CurrentSampleStack->Id[SampleIndex] = *(int32*)(&NewData[NewDataIndex].Id); |
||||
|
bSuccess = true; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// If we didn't succeed in putting the pixel into the stack above then it is full. Now we need to search the sparse data.
|
||||
|
if (!bSuccess) |
||||
|
{ |
||||
|
// Don't allow another thread to allocate extra data while we're allocating data as we don't
|
||||
|
// want the indexes messed up.
|
||||
|
FScopeLock ScopeLock(&SparsePixelDataCS); |
||||
|
|
||||
|
if (CurrentSampleStack->ExtraDataOffset < 0) |
||||
|
{ |
||||
|
// Allocate a new sparse data block to hold onto the data for us.
|
||||
|
TSharedPtr<FMaskPixelSamples, ESPMode::ThreadSafe> NewSampleData = MakeShared<FMaskPixelSamples, ESPMode::ThreadSafe>(); |
||||
|
int32 NewIndex = SparsePixelData.Add(NewSampleData); |
||||
|
CurrentSampleStack->ExtraDataOffset = NewIndex; |
||||
|
} |
||||
|
|
||||
|
// Repeat the for loop searching this new sparse block. This is a pointer to the memory the TSharedPtr
|
||||
|
// owns, so it's okay if another thread causes a reallocation fo the SparsePixelData array, the destination
|
||||
|
// memory won't change.
|
||||
|
CurrentSampleStack = SparsePixelData[CurrentSampleStack->ExtraDataOffset].Get(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void FMaskOverlappedAccumulator::FetchFinalPixelDataLinearColor(TArray<TArray64<FLinearColor>>& OutPixelDataLayers) const |
||||
|
{ |
||||
|
SCOPE_CYCLE_COUNTER(STAT_FetchFinalPixelData32bit); |
||||
|
int32 FullSizeX = PlaneSize.X; |
||||
|
int32 FullSizeY = PlaneSize.Y; |
||||
|
|
||||
|
for (TArray64<FLinearColor>& Layer : OutPixelDataLayers) |
||||
|
{ |
||||
|
Layer.SetNumUninitialized(FullSizeX * FullSizeY); |
||||
|
} |
||||
|
|
||||
|
struct FRank |
||||
|
{ |
||||
|
FRank() |
||||
|
: Id(0) |
||||
|
, Weight(0.f) |
||||
|
{} |
||||
|
|
||||
|
int32 Id; |
||||
|
float Weight; |
||||
|
}; |
||||
|
|
||||
|
// Process them in scanlines since we're reading shared data and outputting to distinct indexes
|
||||
|
ParallelFor(FullSizeY, |
||||
|
[&](int32 FullY = 0) |
||||
|
{ |
||||
|
TArray<FRank> Ranks; |
||||
|
|
||||
|
for (int32 FullX = 0L; FullX < FullSizeX; FullX++) |
||||
|
{ |
||||
|
Ranks.Reset(); |
||||
|
|
||||
|
// be careful with this index, make sure to use 64bit math, not 32bit
|
||||
|
int64 DstIndex = int64(FullY) * int64(FullSizeX) + int64(FullX); |
||||
|
|
||||
|
// We look at the linked list of samples per pixel, and then sort them by weight so we can take the top influences.
|
||||
|
FMaskPixelSamples const* CurrentSampleStack = &ImageData[FullY * PlaneSize.X + FullX]; |
||||
|
while (CurrentSampleStack) |
||||
|
{ |
||||
|
for (int32 Index = 0; Index < 6; Index++) |
||||
|
{ |
||||
|
// They are inserted in order so the first invalid id means no more.
|
||||
|
if (CurrentSampleStack->Id[Index] == -1) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
FRank& Rank = Ranks.AddDefaulted_GetRef(); |
||||
|
Rank.Id = CurrentSampleStack->Id[Index]; |
||||
|
Rank.Weight = CurrentSampleStack->Weight[Index]; |
||||
|
} |
||||
|
|
||||
|
if (CurrentSampleStack->ExtraDataOffset >= 0) |
||||
|
{ |
||||
|
CurrentSampleStack = SparsePixelData[CurrentSampleStack->ExtraDataOffset].Get(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
CurrentSampleStack = nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// We now have a list of ranks, we can sort them.
|
||||
|
Ranks.Sort([](const FRank& A, const FRank& B) |
||||
|
{ |
||||
|
return B.Weight < A.Weight; |
||||
|
}); |
||||
|
|
||||
|
// Pad this up to the number of outputs we have.
|
||||
|
for (int32 Index = Ranks.Num(); Index < (OutPixelDataLayers.Num() * 2); Index++) |
||||
|
{ |
||||
|
Ranks.AddDefaulted(); |
||||
|
} |
||||
|
|
||||
|
// Normalize the weights
|
||||
|
float TotalWeight = 0.0f; |
||||
|
{ |
||||
|
for (int32 Index = 0; Index < Ranks.Num(); Index++) |
||||
|
{ |
||||
|
TotalWeight += Ranks[Index].Weight; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Shouldn't get a zero weight unless these have been called out of order, but better odd behavior than crash.
|
||||
|
if(TotalWeight <= 0.f) |
||||
|
{ |
||||
|
TotalWeight = KINDA_SMALL_NUMBER; |
||||
|
} |
||||
|
|
||||
|
for (int32 LayerIndex = 0; LayerIndex < OutPixelDataLayers.Num(); LayerIndex++) |
||||
|
{ |
||||
|
FLinearColor& Color = OutPixelDataLayers[LayerIndex][DstIndex]; |
||||
|
|
||||
|
for (int32 ChannelIndex = 0; ChannelIndex < 2; ChannelIndex++) |
||||
|
{ |
||||
|
int32 RankIndex = (LayerIndex * 2) + ChannelIndex; |
||||
|
// R, B
|
||||
|
Color.Component((ChannelIndex * 2) + 0) = *(float*)(&Ranks[RankIndex].Id); |
||||
|
|
||||
|
float RawWeight = WeightPlane.ChannelData[FullY * FullSizeX + FullX]; |
||||
|
float Scale = 1.0f / (FMath::Max<float>(RawWeight, 0.0001f) * TotalWeight); |
||||
|
|
||||
|
// G, A
|
||||
|
Color.Component((ChannelIndex * 2) + 1) = Ranks[RankIndex].Weight / TotalWeight; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisToolsEditor.h" |
||||
|
|
||||
|
#include "ArchVisCineCameraActor.h" |
||||
|
#include "ArchVisToolsEditorDefinitions.h" |
||||
|
#include "IPlacementModeModule.h" |
||||
|
#include "LevelSequenceEditorModule.h" |
||||
|
|
||||
|
|
||||
|
#define LOCTEXT_NAMESPACE "FArchVisToolsEditorModule" |
||||
|
#define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
|
||||
|
void FArchVisToolsEditorModule::StartupModule() |
||||
|
{ |
||||
|
HERE; |
||||
|
|
||||
|
LOG("[FArchVisToolsEditorModule::StartupModule] Running on UE %i.%i.%i", ENGINE_MAJOR_VERSION, ENGINE_MINOR_VERSION, ENGINE_PATCH_VERSION); |
||||
|
|
||||
|
RegisterPlacementModeExtensions(); |
||||
|
} |
||||
|
|
||||
|
void FArchVisToolsEditorModule::ShutdownModule() |
||||
|
{ |
||||
|
HERE; |
||||
|
UnregisterPlacementModeExtensions(); |
||||
|
} |
||||
|
|
||||
|
/** Registers placement mode extensions. */ |
||||
|
void FArchVisToolsEditorModule::RegisterPlacementModeExtensions() |
||||
|
{ |
||||
|
// inspired from
|
||||
|
// C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\MovieScene\LevelSequenceEditor\Source\LevelSequenceEditor\Private\LevelSequenceEditorModule.cpp
|
||||
|
|
||||
|
FModuleManager::LoadModuleChecked<ILevelSequenceEditorModule>("LevelSequenceEditor"); // ensure the 'Cinematic' category already exists
|
||||
|
const FPlacementCategoryInfo* Info = IPlacementModeModule::Get().GetRegisteredPlacementCategory("Cinematic"); |
||||
|
if(Info) |
||||
|
{ |
||||
|
LOG("FArchVisToolsEditorModule::RegisterPlacementModeExtensions: Category `Cinematic` found!"); |
||||
|
PlaceActors.Add(IPlacementModeModule::Get().RegisterPlaceableItem(Info->UniqueHandle, MakeShareable(new FPlaceableItem(nullptr, FAssetData(AArchVisCineCameraActor::StaticClass()))))); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
WARN("FArchVisToolsEditorModule::RegisterPlacementModeExtensions: Category `Cinematic` NOT found!"); |
||||
|
const FPlacementCategoryInfo NewInfo( |
||||
|
LOCTEXT("CinematicCategoryName", "Cinematic"), |
||||
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "PlacementBrowser.Icons.Cinematics"), |
||||
|
"Cinematic", |
||||
|
TEXT("PMCinematic"), |
||||
|
25 |
||||
|
); |
||||
|
|
||||
|
IPlacementModeModule::Get().RegisterPlacementCategory(NewInfo); |
||||
|
PlaceActors.Add(IPlacementModeModule::Get().RegisterPlaceableItem(NewInfo.UniqueHandle, MakeShareable(new FPlaceableItem(nullptr, FAssetData(AArchVisCineCameraActor::StaticClass()))))); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** Unregisters placement mode extensions. */ |
||||
|
void FArchVisToolsEditorModule::UnregisterPlacementModeExtensions() |
||||
|
{ |
||||
|
// inspired from
|
||||
|
// C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\VirtualProduction\CameraCalibration\Source\CameraCalibrationEditor\Private\CameraCalibrationEditorModule.cpp
|
||||
|
if (IPlacementModeModule::IsAvailable()) |
||||
|
{ |
||||
|
IPlacementModeModule& PlacementModeModule = IPlacementModeModule::Get(); |
||||
|
|
||||
|
for (TOptional<FPlacementModeID>& PlaceActor : PlaceActors) |
||||
|
{ |
||||
|
if (PlaceActor.IsSet()) |
||||
|
{ |
||||
|
PlacementModeModule.UnregisterPlaceableItem(*PlaceActor); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
PlaceActors.Empty(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#undef LOCTEXT_NAMESPACE |
||||
|
#undef CURRENT_LOG_CATEGORY |
||||
|
|
||||
|
IMPLEMENT_MODULE(FArchVisToolsEditorModule, ArchVisToolsEditor) |
||||
@ -0,0 +1,5 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "ArchVisToolsEditorDefinitions.h" |
||||
|
|
||||
|
DEFINE_LOG_CATEGORY(LogArchVisToolsEditor); |
||||
@ -0,0 +1,12 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPBuildAdjacency.h" |
||||
|
|
||||
|
|
||||
|
void UDPBuildAdjacency::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
TArray<UStaticMesh*> ModifiedMeshes; |
||||
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS |
||||
|
BuildAdjacency(InContext.Objects, ModifiedMeshes); |
||||
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPDebugActor.h" |
||||
|
|
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
|
||||
|
|
||||
|
ADPDebugActor::ADPDebugActor() |
||||
|
{ |
||||
|
Tags.Add(UDataprepArchVisEditingOperation::Name_Tag_Debug); |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPDebugActorBoundingBoxOperation.h" |
||||
|
|
||||
|
|
||||
|
void UDPDebugActorBoundingBoxOperation::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
for (UObject* Object : InContext.Objects) |
||||
|
{ |
||||
|
AActor* Actor = Cast<AActor>(Object); |
||||
|
|
||||
|
if (!IsValid(Actor)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (bGenerateSeparateActor) |
||||
|
{ |
||||
|
if (bUseAlignedBoundingBox) |
||||
|
{ |
||||
|
DebugCreateAABBActorOfActor(Actor, bUseAccurateBoundingBox, bIncludeChildren, Thickness); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
DebugCreateBoundingBoxActorOfActor(Actor, bUseAccurateBoundingBox, bIncludeChildren, Thickness); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (bUseAlignedBoundingBox) |
||||
|
{ |
||||
|
DebugCreateAABBComponentOfActor(Actor, bUseAccurateBoundingBox, bIncludeChildren, Thickness); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
DebugCreateBoundingBoxComponentOfActor(Actor, bUseAccurateBoundingBox, bIncludeChildren, Thickness); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPDebugActorLocationOperation.h" |
||||
|
|
||||
|
|
||||
|
void UDPDebugActorLocationOperation::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
for (UObject* Object : InContext.Objects) |
||||
|
{ |
||||
|
AActor* Actor = Cast<AActor>(Object); |
||||
|
if (!IsValid(Actor)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (bGenerateSeparateActor) |
||||
|
{ |
||||
|
DebugCreateAxisActorAtActorLocation(Actor); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
DebugCreateAxisComponentAtActorLocation(Actor); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPDebugRemoveDebugData.h" |
||||
|
|
||||
|
|
||||
|
void UDPDebugRemoveDebugData::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
DebugRemoveDebugInformationFromUObjects(InContext.Objects); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPExplode.h" |
||||
|
|
||||
|
|
||||
|
void UDPExplode::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
Explode(InContext.Objects, bDeleteExplodedActors); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPFaceCamera.h" |
||||
|
|
||||
|
|
||||
|
void UDPFaceCamera::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
TArray<AActor*> MatchingActors; |
||||
|
TArray<UFaceCameraComponent*> FaceCameraComponents; |
||||
|
AddFaceCameraComponentIfMatchesTag(InContext.Objects, TagName, MatchingActors, FaceCameraComponents); |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPMaterialName.h" |
||||
|
|
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
|
||||
|
|
||||
|
TArray<FString> UDPMaterialName::Fetch_Implementation(const UObject* Object, bool& bOutFetchSucceeded) const |
||||
|
{ |
||||
|
return UDataprepArchVisEditingOperation::FetchMaterialName(Object, bOutFetchSucceeded); |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPMergeActorsOperation.h" |
||||
|
|
||||
|
#include "Engine/StaticMeshActor.h" |
||||
|
|
||||
|
|
||||
|
void UDPMergeActorsOperation::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
MergeUObjects(InContext.Objects, PivotPointAlignment, bMergeRootSiblings, MergedActors); |
||||
|
if (bCreateDebugAxisAtMergedActorLocation) |
||||
|
{ |
||||
|
for (AStaticMeshActor* Actor : MergedActors) |
||||
|
{ |
||||
|
DebugCreateAxisComponentAtActorLocation(Cast<AActor>(Actor)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPReplaceCineCameraOperation.h" |
||||
|
|
||||
|
|
||||
|
void UDPReplaceCineCameraOperation::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
TArray<AArchVisCineCameraActor*> NewActors; |
||||
|
ReplaceCameras(InContext.Objects, NewActors); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "Dataprep/DPSimplifyHierarchyOperation.h" |
||||
|
|
||||
|
|
||||
|
void UDPSimplifyHierarchyOperation::OnExecution_Implementation(const FDataprepContext& InContext) |
||||
|
{ |
||||
|
SimplifyHierarchy(InContext.Objects, TagName, bMergeMultipleMeshes); |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,93 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "MovieGraphArchVisObjectIdNode.h" |
||||
|
|
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.h" |
||||
|
#include "MovieGraphArchVisObjectIdPass.h" |
||||
|
#include "MoviePipelineTelemetry.h" |
||||
|
#include "Editor/EditorPerProjectUserSettings.h" |
||||
|
|
||||
|
|
||||
|
FUObjectAnnotationSparse<ArchVisMoviePipeline::FObjectIdAccelerationData, true>& UDEPRECATED_MovieGraphArchVisObjectIdNode::GetManifestAnnotation() |
||||
|
{ |
||||
|
static FUObjectAnnotationSparse<ArchVisMoviePipeline::FObjectIdAccelerationData, true> MovieGraphArchVisManifestAnnotation; |
||||
|
|
||||
|
return MovieGraphArchVisManifestAnnotation; |
||||
|
} |
||||
|
|
||||
|
FEngineShowFlags UDEPRECATED_MovieGraphArchVisObjectIdNode::GetShowFlags() const |
||||
|
{ |
||||
|
FEngineShowFlags Flags(ESFIM_Game); |
||||
|
Flags.DisableAdvancedFeatures(); |
||||
|
Flags.SetPostProcessing(false); |
||||
|
Flags.SetPostProcessMaterial(false); |
||||
|
|
||||
|
// The most important flag. The Hit Proxy IDs will be used to generate Object IDs.
|
||||
|
Flags.SetHitProxies(true); |
||||
|
|
||||
|
// Screen-percentage scaling mixes IDs when doing down-sampling, so it is disabled.
|
||||
|
Flags.SetScreenPercentage(false); |
||||
|
|
||||
|
return Flags; |
||||
|
} |
||||
|
|
||||
|
EViewModeIndex UDEPRECATED_MovieGraphArchVisObjectIdNode::GetViewModeIndex() const |
||||
|
{ |
||||
|
return VMI_Unlit; |
||||
|
} |
||||
|
|
||||
|
bool UDEPRECATED_MovieGraphArchVisObjectIdNode::GetAllowsShowFlagsCustomization() const |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
FText UDEPRECATED_MovieGraphArchVisObjectIdNode::GetNodeTitle(const bool bGetDescriptive) const |
||||
|
{ |
||||
|
return NSLOCTEXT("MovieRenderGraph", "ObjectIdNodeTitle", "[ArchVis] Object IDs"); |
||||
|
} |
||||
|
|
||||
|
FSlateIcon UDEPRECATED_MovieGraphArchVisObjectIdNode::GetIconAndTint(FLinearColor& OutColor) const |
||||
|
{ |
||||
|
static const FSlateIcon DeferredRendererIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.SizeMap"); |
||||
|
OutColor = FLinearColor::White; |
||||
|
|
||||
|
return DeferredRendererIcon; |
||||
|
} |
||||
|
#endif // WITH_EDITOR
|
||||
|
|
||||
|
void UDEPRECATED_MovieGraphArchVisObjectIdNode::UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const |
||||
|
{ |
||||
|
InTelemetry->bUsesObjectID = true; |
||||
|
} |
||||
|
|
||||
|
TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> UDEPRECATED_MovieGraphArchVisObjectIdNode::CreateInstance() const |
||||
|
{ |
||||
|
return MakeUnique<FMovieGraphArchVisObjectIdPass>(); |
||||
|
} |
||||
|
|
||||
|
FString UDEPRECATED_MovieGraphArchVisObjectIdNode::GetRendererNameImpl() const |
||||
|
{ |
||||
|
return TEXT("[ArchVis] ObjectID"); |
||||
|
} |
||||
|
|
||||
|
void UDEPRECATED_MovieGraphArchVisObjectIdNode::SetupImpl(const FMovieGraphRenderPassSetupData& InSetupData) |
||||
|
{ |
||||
|
Super::SetupImpl(InSetupData); |
||||
|
|
||||
|
#if WITH_EDITOR |
||||
|
UEditorPerProjectUserSettings* EditorSettings = GetMutableDefault<UEditorPerProjectUserSettings>(); |
||||
|
bPrevAllowSelectTranslucent = EditorSettings->bAllowSelectTranslucent; |
||||
|
EditorSettings->bAllowSelectTranslucent = bIncludeTranslucentObjects; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
void UDEPRECATED_MovieGraphArchVisObjectIdNode::TeardownImpl() |
||||
|
{ |
||||
|
#if WITH_EDITOR |
||||
|
UEditorPerProjectUserSettings* EditorSettings = GetMutableDefault<UEditorPerProjectUserSettings>(); |
||||
|
EditorSettings->bAllowSelectTranslucent = bPrevAllowSelectTranslucent; |
||||
|
#endif |
||||
|
|
||||
|
Super::TeardownImpl(); |
||||
|
} |
||||
@ -0,0 +1,316 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#include "MovieGraphArchVisObjectIdPass.h" |
||||
|
|
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.h" |
||||
|
#include "ArchVisMovieRenderOverlappedMask.h" |
||||
|
#include "MovieGraphArchVisDeferredPass.h" |
||||
|
#include "MovieGraphArchVisObjectIdNode.h" |
||||
|
#include "MoviePipelineHashUtils.h" |
||||
|
#include "MoviePipelineQueue.h" |
||||
|
#include "Graph/MovieGraphBlueprintLibrary.h" |
||||
|
// #include "Graph/MovieGraphObjectIdNode.h" // TODO: in 5.6, because MovieGraphObjectIdNode.h includes the private header MoviePipelineObjectIdUtils.h, we cannot include it
|
||||
|
#include "Graph/MovieGraphPipeline.h" |
||||
|
#include "Graph/Nodes/MovieGraphCameraNode.h" |
||||
|
#include "Graph/Nodes/MovieGraphImagePassBaseNode.h" |
||||
|
|
||||
|
namespace ArchVisMovieGraph |
||||
|
{ |
||||
|
/**
|
||||
|
* Gets the typename that will be used in the cryptomatte metadata ("typename" here is an official term in the cryptomatte spec). This is also |
||||
|
* the name of the layer without the numerical suffix. |
||||
|
*/ |
||||
|
static FString GetCryptomatteTypename(const FMovieGraphRenderDataIdentifier& InRenderDataIdentifier, const bool bIsMultiCam) |
||||
|
{ |
||||
|
FString CryptomatteTypename = FString::Format(TEXT("{0}_{1}"), {InRenderDataIdentifier.LayerName, InRenderDataIdentifier.RendererName}); |
||||
|
|
||||
|
// Include the camera name if there is more than one camera being rendered.
|
||||
|
if (bIsMultiCam) |
||||
|
{ |
||||
|
CryptomatteTypename = FString::Format(TEXT("{0}_{1}"), {InRenderDataIdentifier.CameraName, CryptomatteTypename}); |
||||
|
} |
||||
|
|
||||
|
return CryptomatteTypename; |
||||
|
} |
||||
|
|
||||
|
/** Gets the typename hash used as part of the cryptomatte metadata. Eg, "cryptomatte/<typename_hash>/..." */ |
||||
|
static FString GetTypenameHash(const FMovieGraphRenderDataIdentifier& InRenderDataIdentifier) |
||||
|
{ |
||||
|
// Note that the name hash includes the camera name as well because there may be multiple cameras rendered within one layer. Each layer/camera
|
||||
|
// combination needs its own unique typename hash so it has a distinct entry in the metadata.
|
||||
|
const uint32 NameHash = ::MoviePipeline::HashNameToId(TCHAR_TO_UTF8(*(InRenderDataIdentifier.LayerName + InRenderDataIdentifier.CameraName))); |
||||
|
FString TypenameHashString = FString::Printf(TEXT("%08x"), NameHash); |
||||
|
TypenameHashString.LeftInline(7); |
||||
|
|
||||
|
return TypenameHashString; |
||||
|
} |
||||
|
|
||||
|
static void AccumulateSampleObjectId_TaskThread(TUniquePtr<FImagePixelData>&& InPixelData, const TSharedRef<UE::MovieGraph::FMovieGraphSampleState> InSampleState, const TSharedRef<::MoviePipeline::IMoviePipelineAccumulationArgs> InAccumulatorArgs) |
||||
|
{ |
||||
|
TRACE_CPUPROFILER_EVENT_SCOPE(AccumulateSampleObjectId_TaskThread); |
||||
|
|
||||
|
TUniquePtr<FImagePixelData> SamplePixelData = MoveTemp(InPixelData); |
||||
|
|
||||
|
// Associate the sample state with the image as payload data, this allows downstream systems to fetch the values without us having to store the data
|
||||
|
// separately and ensure they stay paired the whole way down.
|
||||
|
TSharedPtr<UE::MovieGraph::FMovieGraphSampleState> SampleStatePayload = InSampleState->Copy(); |
||||
|
SamplePixelData->SetPayload(StaticCastSharedPtr<IImagePixelDataPayload>(SampleStatePayload)); |
||||
|
|
||||
|
const TSharedRef<FMovieGraphArchVisObjectIdMaskSampleAccumulationArgs> ObjectIdArgs = StaticCastSharedRef<FMovieGraphArchVisObjectIdMaskSampleAccumulationArgs>(InAccumulatorArgs); |
||||
|
|
||||
|
const TSharedPtr<UE::MovieGraph::IMovieGraphOutputMerger, ESPMode::ThreadSafe> OutputMergerPin = ObjectIdArgs->OutputMerger.Pin(); |
||||
|
if (!OutputMergerPin.IsValid()) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const bool bIsWellFormed = SamplePixelData->IsDataWellFormed(); |
||||
|
check(bIsWellFormed); |
||||
|
|
||||
|
// Note: Object ID doesn't currently have a property to control writing samples to disk, so this condition will never be true. One can be
|
||||
|
// added in the future though, if the need to write samples ever comes up.
|
||||
|
if (SampleStatePayload->bWriteSampleToDisk) |
||||
|
{ |
||||
|
// Debug Feature: Write the raw sample to disk for debugging purposes. We copy the data here,
|
||||
|
// as we don't want to disturb the memory flow below.
|
||||
|
TUniquePtr<FImagePixelData> SampleData = SamplePixelData->CopyImageData(); |
||||
|
OutputMergerPin->OnSingleSampleDataAvailable_AnyThread(MoveTemp(SampleData)); |
||||
|
} |
||||
|
|
||||
|
const TSharedPtr<ArchVisMoviePipeline::FMaskOverlappedAccumulator, ESPMode::ThreadSafe> AccumulatorPin = StaticCastWeakPtr<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(ObjectIdArgs->ImageAccumulator).Pin(); |
||||
|
|
||||
|
if (!AccumulatorPin->bIsInitialized) |
||||
|
{ |
||||
|
LLM_SCOPE_BYNAME(TEXT("MoviePipeline/ImageAccumulatorInitMemory")); |
||||
|
|
||||
|
const FIntPoint PlaneSize = FIntPoint(SampleStatePayload->AccumulatorResolution); |
||||
|
|
||||
|
AccumulatorPin->InitMemory(PlaneSize); |
||||
|
AccumulatorPin->ZeroPlanes(); |
||||
|
} |
||||
|
|
||||
|
// Accumulate the new sample to our target
|
||||
|
{ |
||||
|
TRACE_CPUPROFILER_EVENT_SCOPE(MoviePipeline_AccumulatePixelData); |
||||
|
|
||||
|
FIntPoint RawSize = SamplePixelData->GetSize(); |
||||
|
|
||||
|
const void* RawData; |
||||
|
int64 TotalSize; |
||||
|
SamplePixelData->GetRawData(RawData, TotalSize); |
||||
|
|
||||
|
const FColor* RawDataPtr = static_cast<const FColor*>(RawData); |
||||
|
TArray64<float> IdData; |
||||
|
IdData.SetNumUninitialized(RawSize.X * RawSize.Y); |
||||
|
|
||||
|
// Remap HitProxy ID into precalculated Cryptomatte hash
|
||||
|
RemapHitProxyIdToCryptomatteHash(RawSize, RawDataPtr, ObjectIdArgs->CacheData, IdData); |
||||
|
|
||||
|
const FIntPoint TileSize = SampleStatePayload->BackbufferResolution; |
||||
|
const FIntPoint OverlappedPad = SampleStatePayload->OverlappedPad; |
||||
|
const FIntPoint OverlappedOffset = SampleStatePayload->OverlappedOffset; |
||||
|
const FVector2D OverlappedSubpixelShift = SampleStatePayload->OverlappedSubpixelShift; |
||||
|
::MoviePipeline::FTileWeight1D WeightFunctionX; |
||||
|
::MoviePipeline::FTileWeight1D WeightFunctionY; |
||||
|
WeightFunctionX.InitHelper(OverlappedPad.X, TileSize.X, OverlappedPad.X); |
||||
|
WeightFunctionY.InitHelper(OverlappedPad.Y, TileSize.Y, OverlappedPad.Y); |
||||
|
|
||||
|
AccumulatorPin->AccumulatePixelData(IdData.GetData(), RawSize, OverlappedOffset, OverlappedSubpixelShift, WeightFunctionX, WeightFunctionY); |
||||
|
} |
||||
|
|
||||
|
// Finally on our last sample, we fetch the data out of the accumulator
|
||||
|
// and move it to the Output Merger.
|
||||
|
if (SampleStatePayload->bFetchFromAccumulator) |
||||
|
{ |
||||
|
const int32 FullSizeX = AccumulatorPin->PlaneSize.X; |
||||
|
const int32 FullSizeY = AccumulatorPin->PlaneSize.Y; |
||||
|
|
||||
|
// Now that a tile is fully built and accumulated we can notify the output builder that the
|
||||
|
// data is ready so it can pass that onto the output containers (if needed).
|
||||
|
// 32 bit FLinearColor
|
||||
|
TArray<TArray64<FLinearColor>> OutputLayers; |
||||
|
for (int32 Index = 0; Index < ObjectIdArgs->NumOutputLayers; Index++) |
||||
|
{ |
||||
|
OutputLayers.Add(TArray64<FLinearColor>()); |
||||
|
} |
||||
|
|
||||
|
AccumulatorPin->FetchFinalPixelDataLinearColor(OutputLayers); |
||||
|
|
||||
|
ArchVisMoviePipeline::FObjectIdAccelerationData* AccelData = FMovieGraphArchVisObjectIdPass::GetAccelerationData(SampleStatePayload->TraversalContext.RenderDataIdentifier.RootBranchName); |
||||
|
check(AccelData); |
||||
|
|
||||
|
// If there are multiple cameras being rendered, layer names should include the camera name
|
||||
|
bool bIsMultiCam = false; |
||||
|
{ |
||||
|
const UMovieGraphCameraSettingNode* CameraNode = SampleStatePayload->TraversalContext.Time.EvaluatedConfig->GetSettingForBranch<UMovieGraphCameraSettingNode>(UMovieGraphNode::GlobalsPinName); |
||||
|
bIsMultiCam = (CameraNode && CameraNode->bRenderAllCameras) && (SampleStatePayload->TraversalContext.Shot->SidecarCameras.Num() > 1); |
||||
|
} |
||||
|
|
||||
|
// Add in the object ID metadata. This cannot be done in the node's GetFormatResolveArgs() because the manifest is only known after render-time,
|
||||
|
// and the manifest data is destroyed during node teardown (and teardown occurs before the metadata is finalized and the file written to disk)
|
||||
|
const FMovieGraphRenderDataIdentifier& RenderDataIdentifier = SampleStatePayload->TraversalContext.RenderDataIdentifier; |
||||
|
const FString CryptomatteTypename = GetCryptomatteTypename(RenderDataIdentifier, bIsMultiCam); |
||||
|
const FString TypenameHash = GetTypenameHash(RenderDataIdentifier); |
||||
|
UpdateCryptomatteMetadata(*AccelData, TypenameHash, CryptomatteTypename, SampleStatePayload->AdditionalFileMetadata); |
||||
|
|
||||
|
for (int32 Index = 0; Index < ObjectIdArgs->NumOutputLayers; Index++) |
||||
|
{ |
||||
|
// We unfortunately can't share ownership of the payload from the last sample due to the changed pass identifiers and layer name.
|
||||
|
TSharedRef<UE::MovieGraph::FMovieGraphSampleState, ESPMode::ThreadSafe> NewPayload = SampleStatePayload->Copy(); |
||||
|
FMovieGraphRenderDataIdentifier NewIdentifier = FMovieGraphRenderDataIdentifier(SampleStatePayload->TraversalContext.RenderDataIdentifier); |
||||
|
NewIdentifier.SubResourceName = CryptomatteTypename + FString::Printf(TEXT("%02d"), Index); |
||||
|
NewPayload->TraversalContext.RenderDataIdentifier = NewIdentifier; |
||||
|
|
||||
|
// Update the layer name to be exactly what is needed for the Cryptomatte specification. The "name" in the metadata must match up
|
||||
|
// exactly with the layer names in the output file (with a number at the end, eg "<LayerName>01").
|
||||
|
NewPayload->LayerNameOverride = NewIdentifier.SubResourceName; |
||||
|
|
||||
|
// Bump the sort order for the layer. An Object ID layer shouldn't be the first in the output format. 100 here is just an arbitrary
|
||||
|
// number to force the Object ID layers to the end.
|
||||
|
NewPayload->CompositingSortOrder += 100; |
||||
|
|
||||
|
// Don't allow OCIO, otherwise Cryptomatte colors will not be correct.
|
||||
|
NewPayload->bAllowOCIO = false; |
||||
|
|
||||
|
TUniquePtr<TImagePixelData<FLinearColor>> FinalPixelData = MakeUnique<TImagePixelData<FLinearColor>>(FIntPoint(FullSizeX, FullSizeY), MoveTemp(OutputLayers[Index]), NewPayload); |
||||
|
|
||||
|
// Send each layer to the Output Builder
|
||||
|
OutputMergerPin->OnCompleteRenderPassDataAvailable_AnyThread(MoveTemp(FinalPixelData)); |
||||
|
} |
||||
|
|
||||
|
// Free the memory in the accumulator now that we've extracted all
|
||||
|
AccumulatorPin->Reset(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
FMovieGraphArchVisObjectIdPass::FMovieGraphArchVisObjectIdPass() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void FMovieGraphArchVisObjectIdPass::Setup(TWeakObjectPtr<UMovieGraphDefaultRenderer> InRenderer, TWeakObjectPtr<UMovieGraphImagePassBaseNode> InRenderPassNode, const FMovieGraphRenderPassLayerData& InLayer) |
||||
|
{ |
||||
|
FMovieGraphDeferredPass::Setup(InRenderer, InRenderPassNode, InLayer); |
||||
|
|
||||
|
LayerData = InLayer; |
||||
|
|
||||
|
// We output three layers. This equals 6 "ranks" in the Cryptomatte spec.
|
||||
|
for (int32 Index = 0; Index < 3; Index++) |
||||
|
{ |
||||
|
FMovieGraphRenderDataIdentifier NewIdentifier; |
||||
|
|
||||
|
NewIdentifier.RootBranchName = LayerData.BranchName; |
||||
|
NewIdentifier.LayerName = LayerData.LayerName; |
||||
|
NewIdentifier.RendererName = InRenderPassNode->GetRendererName(); |
||||
|
NewIdentifier.CameraName = LayerData.CameraName; |
||||
|
NewIdentifier.SubResourceName = ArchVisMovieGraph::GetCryptomatteTypename(NewIdentifier, InLayer.NumCameras > 1) + FString::Printf(TEXT("%02d"), Index); |
||||
|
|
||||
|
RenderDataIdentifiers.Add(NewIdentifier); |
||||
|
} |
||||
|
|
||||
|
// The deferred render pass base class needs a valid RenderDataIdentifier, so the first Object ID render data identifier will be used. All of
|
||||
|
// the Object ID render data identifiers are the same, other than the sub resource name, so this should be fine.
|
||||
|
RenderDataIdentifier = RenderDataIdentifiers[0]; |
||||
|
|
||||
|
ArchVisMoviePipeline::FObjectIdAccelerationData& AccelData = AccelerationDataByBranch.Add(LayerData.BranchName); |
||||
|
AccelData.JsonManifest = MakeShared<FJsonObject>(); |
||||
|
AccelData.Cache = MakeShared<TMap<int32, ArchVisMoviePipeline::FMoviePipelineHitProxyCacheValue>>(); |
||||
|
AccelData.Cache->Reserve(1000); |
||||
|
|
||||
|
// Update the Cryptomatte manifest
|
||||
|
{ |
||||
|
// Add our default to the manifest
|
||||
|
static const uint32 DefaultHash = ::MoviePipeline::HashNameToId(TCHAR_TO_UTF8(TEXT("default"))); |
||||
|
AccelData.JsonManifest->SetStringField(TEXT("default"), FString::Printf(TEXT("%08x"), DefaultHash)); |
||||
|
|
||||
|
// Add the HitProxies
|
||||
|
// TODO: in 5.6, because MovieGraphObjectIdNode.h includes the private header MoviePipelineObjectIdUtils.h, we cannot include it
|
||||
|
// const UMovieGraphObjectIdNode* ObjectIdNode = Cast<UMovieGraphObjectIdNode>(LayerData.RenderPassNode);
|
||||
|
// check(ObjectIdNode);
|
||||
|
// UpdateManifestAccelerationData(AccelData, ObjectIdNode->IdType);
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void FMovieGraphArchVisObjectIdPass::Teardown() |
||||
|
{ |
||||
|
FMovieGraphDeferredPass::Teardown(); |
||||
|
|
||||
|
AccelerationDataByBranch.Remove(RenderDataIdentifier.RootBranchName); |
||||
|
} |
||||
|
|
||||
|
void FMovieGraphArchVisObjectIdPass::GatherOutputPasses(UMovieGraphEvaluatedConfig* InConfig, TArray<FMovieGraphRenderDataIdentifier>& OutExpectedPasses) const |
||||
|
{ |
||||
|
OutExpectedPasses.Append(RenderDataIdentifiers); |
||||
|
} |
||||
|
|
||||
|
UMovieGraphImagePassBaseNode* FMovieGraphArchVisObjectIdPass::GetParentNode(UMovieGraphEvaluatedConfig* InConfig) const |
||||
|
{ |
||||
|
constexpr bool bIncludeCDOs = true; |
||||
|
UDEPRECATED_MovieGraphArchVisObjectIdNode* ParentNode = InConfig->GetSettingForBranch<UDEPRECATED_MovieGraphArchVisObjectIdNode>(GetBranchName(), bIncludeCDOs); |
||||
|
if (!ensureMsgf(ParentNode, TEXT("ObjectIdPass should not exist without parent node in graph."))) |
||||
|
{ |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
return ParentNode; |
||||
|
} |
||||
|
|
||||
|
void FMovieGraphArchVisObjectIdPass::Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) |
||||
|
{ |
||||
|
FMovieGraphDeferredPass::Render(InFrameTraversalContext, InTimeData); |
||||
|
|
||||
|
// TODO: in 5.6, because MovieGraphObjectIdNode.h includes the private header MoviePipelineObjectIdUtils.h, we cannot include it
|
||||
|
// UMovieGraphObjectIdNode* ParentNode = Cast<UMovieGraphObjectIdNode>(GetParentNode(InTimeData.EvaluatedConfig));
|
||||
|
// check(ParentNode);
|
||||
|
//
|
||||
|
// ArchVisMoviePipeline::FObjectIdAccelerationData* AccelData = GetAccelerationData(LayerData.BranchName);
|
||||
|
// check(AccelData);
|
||||
|
//
|
||||
|
// // This needs to be updated every frame. It's also important to call this AFTER the render finishes; HitProxy IDs will be out-of-date
|
||||
|
// // before the render starts. Render property changes made during the frame (via modifiers) will invalidate the HitProxy IDs in the global array,
|
||||
|
// // and those invalidations happen only once Render() is called (thus we need to wait until after Render() before updating the cache to
|
||||
|
// // get the new IDs).
|
||||
|
// UpdateManifestAccelerationData(*AccelData, ParentNode->IdType);
|
||||
|
} |
||||
|
|
||||
|
TSharedRef<MoviePipeline::IMoviePipelineAccumulationArgs> FMovieGraphArchVisObjectIdPass::GetOrCreateAccumulator(TObjectPtr<UMovieGraphDefaultRenderer> InGraphRenderer, const UE::MovieGraph::FMovieGraphSampleState& InSampleState) const |
||||
|
{ |
||||
|
const FMoviePipelineAccumulatorPoolPtr SampleAccumulatorPool = InGraphRenderer->GetOrCreateAccumulatorPool<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(); |
||||
|
const UE::MovieGraph::DefaultRenderer::FSurfaceAccumulatorPool::FInstancePtr AccumulatorInstance = SampleAccumulatorPool->GetAccumulatorInstance_GameThread<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(InSampleState.TraversalContext.Time.OutputFrameNumber, InSampleState.TraversalContext.RenderDataIdentifier); |
||||
|
|
||||
|
const ArchVisMoviePipeline::FObjectIdAccelerationData* AccelerationData = GetAccelerationData(InSampleState.TraversalContext.RenderDataIdentifier.RootBranchName); |
||||
|
check(AccelerationData); |
||||
|
|
||||
|
TSharedRef<FMovieGraphArchVisObjectIdMaskSampleAccumulationArgs> AccumulationArgs = MakeShared<FMovieGraphArchVisObjectIdMaskSampleAccumulationArgs>(); |
||||
|
AccumulationArgs->OutputMerger = InGraphRenderer->GetOwningGraph()->GetOutputMerger(); |
||||
|
AccumulationArgs->ImageAccumulator = StaticCastSharedPtr<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(AccumulatorInstance->Accumulator); |
||||
|
AccumulationArgs->AccumulatorInstance = SampleAccumulatorPool->GetAccumulatorInstance_GameThread<ArchVisMoviePipeline::FMaskOverlappedAccumulator>(InSampleState.TraversalContext.Time.OutputFrameNumber, InSampleState.TraversalContext.RenderDataIdentifier); |
||||
|
AccumulationArgs->NumOutputLayers = RenderDataIdentifiers.Num(); |
||||
|
AccumulationArgs->CacheData = MakeShared<TMap<int32, ArchVisMoviePipeline::FMoviePipelineHitProxyCacheValue>>(*AccelerationData->Cache); |
||||
|
AccumulationArgs->RenderPassNode = LayerData.RenderPassNode; |
||||
|
|
||||
|
return AccumulationArgs; |
||||
|
} |
||||
|
|
||||
|
UE::MovieGraph::Rendering::FMovieGraphImagePassBase::FAccumulatorSampleFunc FMovieGraphArchVisObjectIdPass::GetAccumulateSampleFunction() const |
||||
|
{ |
||||
|
return ArchVisMovieGraph::AccumulateSampleObjectId_TaskThread; |
||||
|
} |
||||
|
|
||||
|
ArchVisMoviePipeline::FObjectIdAccelerationData* FMovieGraphArchVisObjectIdPass::GetAccelerationData(const FName& InBranchName) |
||||
|
{ |
||||
|
return AccelerationDataByBranch.Find(InBranchName); |
||||
|
} |
||||
|
|
||||
|
UE::MovieGraph::DefaultRenderer::FRenderTargetInitParams FMovieGraphArchVisObjectIdPass::GetRenderTargetInitParams(const FMovieGraphTimeStepData& InTimeData, const FIntPoint& InResolution) |
||||
|
{ |
||||
|
UE::MovieGraph::DefaultRenderer::FRenderTargetInitParams InitParams; |
||||
|
|
||||
|
// Ensure there's no gamma in the render target otherwise the HitProxy color IDs don't round-trip properly.
|
||||
|
InitParams.Size = InResolution; |
||||
|
InitParams.TargetGamma = 0.f; |
||||
|
InitParams.bForceLinearGamma = true; |
||||
|
InitParams.PixelFormat = PF_B8G8R8A8; |
||||
|
|
||||
|
return InitParams; |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
// C:\Program Files\Epic Games\UE_4.27\Engine\Plugins\MovieScene\MoviePipelineMaskRenderPass\Source\MoviePipelineMaskRenderPass\Private\MoviePipelineHashUtils.h
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "Containers/HashTable.h" |
||||
|
|
||||
|
|
||||
|
namespace MoviePipeline |
||||
|
{ |
||||
|
static FORCEINLINE uint32 Murmur32_ROTL(uint32 InHash) |
||||
|
{ |
||||
|
InHash *= 0xcc9e2d51; |
||||
|
InHash = (InHash << 15) | (InHash >> 17); |
||||
|
InHash *= 0x1b873593; |
||||
|
return InHash; |
||||
|
} |
||||
|
|
||||
|
static uint32 Murmur32_x86_32(const uint8* InData, const uint32 InLength) |
||||
|
{ |
||||
|
const uint32* DataPtr = reinterpret_cast<const uint32*>(InData); |
||||
|
uint32 Hash = 0; |
||||
|
|
||||
|
int32 NumQuadruplets = InLength >> 2; |
||||
|
for (int32 Index = 0; Index < NumQuadruplets; Index++) |
||||
|
{ |
||||
|
uint32 Element = DataPtr[Index]; |
||||
|
|
||||
|
Hash ^= Murmur32_ROTL(Element); |
||||
|
Hash = (Hash << 13) | (Hash >> (32 - 13)); |
||||
|
Hash = Hash * 5 + 0xe6546b64; |
||||
|
} |
||||
|
|
||||
|
int32 RemainderCount = InLength & 0x3; |
||||
|
int32 Remainder = 0; |
||||
|
|
||||
|
const uint8* OffsetDataPtr = reinterpret_cast<const uint8*>(&InData[NumQuadruplets * sizeof(uint32)]); |
||||
|
for (int32 Index = 0; Index < RemainderCount; Index++) |
||||
|
{ |
||||
|
Remainder <<= 8; |
||||
|
Remainder |= OffsetDataPtr[(RemainderCount - 1) - Index]; |
||||
|
} |
||||
|
Hash ^= Murmur32_ROTL(Remainder); |
||||
|
Hash ^= InLength; |
||||
|
|
||||
|
return MurmurFinalize32(Hash); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static uint32 HashNameToId(ANSICHAR* InName) |
||||
|
{ |
||||
|
int32 MurmurLength = TCString<ANSICHAR>::Strlen(InName); |
||||
|
uint32 Hash = Murmur32_x86_32(reinterpret_cast<uint8*>(InName), MurmurLength); |
||||
|
|
||||
|
// NaN and +/- Inf are not suitable values for hashes (as there are multiple representations and they can't round trip).
|
||||
|
uint32 Exponent = Hash >> 23 & 0xFF; |
||||
|
// Subnormals have a exponent of zero while Inf +/- has an exponent of 0xFF
|
||||
|
if (Exponent == 0x0 || Exponent == 0xFF) |
||||
|
{ |
||||
|
// Flip the lowest order bit to to turn us into a valid float.
|
||||
|
Hash ^= 1 << 23; |
||||
|
} |
||||
|
|
||||
|
return Hash; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "MoviePipelineImagePassBase.h" |
||||
|
#include "MovieRenderPipelineDataTypes.h" |
||||
|
#include "ArchVisMoviePipelineDeferredPassObjectId.generated.h" |
||||
|
|
||||
|
|
||||
|
enum class EArchVisMoviePipelineObjectIdPassIdType : uint8; |
||||
|
|
||||
|
UCLASS(BlueprintType) |
||||
|
class ARCHVISTOOLSEDITOR_API UArchVisMoviePipelineObjectIdRenderPass : public UMoviePipelineImagePassBase |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UArchVisMoviePipelineObjectIdRenderPass(); |
||||
|
|
||||
|
virtual UE::MoviePipeline::FImagePassCameraViewData GetCameraInfo(FMoviePipelineRenderPassMetrics& InOutSampleState, IViewCalcPayload* OptPayload = nullptr) const override; |
||||
|
|
||||
|
public: |
||||
|
|
||||
|
virtual FText GetDisplayText() const override |
||||
|
{ |
||||
|
return INVTEXT("[ArchVis] Object Ids (Limited)"); |
||||
|
} |
||||
|
protected: |
||||
|
virtual int32 GetNumCamerasToRender() const; |
||||
|
public: |
||||
|
virtual void GetViewShowFlags(FEngineShowFlags& OutShowFlag, EViewModeIndex& OutViewModeIndex) const override; |
||||
|
virtual void RenderSample_GameThreadImpl(const FMoviePipelineRenderPassMetrics& InSampleState) override; |
||||
|
virtual void TeardownImpl() override; |
||||
|
virtual void SetupImpl(const MoviePipeline::FMoviePipelineRenderPassInitSettings& InPassInitSettings) override; |
||||
|
virtual void GatherOutputPassesImpl(TArray<FMoviePipelinePassIdentifier>& ExpectedRenderPasses) override; |
||||
|
virtual bool IsScreenPercentageSupported() const override { return false; } |
||||
|
virtual int32 GetOutputFileSortingOrder() const override { return 10; } |
||||
|
virtual void UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const override; |
||||
|
|
||||
|
virtual TWeakObjectPtr<UTextureRenderTarget2D> CreateViewRenderTargetImpl(const FIntPoint& InSize, IViewCalcPayload* OptPayload = nullptr) const override; |
||||
|
virtual TSharedPtr<FMoviePipelineSurfaceQueue, ESPMode::ThreadSafe> CreateSurfaceQueueImpl(const FIntPoint& InSize, IViewCalcPayload* OptPayload = nullptr) const override; |
||||
|
|
||||
|
protected: |
||||
|
void PostRendererSubmission(const FMoviePipelineRenderPassMetrics& InSampleState); |
||||
|
virtual void AddViewExtensions(FSceneViewFamilyContext& InContext, FMoviePipelineRenderPassMetrics& InOutSampleState) override; |
||||
|
|
||||
|
private: |
||||
|
/** Gets the typename hash used as part of the cryptomatte metadata. Eg, "cryptomatte/<typename_hash>/..." */ |
||||
|
FString GetTypenameHash() const; |
||||
|
|
||||
|
public: |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") |
||||
|
EArchVisMoviePipelineObjectIdPassIdType IdType; |
||||
|
|
||||
|
/** If true, translucent objects will be included in the ObjectId pass (but as an opaque layer due to limitations). False will omit translucent objects. */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings") |
||||
|
bool bIncludeTranslucentObjects; |
||||
|
|
||||
|
private: |
||||
|
TSharedPtr<FAccumulatorPool, ESPMode::ThreadSafe> AccumulatorPool; |
||||
|
TArray<FMoviePipelinePassIdentifier> ExpectedPassIdentifiers; |
||||
|
bool bPrevAllowSelectTranslucent; |
||||
|
}; |
||||
@ -0,0 +1,359 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "ArchVisMovieRenderOverlappedMask.h" |
||||
|
#include "EngineUtils.h" |
||||
|
#include "HitProxies.h" |
||||
|
#include "MoviePipelineHashUtils.h" |
||||
|
#include "MovieRenderPipelineCoreModule.h" |
||||
|
#include "Components/InstancedStaticMeshComponent.h" |
||||
|
#include "Dom/JsonObject.h" |
||||
|
#include "Materials/Material.h" |
||||
|
#include "Materials/MaterialInstance.h" |
||||
|
#include "Materials/MaterialInstanceDynamic.h" |
||||
|
#include "Serialization/JsonSerializer.h" |
||||
|
#include "Serialization/JsonWriter.h" |
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.generated.h" |
||||
|
|
||||
|
|
||||
|
extern const TSparseArray<HHitProxy*>& GetAllHitProxies(); |
||||
|
|
||||
|
UENUM(BlueprintType) |
||||
|
enum class EArchVisMoviePipelineObjectIdPassIdType : uint8 |
||||
|
{ |
||||
|
/** As much information as the renderer can provide - unique per material per primitive in the world. */ |
||||
|
Full, |
||||
|
|
||||
|
/** Grouped by material name. This means different objects that use the same material will be merged. */ |
||||
|
Material, |
||||
|
|
||||
|
/** Grouped by Actor Name, all materials for a given actor are merged together, and all actors with that name are merged together as well. */ |
||||
|
Actor, |
||||
|
|
||||
|
/** Grouped by Actor Name and Folder Hierarchy. This means actors with the same name in different folders will not be merged together. */ |
||||
|
ActorWithHierarchy, |
||||
|
|
||||
|
/** Grouped by Folder Name. All actors within a given folder hierarchy in the World Outliner are merged together. */ |
||||
|
Folder, |
||||
|
|
||||
|
/**
|
||||
|
* Grouped by Actor Layer (the first layer found in the AActor::Layers array). May not do what you expect if an actor belongs to multiple layers. |
||||
|
* If used within a graph, this does NOT refer to the layer within the graph. |
||||
|
* |
||||
|
* In scripting, this option is referred to as "Layer" and not "ActorLayer". |
||||
|
*/ |
||||
|
Layer UMETA(DisplayName = "Actor Layer") |
||||
|
}; |
||||
|
|
||||
|
namespace ArchVisMoviePipeline |
||||
|
{ |
||||
|
struct FMoviePipelineHitProxyCacheKey |
||||
|
{ |
||||
|
const AActor* Actor; |
||||
|
const UPrimitiveComponent* PrimComponent; |
||||
|
|
||||
|
FMoviePipelineHitProxyCacheKey(const AActor* InActor, const UPrimitiveComponent* InComponent) |
||||
|
: Actor(InActor) |
||||
|
, PrimComponent(InComponent) |
||||
|
{} |
||||
|
|
||||
|
friend inline uint32 GetTypeHash(const FMoviePipelineHitProxyCacheKey& Key) |
||||
|
{ |
||||
|
return HashCombine(PointerHash(Key.Actor), PointerHash(Key.PrimComponent)); |
||||
|
} |
||||
|
|
||||
|
bool operator==(const FMoviePipelineHitProxyCacheKey& Other) const |
||||
|
{ |
||||
|
return (Actor == Other.Actor) && (PrimComponent == Other.PrimComponent); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
struct FMoviePipelineHitProxyCacheValue |
||||
|
{ |
||||
|
const AActor* Actor; |
||||
|
const UPrimitiveComponent* PrimComponent; |
||||
|
int32 SectionIndex; |
||||
|
int32 MaterialIndex; |
||||
|
float Hash; |
||||
|
FString HashAsString; |
||||
|
FString ProxyName; |
||||
|
}; |
||||
|
|
||||
|
struct FObjectIdAccelerationData |
||||
|
{ |
||||
|
FObjectIdAccelerationData() |
||||
|
{} |
||||
|
|
||||
|
FORCEINLINE bool IsDefault() const |
||||
|
{ |
||||
|
return !Cache.IsValid() |
||||
|
&& !JsonManifest.IsValid() |
||||
|
&& JsonManifestCachedOutput.Len() == 0 |
||||
|
&& PassIdentifierHashAsShortString.Len() == 0; |
||||
|
} |
||||
|
|
||||
|
/** Maps a HitProxy index to the data associated with the HitProxy. */ |
||||
|
TSharedPtr<TMap<int32, FMoviePipelineHitProxyCacheValue>> Cache; |
||||
|
|
||||
|
/** The Cryptomatte JSON manifest data. */ |
||||
|
TSharedPtr<FJsonObject> JsonManifest; |
||||
|
|
||||
|
/** A cached and serialized version of the JsonManifest data. */ |
||||
|
FString JsonManifestCachedOutput; |
||||
|
|
||||
|
/** The pass identifier that is used within Cryptomatte metadata keys like `cryptomatte/<pass_id>/name`. */ |
||||
|
FString PassIdentifierHashAsShortString; |
||||
|
}; |
||||
|
|
||||
|
struct FObjectIdMaskSampleAccumulationArgs : ::MoviePipeline::IMoviePipelineAccumulationArgs |
||||
|
{ |
||||
|
TSharedPtr<FMaskOverlappedAccumulator, ESPMode::ThreadSafe> Accumulator; |
||||
|
TSharedPtr<FMoviePipelineOutputMerger, ESPMode::ThreadSafe> OutputMerger; |
||||
|
int32 NumOutputLayers; |
||||
|
TSharedPtr<TMap<int32, FMoviePipelineHitProxyCacheValue>> CacheData; |
||||
|
}; |
||||
|
|
||||
|
static FString GenerateProxyIdGroup(const AActor* InActor, const UPrimitiveComponent* InPrimComponent, const EArchVisMoviePipelineObjectIdPassIdType IdType, const int32 InMaterialIndex, const int32 InSectionIndex) |
||||
|
{ |
||||
|
// If it doesn't exist in the cache already, then we will do the somewhat expensive of building the string and hashing it.
|
||||
|
TStringBuilder<128> StringBuilder; |
||||
|
|
||||
|
FName FolderPath = InActor->GetFolderPath(); |
||||
|
|
||||
|
// If they don't want the hierarchy, we'll just set this to empty string.
|
||||
|
if (IdType == EArchVisMoviePipelineObjectIdPassIdType::Actor) |
||||
|
{ |
||||
|
FolderPath = NAME_None; |
||||
|
} |
||||
|
|
||||
|
switch (IdType) |
||||
|
{ |
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::Layer: |
||||
|
{ |
||||
|
if (InActor->Layers.Num() > 0) |
||||
|
{ |
||||
|
StringBuilder.Append(*InActor->Layers[0].ToString()); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::Folder: |
||||
|
{ |
||||
|
if (!FolderPath.IsNone()) |
||||
|
{ |
||||
|
StringBuilder.Append(*FolderPath.ToString()); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::Material: |
||||
|
{ |
||||
|
if (InPrimComponent->GetNumMaterials() > 0) |
||||
|
{ |
||||
|
UMaterialInterface* MaterialInterface = InPrimComponent->GetMaterial(FMath::Clamp(InMaterialIndex, 0, InPrimComponent->GetNumMaterials() - 1)); |
||||
|
|
||||
|
// This collapses dynamic material instances back into their parent asset so we don't end up with 'MaterialInstanceDynamic_1' instead of MI_Foo
|
||||
|
if (const UMaterialInstanceDynamic* AsDynamicMaterialInstance = Cast<UMaterialInstanceDynamic>(MaterialInterface)) |
||||
|
{ |
||||
|
if (AsDynamicMaterialInstance->Parent) |
||||
|
{ |
||||
|
StringBuilder.Append(*AsDynamicMaterialInstance->Parent->GetName()); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
StringBuilder.Append(*AsDynamicMaterialInstance->GetName()); |
||||
|
} |
||||
|
} |
||||
|
else if (UMaterialInstance* AsMaterialInstance = Cast<UMaterialInstance>(MaterialInterface)) |
||||
|
{ |
||||
|
StringBuilder.Append(*MaterialInterface->GetName()); |
||||
|
} |
||||
|
else if (MaterialInterface && MaterialInterface->GetMaterial()) |
||||
|
{ |
||||
|
StringBuilder.Append(*MaterialInterface->GetMaterial()->GetName()); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::Actor: |
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::ActorWithHierarchy: |
||||
|
{ |
||||
|
// Folder Path will be NAME_None for root objects and for the "Actor" group type.
|
||||
|
if (!FolderPath.IsNone()) |
||||
|
{ |
||||
|
StringBuilder.Append(*FolderPath.ToString()); |
||||
|
StringBuilder.Append(TEXT("/")); |
||||
|
} |
||||
|
StringBuilder.Append(*InActor->GetActorLabel()); |
||||
|
break; |
||||
|
} |
||||
|
case EArchVisMoviePipelineObjectIdPassIdType::Full: |
||||
|
{ |
||||
|
// Full gives as much detail as we can - per folder, per actor, per component, per material
|
||||
|
if (!FolderPath.IsNone()) |
||||
|
{ |
||||
|
StringBuilder.Append(*FolderPath.ToString()); |
||||
|
StringBuilder.Append(TEXT("/")); |
||||
|
} |
||||
|
StringBuilder.Appendf(TEXT("%s.%s[%d.%d]"), *InActor->GetActorLabel(), *GetNameSafe(InPrimComponent), InMaterialIndex, InSectionIndex); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (StringBuilder.Len() == 0) |
||||
|
{ |
||||
|
StringBuilder.Append(TEXT("default")); |
||||
|
} |
||||
|
|
||||
|
return StringBuilder.ToString(); |
||||
|
} |
||||
|
|
||||
|
static void UpdateManifestAccelerationData(FObjectIdAccelerationData& InAccelData, const EArchVisMoviePipelineObjectIdPassIdType IdType) |
||||
|
{ |
||||
|
// The HitProxy array gets invalidated quite often, so the results are no longer valid in the accumulation thread.
|
||||
|
// To solve this, we will cache the required info on the game thread and pass the required info along with the render so that
|
||||
|
// it stays in sync with what was actually rendered. Additionally, we cache the hashes between frames as they will be largely
|
||||
|
// the same between each frame.
|
||||
|
const TSparseArray<HHitProxy*>& AllHitProxies = GetAllHitProxies(); |
||||
|
|
||||
|
std::atomic<int32> NumCacheHits(0); |
||||
|
std::atomic<int32> NumCacheMisses(0); |
||||
|
std::atomic<int32> NumCacheUpdates(0); |
||||
|
|
||||
|
// Update the data in place, no need to copy back to the annotation.
|
||||
|
const double CacheStartTime = FPlatformTime::Seconds(); |
||||
|
|
||||
|
for (typename TSparseArray<HHitProxy*>::TConstIterator It(AllHitProxies); It; ++It) |
||||
|
{ |
||||
|
const HActor* ActorHitProxy = HitProxyCast<HActor>(*It); |
||||
|
const HInstancedStaticMeshInstance* FoliageHitProxy = HitProxyCast<HInstancedStaticMeshInstance>(*It); |
||||
|
|
||||
|
const AActor* ProxyActor = nullptr; |
||||
|
const UPrimitiveComponent* ProxyComponent = nullptr; |
||||
|
int32 ProxySectionIndex = -1; |
||||
|
int32 ProxyMaterialIndex = -1; |
||||
|
|
||||
|
if (ActorHitProxy && IsValid(ActorHitProxy->Actor) && IsValid(ActorHitProxy->PrimComponent)) |
||||
|
{ |
||||
|
ProxyActor = ActorHitProxy->Actor; |
||||
|
ProxyComponent = ActorHitProxy->PrimComponent; |
||||
|
ProxySectionIndex = ActorHitProxy->SectionIndex; |
||||
|
ProxyMaterialIndex = ActorHitProxy->MaterialIndex; |
||||
|
} |
||||
|
else if (FoliageHitProxy && IsValid(FoliageHitProxy->Component)) |
||||
|
{ |
||||
|
ProxyActor = FoliageHitProxy->Component->GetOwner(); |
||||
|
ProxyComponent = FoliageHitProxy->Component; |
||||
|
ProxySectionIndex = FoliageHitProxy->InstanceIndex; |
||||
|
} |
||||
|
|
||||
|
if (ProxyActor && ProxyComponent) |
||||
|
{ |
||||
|
// We assume names to be stable within a shot. This is technically incorrect if you were to
|
||||
|
// rename an actor mid-frame but using this assumption allows us to skip calculating the string
|
||||
|
// name every frame.
|
||||
|
const FColor Color = (*It)->Id.GetColor(); |
||||
|
int32 IdToInt = (static_cast<int32>(Color.R) << 16) | (static_cast<int32>(Color.G) << 8) | (static_cast<int32>(Color.B) << 0); |
||||
|
|
||||
|
if (const FMoviePipelineHitProxyCacheValue* CacheEntry = InAccelData.Cache->Find(IdToInt)) |
||||
|
{ |
||||
|
// The cache could be out of date since it's only an index. We'll double check that the actor and component
|
||||
|
// are the same and assume if they are, the cache is still valid.
|
||||
|
const bool bSameActor = CacheEntry->Actor == ProxyActor; |
||||
|
const bool bSameComp = CacheEntry->PrimComponent == ProxyComponent; |
||||
|
const bool bSameSection = CacheEntry->SectionIndex == ProxySectionIndex; |
||||
|
const bool bSameMaterial = CacheEntry->MaterialIndex == ProxyMaterialIndex; |
||||
|
|
||||
|
if (bSameActor && bSameComp && bSameSection && bSameMaterial) |
||||
|
{ |
||||
|
++NumCacheHits; |
||||
|
continue; |
||||
|
} |
||||
|
++NumCacheUpdates; |
||||
|
} |
||||
|
++NumCacheMisses; |
||||
|
|
||||
|
// We hash the string and printf it here to reduce allocations later, even though it makes this loop ~% more expensive.
|
||||
|
{ |
||||
|
FString ProxyIdName = GenerateProxyIdGroup(ProxyActor, ProxyComponent, IdType, ProxyMaterialIndex, ProxySectionIndex); |
||||
|
uint32 Hash = ::MoviePipeline::HashNameToId(TCHAR_TO_UTF8(*ProxyIdName)); |
||||
|
FString HashAsString = FString::Printf(TEXT("%08x"), Hash); |
||||
|
|
||||
|
FMoviePipelineHitProxyCacheValue& NewCacheEntry = InAccelData.Cache->Add(IdToInt); |
||||
|
NewCacheEntry.ProxyName = ProxyIdName; |
||||
|
NewCacheEntry.Hash = *reinterpret_cast<float*>(&Hash); |
||||
|
NewCacheEntry.Actor = ProxyActor; |
||||
|
NewCacheEntry.PrimComponent = ProxyComponent; |
||||
|
NewCacheEntry.SectionIndex = ProxySectionIndex; |
||||
|
NewCacheEntry.MaterialIndex = ProxyMaterialIndex; |
||||
|
|
||||
|
// Add the object to the manifest. Done here because this takes ~170ms a frame for 700 objects.
|
||||
|
// May as well only take that hit once per shot. This will add or update an existing field.
|
||||
|
InAccelData.JsonManifest->SetStringField(ProxyIdName, HashAsString); |
||||
|
|
||||
|
// Only move it after we've used it to update the Json Manifest.
|
||||
|
NewCacheEntry.HashAsString = MoveTemp(HashAsString); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const double CacheEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedMs = static_cast<float>((CacheEndTime - CacheStartTime) * 1000.0f); |
||||
|
|
||||
|
const double JsonBeginTime = FPlatformTime::Seconds(); |
||||
|
|
||||
|
// Update the cached JSON if needed.
|
||||
|
// We only update the serialized manifest JSON if something has changed because serializing is slow.
|
||||
|
if (NumCacheMisses.load() > 0) |
||||
|
{ |
||||
|
InAccelData.JsonManifestCachedOutput.Empty(); |
||||
|
|
||||
|
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&InAccelData.JsonManifestCachedOutput); |
||||
|
FJsonSerializer::Serialize(InAccelData.JsonManifest.ToSharedRef(), Writer); |
||||
|
} |
||||
|
|
||||
|
const double JsonEndTime = FPlatformTime::Seconds(); |
||||
|
const float ElapsedJsonMs = static_cast<float>((JsonEndTime - JsonBeginTime) * 1000.0f); |
||||
|
|
||||
|
UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("Cache Size: %d NumCacheHits: %d NumCacheMisses: %d NumCacheUpdates: %d CacheDuration: %8.2fms JsonDuration: %8.2fms"), InAccelData.Cache->Num(), NumCacheHits.load(), NumCacheMisses.load(), NumCacheUpdates.load(), ElapsedMs, ElapsedJsonMs); |
||||
|
} |
||||
|
|
||||
|
static void RemapHitProxyIdToCryptomatteHash(const FIntPoint& InImageSize, const FColor* InHitProxyBuffer, const TSharedPtr<TMap<int32, FMoviePipelineHitProxyCacheValue>> InHitProxyCache, TArray64<float>& OutCryptomatteBuffer) |
||||
|
{ |
||||
|
static const uint32 DefaultHash = ::MoviePipeline::HashNameToId(TCHAR_TO_UTF8(TEXT("default"))); |
||||
|
static const float DefaultHashAsFloat = *(float*)(&DefaultHash); |
||||
|
|
||||
|
ParallelFor(InImageSize.Y, |
||||
|
[&](const int32 ScanlineIndex = 0) |
||||
|
{ |
||||
|
for (int64 Index = 0; Index < InImageSize.X; Index++) |
||||
|
{ |
||||
|
int64 DstIndex = int64(ScanlineIndex) * int64(InImageSize.X) + int64(Index); |
||||
|
const FColor* Color = &InHitProxyBuffer[DstIndex]; |
||||
|
|
||||
|
// Turn the FColor into an integer index
|
||||
|
int32 HitProxyIndex = ((int32)Color->R << 16) | ((int32)Color->G << 8) | ((int32)Color->B << 0); |
||||
|
|
||||
|
float Hash = DefaultHashAsFloat; |
||||
|
if (const FMoviePipelineHitProxyCacheValue* CachedValue = InHitProxyCache->Find(HitProxyIndex)) |
||||
|
{ |
||||
|
Hash = CachedValue->Hash; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogMovieRenderPipeline, VeryVerbose, TEXT("Failed to find cache data for Hitproxy! Id: %d"), HitProxyIndex); |
||||
|
} |
||||
|
|
||||
|
OutCryptomatteBuffer[DstIndex] = Hash; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
static void UpdateCryptomatteMetadata(const FObjectIdAccelerationData& InAccelData, const FString& InTypenameHash, const FString& InLayerName, TMap<FString, FString>& InOutMetadataMap) |
||||
|
{ |
||||
|
InOutMetadataMap.Add(FString::Printf(TEXT("cryptomatte/%s/manifest"), *InTypenameHash), InAccelData.JsonManifestCachedOutput); |
||||
|
InOutMetadataMap.Add(FString::Printf(TEXT("cryptomatte/%s/name"), *InTypenameHash), InLayerName); |
||||
|
InOutMetadataMap.Add(FString::Printf(TEXT("cryptomatte/%s/hash"), *InTypenameHash), TEXT("MurmurHash3_32")); |
||||
|
InOutMetadataMap.Add(FString::Printf(TEXT("cryptomatte/%s/conversion"), *InTypenameHash), TEXT("uint32_to_float32")); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "MovieRenderOverlappedImage.h" |
||||
|
|
||||
|
|
||||
|
namespace ArchVisMoviePipeline |
||||
|
{ |
||||
|
struct FMaskPixelSamples |
||||
|
{ |
||||
|
FMaskPixelSamples() |
||||
|
{ |
||||
|
for (int32 Index = 0; Index < 6; Index++) |
||||
|
{ |
||||
|
Id[Index] = -1; |
||||
|
Weight[Index] = 0; |
||||
|
} |
||||
|
ExtraDataOffset = -1; |
||||
|
} |
||||
|
int32 Id[6]; |
||||
|
float Weight[6]; |
||||
|
int32 ExtraDataOffset; |
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* Contains all the image planes for the tiles. |
||||
|
*/ |
||||
|
struct FMaskOverlappedAccumulator : MoviePipeline::IMoviePipelineOverlappedAccumulator |
||||
|
{ |
||||
|
public: |
||||
|
/**
|
||||
|
* Default constructor. |
||||
|
*/ |
||||
|
FMaskOverlappedAccumulator() |
||||
|
: bIsInitialized(false) |
||||
|
, PlaneSize(0,0) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
static FName GetName() |
||||
|
{ |
||||
|
static FName Name = FName("MaskOverlappedAccumulator"); |
||||
|
return Name; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Allocates memory. |
||||
|
* |
||||
|
* @param InTileSizeX - Horizontal tile size. |
||||
|
* @param InTileSizeY - Vertical tile size. |
||||
|
* @param InNumTilesX - Num horizontal tiles. |
||||
|
* @param InNumTilesY - Num vertical tiles. |
||||
|
* @param InNumChannels - Num Channels. |
||||
|
*/ |
||||
|
void InitMemory(FIntPoint InPlaneSize); |
||||
|
|
||||
|
/**
|
||||
|
* Initializes memory. |
||||
|
* |
||||
|
* Resets the memory to 0s so that we can start a new frame. |
||||
|
*/ |
||||
|
void ZeroPlanes(); |
||||
|
|
||||
|
/**
|
||||
|
* Resets the memory. |
||||
|
*/ |
||||
|
void Reset(); |
||||
|
|
||||
|
/**
|
||||
|
* Given a rendered tile, accumulate the data to the full size image. |
||||
|
* |
||||
|
* @param InPixelData - Raw pixel data. |
||||
|
* @param InTileOffset - Tile offset in pixels. |
||||
|
* @param InSubPixelOffset - SubPixel offset, should be in the range [0,1) |
||||
|
* @param WeightX - 1D Weights in X |
||||
|
* @param WeightY - 1D Weights in Y |
||||
|
*/ |
||||
|
void AccumulatePixelData(float* InPixelData, FIntPoint InPixelDataSize, FIntPoint InTileOffset, FVector2D InSubpixelOffset, const MoviePipeline::FTileWeight1D & WeightX, const MoviePipeline::FTileWeight1D & WeightY); |
||||
|
void AccumulatePixelData(const TArray<float>& InLayerIds, const FColor* InPixelWeights, FIntPoint InPixelDataSize, FIntPoint InTileOffset, FVector2D InSubpixelOffset, const MoviePipeline::FTileWeight1D& WeightX, const MoviePipeline::FTileWeight1D& WeightY); |
||||
|
|
||||
|
void FetchFinalPixelDataLinearColor(TArray<TArray64<FLinearColor>>& OutPixelDataLayers) const; |
||||
|
|
||||
|
protected: |
||||
|
void AccumulateSingleRank(const float* InRawData, FIntPoint InSize, FIntPoint InOffset, |
||||
|
float SubpixelOffsetX, float SubpixelOffsetY, |
||||
|
FIntPoint SubRectOffset, |
||||
|
FIntPoint SubRectSize, |
||||
|
const TArray<float>& WeightDataX, |
||||
|
const TArray<float>& WeightDataY); |
||||
|
|
||||
|
void AccumulateMultipleRanks(const TArray<float>& InRankIds, const FColor* InPixelWeights, FIntPoint InSize, FIntPoint InOffset, |
||||
|
float SubpixelOffsetX, float SubpixelOffsetY, |
||||
|
FIntPoint SubRectOffset, |
||||
|
FIntPoint SubRectSize, |
||||
|
const TArray<float>& WeightDataX, |
||||
|
const TArray<float>& WeightDataY); |
||||
|
public: |
||||
|
/** Has InitMemory been called to allocate memory for this accumulator. */ |
||||
|
bool bIsInitialized; |
||||
|
|
||||
|
/** Width and height of each tile in pixels */ |
||||
|
FIntPoint PlaneSize; |
||||
|
|
||||
|
/** The data for this image. One entry per pixel in the output image. */ |
||||
|
TArray64<FMaskPixelSamples> ImageData; |
||||
|
|
||||
|
/** Sparse data for pixels that overflow the initial memory block. Use the offset from ImageData to index into this. */ |
||||
|
TArray64<TSharedPtr<FMaskPixelSamples, ESPMode::ThreadSafe>> SparsePixelData; |
||||
|
|
||||
|
/** A grayscale mask used to generate the falloff needed for overlapped tiles. */ |
||||
|
FImageOverlappedPlane WeightPlane; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,23 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
|
||||
|
|
||||
|
class FArchVisToolsEditorModule : public IModuleInterface |
||||
|
{ |
||||
|
public: |
||||
|
|
||||
|
/** IModuleInterface implementation */ |
||||
|
virtual void StartupModule() override; |
||||
|
virtual void ShutdownModule() override; |
||||
|
virtual bool SupportsDynamicReloading() override { return true; } |
||||
|
|
||||
|
protected: |
||||
|
void RegisterPlacementModeExtensions(); |
||||
|
void UnregisterPlacementModeExtensions(); |
||||
|
|
||||
|
private: |
||||
|
TArray<TOptional<class FPlacementModeID>> PlaceActors; |
||||
|
}; |
||||
@ -0,0 +1,117 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Runtime/Launch/Resources/Version.h" |
||||
|
|
||||
|
|
||||
|
DECLARE_LOG_CATEGORY_EXTERN(LogArchVisToolsEditor, Log, All); |
||||
|
|
||||
|
#ifndef LOG |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Display, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOG(Format, ...) __LOG__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOG__(LogCategory, Format, ...) UE_LOG(LogCategory, Display, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOG_H |
||||
|
/**
|
||||
|
* @brief LOG + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Display, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOG_H(Format, ...) __LOGH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGH__(LogCategory, Format, ...) UE_LOG(LogCategory, Display, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOGV |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Log, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOGV(Format, ...) __LOGV__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGV__(LogCategory, Format, ...) UE_LOG(LogCategory, Log, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef LOGV_H |
||||
|
/**
|
||||
|
* @brief LOG VERBOSE + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Log, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define LOGV_H(Format, ...) __LOGVH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __LOGVH__(LogCategory, Format, ...) UE_LOG(LogCategory, Log, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WARN |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Warning, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define WARN(Format, ...) __WARN__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __WARN__(LogCategory, Format, ...) UE_LOG(LogCategory, Warning, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WARN_H |
||||
|
/**
|
||||
|
* @brief WARN + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Warning, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define WARN_H(Format, ...) __WARNH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __WARNH__(LogCategory, Format, ...) UE_LOG(LogCategory, Warning, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef ERROR |
||||
|
/**
|
||||
|
* @brief Calls UE_LOG(CURRENT_LOG_CATEGORY, Error, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define ERROR(Format, ...) __ERROR__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __ERROR__(LogCategory, Format, ...) UE_LOG(LogCategory, Error, TEXT(Format), ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef ERROR_H |
||||
|
/**
|
||||
|
* @brief ERROR + HERE. Calls UE_LOG(CURRENT_LOG_CATEGORY, Error, ...). Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define ERROR_H(Format, ...) __ERRORH__(CURRENT_LOG_CATEGORY, Format, ##__VA_ARGS__); |
||||
|
#define __ERRORH__(LogCategory, Format, ...) UE_LOG(LogCategory, Error, TEXT("[%hs] " Format), __FUNCTION__, ##__VA_ARGS__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef __FILENAME__ |
||||
|
/**
|
||||
|
* @brief Returns the current Filename |
||||
|
*/ |
||||
|
#define __FILENAME__ (wcsrchr(TEXT(__FILE__), '\\') ? wcsrchr(TEXT(__FILE__), '\\') + 1 : TEXT(__FILE__)) |
||||
|
#endif |
||||
|
|
||||
|
#ifndef WHERE |
||||
|
/**
|
||||
|
* @brief Logs where this function has been called. Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define WHERE LOG("%s - R%i - %hs", __FILENAME__, __LINE__, __FUNCTION__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef HERE |
||||
|
/**
|
||||
|
* @brief Prints the current function name in the Output. Make sure to define CURRENT_LOG_CATEGORY first with |
||||
|
* #define CURRENT_LOG_CATEGORY LogArchVisToolsEditor |
||||
|
*/ |
||||
|
#define HERE LOGV(" ==== [%hs] ==== ", __FUNCTION__); |
||||
|
#endif |
||||
|
|
||||
|
#ifndef BtoS |
||||
|
/**
|
||||
|
* @brief Converts a bool to *CHAR for logging |
||||
|
*/ |
||||
|
#define BtoS(var) var ? TEXT("True") : TEXT("False") |
||||
|
#endif |
||||
|
|
||||
|
|
||||
|
#define DEFINITION_FILE "ArchVisToolsEditorDefinitions" |
||||
|
#pragma message(" [" DEFINITION_FILE "] COMPILING ON UE `" PREPROCESSOR_TO_STRING(ENGINE_MAJOR_VERSION) "`.`" PREPROCESSOR_TO_STRING(ENGINE_MINOR_VERSION) "`.`" PREPROCESSOR_TO_STRING(ENGINE_PATCH_VERSION) "`") |
||||
|
|
||||
|
#undef DEFINITION_FILE |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPBuildAdjacency.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Build Adjacency", ToolTip = "[ArchVisTools] To properly create crack free tesselations, meshes need to have their adjacency built. Use this to automatically set it for imported meshes. DOES NOT CURRENTLY WORK ON UE5")) |
||||
|
class UE_DEPRECATED(5.0, "Tesselation is not suported in UE5.") ARCHVISTOOLSEDITOR_API UDPBuildAdjacency : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
//~ Begin UDataprepOperation Interface
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::MeshOperation; |
||||
|
} |
||||
|
|
||||
|
//~ End UDataprepOperation Interface
|
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,20 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "GameFramework/Actor.h" |
||||
|
#include "DPDebugActor.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* @brief A base class Actor used for debugging in the Dataprep World. Doesn't have any other functionality. |
||||
|
*/ |
||||
|
UCLASS(Blueprintable) |
||||
|
class ARCHVISTOOLSEDITOR_API ADPDebugActor : public AActor |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
ADPDebugActor(); |
||||
|
}; |
||||
@ -0,0 +1,58 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "DataprepArchVisEditingOperation.h" |
||||
|
#include "DPDebugActorBoundingBoxOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = "Debug", Meta = (DisplayName = "Debug: Actor Bounding Box", ToolTip = "[ArchVisTools] Create a Bounding Box representing the bounds of of each Actor. Used for debugging.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPDebugActorBoundingBoxOperation : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return INVTEXT("Debug"); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the Operation will generate a separate Actor that will hold the BoundingBox. |
||||
|
* Otherwise it will be created as a component of the selected Actors. |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Debug Bounding Box") |
||||
|
bool bGenerateSeparateActor; |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the BoundingBox is recomputed from the exact vertices location instead of the easily available BoundingBox. |
||||
|
* This is therefore more accurate especially for rotated meshes, but this is a slower operation. |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Debug Bounding Box") |
||||
|
bool bUseAccurateBoundingBox; |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the BoundingBox will be locally aligned instead of the default World Aligned one. |
||||
|
* Works best with `Use Accurate BoundingBox` also turned on. |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Debug Bounding Box") |
||||
|
bool bUseAlignedBoundingBox; |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the BoundingBox will contain the selected Actor and all its Attached Children. |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Debug Bounding Box") |
||||
|
bool bIncludeChildren; |
||||
|
|
||||
|
/**
|
||||
|
* @brief The Thickness of the Mesh composing the BoundingBox |
||||
|
*/ |
||||
|
UPROPERTY(AdvancedDisplay, EditAnywhere, meta=(ClampMin = "0.001"), Category="Debug Bounding Box") |
||||
|
float Thickness = 3.f; |
||||
|
|
||||
|
protected: |
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "DataprepArchVisEditingOperation.h" |
||||
|
#include "DPDebugActorLocationOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = "Debug", Meta = (DisplayName = "Debug: Actor Location", ToolTip = "[ArchVisTools] Create an Axis Mesh at the Origin of each Actor. Used for debugging.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPDebugActorLocationOperation : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return INVTEXT("Debug"); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the Operation will generate a separate Actor that will hold the Axis. |
||||
|
* Otherwise it will be created as a component of the selected Actors. |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Debug Location") |
||||
|
bool bGenerateSeparateActor; |
||||
|
|
||||
|
protected: |
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPDebugRemoveDebugData.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = "Debug", Meta = (DisplayName = "Debug: Delete Debug Data", |
||||
|
ToolTip = "[ArchVisTools] Delete the Debug Actors and Components that have been created. Usually one of the last step of the recipe before committing.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPDebugRemoveDebugData : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return INVTEXT("Debug"); |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPExplode.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Explode", ToolTip = "[ArchVisTools] Similar to a Sketchup or Autocad Explode, brings the children of the selected actors one step higher into the hierarchy.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPExplode : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
//~ Begin UDataprepOperation Interface
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::ActorOperation; |
||||
|
} |
||||
|
|
||||
|
//~ End UDataprepOperation Interface
|
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true the exploded Actors will be deleted |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Explode") |
||||
|
bool bDeleteExplodedActors; |
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,33 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPFaceCamera.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Face Camera", ToolTip = "[ArchVisTools] Makes the Actor follow the Camera. Will work properly on Components which had the option 'Always face camera' turned on in SketchUp, but might not work properly on other components.\n DOESN'T SEEM TO WORK ON UE5 PREVIEW 1. It can still be added manually afterwards.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPFaceCamera : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
//~ Begin UDataprepOperation Interface
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::ActorOperation; |
||||
|
} |
||||
|
|
||||
|
//~ End UDataprepOperation Interface
|
||||
|
|
||||
|
/**
|
||||
|
* @brief The Name of the Tag to look for in the Actors to apply the component |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Face Camera") |
||||
|
FName TagName = FName("SU.BEHAVIOR.FaceCamera"); |
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "SelectionSystem/DataprepStringsArrayFetcher.h" |
||||
|
#include "DPMaterialName.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Material Name", ToolTip = "[ArchVisTools] Get the material names of static meshes")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPMaterialName : public UDataprepStringsArrayFetcher |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
virtual FText GetAdditionalKeyword_Implementation() const override |
||||
|
{ |
||||
|
return INVTEXT("ArchVisTools"); |
||||
|
} |
||||
|
|
||||
|
virtual TArray<FString> Fetch_Implementation(const UObject* Object, bool& bOutFetchSucceeded) const override; |
||||
|
}; |
||||
@ -0,0 +1,54 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "DataprepArchVisEditingOperation.h" |
||||
|
#include "DPMergeActorsOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Branch Merge", |
||||
|
ToolTip = "[ArchVisTools] Collect geometry from selected actors and merge them into single mesh.")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPMergeActorsOperation : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
UDPMergeActorsOperation() : |
||||
|
PivotPointAlignment(EPivotPointAlignmentMode::BranchRootActor) |
||||
|
, MergedActors(TArray<AStaticMeshActor*>()) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::ActorOperation; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Rule for the location and alignment of the Pivot Point of the Merged Actor |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Branch Merge") |
||||
|
EPivotPointAlignmentMode PivotPointAlignment; |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the siblings of the root Actors will be merged together |
||||
|
*/ |
||||
|
UPROPERTY(EditAnywhere, Category="Branch Merge") |
||||
|
bool bMergeRootSiblings; |
||||
|
|
||||
|
/**
|
||||
|
* @brief If set to true, the Merged Actor will be given a component to show its Axis Location. |
||||
|
* Useful to assess the difference between the different Pivot Alignment options. |
||||
|
*/ |
||||
|
UPROPERTY(AdvancedDisplay, EditAnywhere, Category="Branch Merge") |
||||
|
bool bCreateDebugAxisAtMergedActorLocation; |
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
|
||||
|
protected: |
||||
|
TArray<AStaticMeshActor*> MergedActors; |
||||
|
}; |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPReplaceCineCameraOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Replace Camera", ToolTip = "[ArchVisTools] Replace the CineCameraActors with ArchVisCineCameraActors")) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPReplaceCineCameraOperation : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
//~ Begin UDataprepOperation Interface
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::ActorOperation; |
||||
|
} |
||||
|
|
||||
|
//~ End UDataprepOperation Interface
|
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,45 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Dataprep/DataprepArchVisEditingOperation.h" |
||||
|
#include "DPSimplifyHierarchyOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* |
||||
|
*/ |
||||
|
UCLASS(Category = ObjectOperation, Meta = (DisplayName = "Simplify Hierarchy", |
||||
|
ToolTip = |
||||
|
"[ArchVisTools] By defaults, Dataprep creates an Actor for a Sketchup Group or Component, and a child StaticMeshActor for its Mesh. This nodes replace the Group Actor with the StaticMeshActor" |
||||
|
)) |
||||
|
class ARCHVISTOOLSEDITOR_API UDPSimplifyHierarchyOperation : public UDataprepArchVisEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
UDPSimplifyHierarchyOperation() : |
||||
|
TagName(FString(TEXT("SU.GUID."))) |
||||
|
, bMergeMultipleMeshes(true) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
//~ Begin UDataprepOperation Interface
|
||||
|
virtual FText GetCategoryArchVis() const override |
||||
|
{ |
||||
|
return FDataprepOperationCategories::ActorOperation; |
||||
|
} |
||||
|
|
||||
|
//~ End UDataprepOperation Interface
|
||||
|
|
||||
|
|
||||
|
/** @brief The name of the Tag that matches between the Actors and their StaticMeshActors */ |
||||
|
UPROPERTY(EditAnywhere, Category="Simplify Hierarchy") |
||||
|
FString TagName; |
||||
|
|
||||
|
/** @brief If a Group Actor is composed of multiple meshes and this option is set to true, merge the Meshes into one, otherwise leaves it untouched */ |
||||
|
UPROPERTY(EditAnywhere, Category="Simplify Hierarchy") |
||||
|
bool bMergeMultipleMeshes; |
||||
|
|
||||
|
virtual void OnExecution_Implementation(const FDataprepContext& InContext) override; |
||||
|
}; |
||||
@ -0,0 +1,578 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "DataprepOperation.h" |
||||
|
#include "DataprepArchVisEditingOperation.generated.h" |
||||
|
|
||||
|
|
||||
|
class UFaceCameraComponent; |
||||
|
UENUM(BlueprintType) |
||||
|
enum class EPivotPointAlignmentMode : uint8 |
||||
|
{ |
||||
|
WorldOrigin UMETA(ToolTip = "The pivot point wll be aligned to the World Origin"), |
||||
|
ContextOrigin UMETA(ToolTip = "The pivot point wll be aligned to the Origin of the Branch's Context"), |
||||
|
BranchRootActor UMETA(ToolTip = "The pivot point wll be aligned to the root Actor of the Branch. If Merge Siblings Option is selected, will fall back on ContextOrigin."), |
||||
|
}; |
||||
|
|
||||
|
/**
|
||||
|
* @brief A class used to hold hierarchy information of Actors within a DataPrep Selection |
||||
|
*/ |
||||
|
UCLASS(Transient, Blueprintable) |
||||
|
class UBranchActor : public UObject |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
UBranchActor() : Actor(nullptr), Depth(0), Parent(nullptr) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* @brief Creates and initializes the Branch Actor with the necessary data |
||||
|
* @param A The Actor itself |
||||
|
* @return Returns the newly created and initialized BranchActor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Create New Branch Actor"), Category="Branch Actor") |
||||
|
static UBranchActor* CreateNew(AActor* A); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Add a Child to this Branch Actor. Should be used instead of directly adding to the Children Array as it ensures that all the data matches. |
||||
|
* @param Child The Child of this Branch Actor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Branch Actor") |
||||
|
void AddChild(UBranchActor* Child); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Return an Array of all Actors within the hierarchy of this Actor. Does not include Siblings |
||||
|
* @tparam T The Type of the Array returned. Should be UObject or AActor |
||||
|
* @return Return an Array of all Actors within the hierarchy of this Actor. Does not include Siblings |
||||
|
*/ |
||||
|
template <class T> |
||||
|
TArray<T*> GetActorArray(); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Return an Array of all BranchActors within the hierarchy of this BranchActor. Does not include Siblings |
||||
|
* @return Return an Array of all BranchActors within the hierarchy of this BranchActor. Does not include Siblings |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Branch Actor") |
||||
|
TArray<UBranchActor*> ToArray(); |
||||
|
|
||||
|
/**
|
||||
|
* @brief The Actor this BranchActor references |
||||
|
*/ |
||||
|
UPROPERTY(BlueprintReadOnly, Category="Branch Actor") |
||||
|
AActor* Actor; |
||||
|
/**
|
||||
|
* @brief The Depth of this Actor within its hierarchy |
||||
|
*/ |
||||
|
UPROPERTY(BlueprintReadOnly, Category="Branch Actor") |
||||
|
int32 Depth; |
||||
|
/**
|
||||
|
* @brief The Parent of this Actor within the Dataprep Selection hierarchy. Do not set directly |
||||
|
*/ |
||||
|
UPROPERTY(BlueprintReadOnly, Category="Branch Actor") |
||||
|
UBranchActor* Parent; |
||||
|
/**
|
||||
|
* @brief The Children of this Actor within the Dataprep Selection hierarchy. Do not set or add to it directly |
||||
|
*/ |
||||
|
UPROPERTY(BlueprintReadOnly, Category="Branch Actor") |
||||
|
TArray<UBranchActor*> Children; |
||||
|
/**
|
||||
|
* @brief The Siblings of this Actor within the Dataprep Selection hierarchy. Do not set or add to it directly apart for Root Actors |
||||
|
*/ |
||||
|
UPROPERTY(BlueprintReadOnly, Category="Branch Actor") |
||||
|
TArray<UBranchActor*> Siblings; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* A DataprepEditingOperation with additional helper functions |
||||
|
*/ |
||||
|
UCLASS(Abstract, Blueprintable) |
||||
|
class ARCHVISTOOLSEDITOR_API UDataprepArchVisEditingOperation : public UDataprepEditingOperation |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
UDataprepArchVisEditingOperation() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
virtual FText GetCategory_Implementation() const override |
||||
|
{ |
||||
|
return FText::Format(INVTEXT("[ArchVisTools] {0}"), GetCategoryArchVis()); |
||||
|
} |
||||
|
/**
|
||||
|
* @brief Return the Category of the Operation. Called internally by GetCategory_Implementation |
||||
|
*/ |
||||
|
virtual FText GetCategoryArchVis() const |
||||
|
{ |
||||
|
return INVTEXT(""); |
||||
|
} |
||||
|
virtual FText GetAdditionalKeyword_Implementation() const override |
||||
|
{ |
||||
|
return INVTEXT("ArchVisTools"); |
||||
|
} |
||||
|
|
||||
|
static const FName Name_Tag_Debug; |
||||
|
static const FName Name_Tag_DebugAxisLocation_Label; |
||||
|
static const FName Name_Tag_DebugAxisLocation_Component; |
||||
|
static const FName Name_Tag_DebugAxisLocation_Actor; |
||||
|
static const FName Name_Tag_DebugBoundingBox_Label; |
||||
|
static const FName Name_Tag_DebugBoundingBox_Component; |
||||
|
static const FName Name_Tag_DebugBoundingBox_Actor; |
||||
|
static const FName Name_Tag_Merged_Actor; |
||||
|
static const FName Name_Tag_DebugActorOwner; |
||||
|
|
||||
|
/**
|
||||
|
* @brief Get the Bounding Box of the given Actor |
||||
|
* @param Actor The Actor to generate the BoundingBox From |
||||
|
* @param Orientation World to Actor transform representing the Orientation of the Bounding Box. (Inverse of Actor->GetTransform()) Pass FTransform::Identity for a World Aligned Bounding Box |
||||
|
* @param bIncludeChildren If set to true, will Include the Children in the Computation of the Bounding Box |
||||
|
* @param bAccurate If set to true, will recompute the BoundingBox based on vertices location rather than the default value from the Engine. |
||||
|
* @return Return the World Aligned BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static FBox ComputeActorBoundingBox(const AActor* Actor, const FTransform Orientation, const bool bIncludeChildren = false, const bool bAccurate = false); |
||||
|
/**
|
||||
|
* @brief Computes the Depth of the Actor in its hierarchy. Returns 0 if it doesn't have a Parent. |
||||
|
* @param Actor The Actor to compute the Depth of |
||||
|
* @return Returns the Depth of the Actor in its hierarchy |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static int32 ComputeActorAttachedDepth(const AActor* Actor); |
||||
|
/**
|
||||
|
* @brief Compute the hierarchy of the given selection of Actors |
||||
|
* @param Actors The Actors to process |
||||
|
* @return Returns an Array of Top Level Actors |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static TArray<UBranchActor*> ComputeHierarchy(const TArray<AActor*>& Actors); |
||||
|
|
||||
|
public: |
||||
|
// --- Fetchers
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Get the material names of static meshes. |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Fetch") |
||||
|
static TArray<FString> FetchMaterialName(const UObject* Object, bool& bOutFetchSucceeded); |
||||
|
|
||||
|
|
||||
|
protected: |
||||
|
// --- Access Debug Static Meshes
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Gets the Axis Static Mesh. Using this functions allows the creation of only one new Asset. |
||||
|
* @return Returns the Axis StaticMesh or null if there was a problem |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMesh* GetAxisStaticMesh(); |
||||
|
/**
|
||||
|
* @brief Gets the Unit Cylinder Static Mesh Oriented on the X Axis. Using this functions allows the creation of only one new Asset. |
||||
|
* @return Returns the Cylinder StaticMesh or null if there was a problem |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMesh* GetCylinderStaticMeshX(); |
||||
|
/**
|
||||
|
* @brief Gets the Unit Cylinder Static Mesh Oriented on the X Axis. Using this functions allows the creation of only one new Asset. |
||||
|
* @return Returns the Cylinder StaticMesh or null if there was a problem |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMesh* GetCylinderStaticMeshY(); |
||||
|
/**
|
||||
|
* @brief Gets the Unit Cylinder Static Mesh Oriented on the X Axis. Using this functions allows the creation of only one new Asset. |
||||
|
* @return Returns the Cylinder StaticMesh or null if there was a problem |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMesh* GetCylinderStaticMeshZ(); |
||||
|
|
||||
|
// --- Remove Debug Components
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Check if the given Component has been created via one of the Debug Function |
||||
|
* @param Component The Component to check |
||||
|
* @return Returns true if the Component is a Debug Component (has the Tag Name_Tag_Debug), false otherwise. |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintPure, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
static FORCEINLINE bool DebugIsDebugComponent(const UActorComponent* Component) |
||||
|
{ |
||||
|
return Component && Component->ComponentTags.Contains(Name_Tag_Debug); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* @brief Check if the given Actor has been created via one of the Debug Function |
||||
|
* @param Actor The Actor to check |
||||
|
* @return Returns true if the Actor is a Debug Actor (has the Tag Name_Tag_Debug), false otherwise. |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintPure, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
static FORCEINLINE bool DebugIsDebugActor(const AActor* Actor) |
||||
|
{ |
||||
|
return Actor && Actor->Tags.Contains(Name_Tag_Debug); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* @brief Remove the Debug Data from the given Actor. If the Actor is a Debug Actor, it will get deleted. If it has any Debug Component, they will get removed. |
||||
|
* @param Actor The Actor to remove the information from |
||||
|
* @return Returns true if the Actor was a Debug Actor and got deleted, or if it had any Debug Component that got deleted |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
bool DebugRemoveDebugInformationFromActor(AActor* Actor); |
||||
|
/**
|
||||
|
* @brief Remove the Debug Data from the given Actors. Calls DebugRemoveDebugInformationFromActor internally. |
||||
|
* @param Actors The Actors to remove the information from |
||||
|
* @return Returns true if any Actor was a Debug Actor and got deleted, or if any had any Debug Component that got deleted |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
bool DebugRemoveDebugInformationFromActors(const TArray<AActor*>& Actors); |
||||
|
/**
|
||||
|
* @brief Remove the Debug Data from the given UObjects. Calls DebugRemoveDebugInformationFromActor internally. |
||||
|
* @param Objects The Objects to remove the information from |
||||
|
* @return Returns true if any Object was a Debug Actor and got deleted, or if any had any Debug Component that got deleted |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
bool DebugRemoveDebugInformationFromUObjects(const TArray<UObject*>& Objects); |
||||
|
|
||||
|
// --- Debug Axis
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Spawn an Axis Mesh (Actor) at the Origin of the given Actor. |
||||
|
* @param Actor The Actor to draw the Axis from |
||||
|
* @return Returns the created Debug Actor with the Axis Mesh |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
class ADPDebugActor* DebugCreateAxisActorAtActorLocation(AActor* Actor); |
||||
|
/**
|
||||
|
* @brief Spawn an Axis Mesh (Actor) at the given World Transform. |
||||
|
* @param Transform World Transform where to spawn the new Actor |
||||
|
* @param BaseName The Base Name of the Actor, which will then be called "BaseName_Origin". Leave Empty to just call "Origin" |
||||
|
* @return Returns the created Debug Actor with the Axis Mesh |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
class ADPDebugActor* DebugCreateAxisActorAtWorldTransform(const FTransform Transform, const FString BaseName = FString("")); |
||||
|
/**
|
||||
|
* @brief Spawn an Axis Mesh (Component) at the Origin of the given Actor. |
||||
|
* @param Actor The Actor to draw the Axis from |
||||
|
* @return Returns the created Debug Component with the Axis Mesh |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMeshComponent* DebugCreateAxisComponentAtActorLocation(AActor* Actor); |
||||
|
/**
|
||||
|
* @brief Spawn an Axis Mesh (Component) at the given World Transform. |
||||
|
* @param Actor The Actor that will own the component |
||||
|
* @param Transform World Transform where to spawn the new Component |
||||
|
* @return Returns the created Debug Actor with the Axis Mesh |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
UStaticMeshComponent* DebugCreateAxisComponentAtWorldTransform(AActor* Actor, const FTransform Transform); |
||||
|
|
||||
|
// --- Debug BoundingBox
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Spawn an Axis Aligned BoundingBox (Actor) around the given Actor. |
||||
|
* @param Actor The Actor to draw the BoundingBox from |
||||
|
* @param bAccurateBoundingBox If set to true, will recompute the BoundingBox based on vertices location rather than the default value from the Engine [Slow]. |
||||
|
* @param bIncludeChildren If set to true, will include the Children of the Actor in the computation of the Bounding Box |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created Debug Actor with the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
class ADPDebugActor* DebugCreateAABBActorOfActor(AActor* Actor, const bool bAccurateBoundingBox = false, bool bIncludeChildren = false, float Thickness = 3.f); |
||||
|
/**
|
||||
|
* @brief Spawn an Axis Aligned BoundingBox (Component) around the given Actor. |
||||
|
* @param Actor The Actor to draw the BoundingBox from |
||||
|
* @param bAccurateBoundingBox If set to true, will recompute the BoundingBox based on vertices location rather than the default value from the Engine [Slow]. |
||||
|
* @param bIncludeChildren If set to true, will include the Children of the Actor in the computation of the Bounding Box |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created SceneComponent of the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
USceneComponent* DebugCreateAABBComponentOfActor(AActor* Actor, const bool bAccurateBoundingBox = false, bool bIncludeChildren = false, float Thickness = 3.f); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Spawn a BoundingBox (Actor) around the given Actor. |
||||
|
* @param Actor The Actor to draw the BoundingBox from |
||||
|
* @param bAccurateBoundingBox If set to true, will recompute the BoundingBox based on vertices location rather than the default value from the Engine [Slow]. |
||||
|
* @param bIncludeChildren If set to true, will include the Children of the Actor in the computation of the Bounding Box |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created Debug Actor with the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
class ADPDebugActor* DebugCreateBoundingBoxActorOfActor(AActor* Actor, const bool bAccurateBoundingBox = false, bool bIncludeChildren = false, float Thickness = 3.f); |
||||
|
/**
|
||||
|
* @brief Spawn a BoundingBox (Actor) around the given World Aligned BoundingBox. |
||||
|
* @param BoundingBox The World Aligned BoundingBox to generate |
||||
|
* @param Orientation World to Actor transform representing the Orientation of the Bounding Box. (Inverse of Actor->GetTransform()) |
||||
|
* @param BaseName The Base Name of the Actor, which will then be called "BaseName_BoundingBox". Leave Empty to just call "BoundingBox" |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created Debug Actor with the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
class ADPDebugActor* DebugCreateBoundingBoxActorOfFBox(const FBox BoundingBox, const FTransform Orientation, const FString BaseName = FString(""), float Thickness = 3.f); |
||||
|
/**
|
||||
|
* @brief Spawn a BoundingBox (Component) around the given Actor. |
||||
|
* @param Actor The Actor to draw the BoundingBox from |
||||
|
* @param bAccurateBoundingBox If set to true, will recompute the BoundingBox based on vertices location rather than the default value from the Engine [Slow]. |
||||
|
* @param bIncludeChildren If set to true, will include the Children of the Actor in the computation of the Bounding Box |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created SceneComponent of the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
USceneComponent* DebugCreateBoundingBoxComponentOfActor(AActor* Actor, const bool bAccurateBoundingBox = false, bool bIncludeChildren = false, float Thickness = 3.f); |
||||
|
/**
|
||||
|
* @brief pawn a BoundingBox (Component) fitting the given BoundingBox |
||||
|
* @param BoundingBox The World Aligned BoundingBox to generate |
||||
|
* @param Orientation World to Actor transform representing the Orientation of the Bounding Box. (Inverse of Actor->GetTransform()) |
||||
|
* @param Actor The Actor that will own the generated components |
||||
|
* @param bSetMinAtZero If true, the Minimum of the Bounding Box will be set at the origin [0,0,0], otherwise will be set at BoundingBox.Min. |
||||
|
* @param Thickness The Thickness of the BoundingBox lines in world units |
||||
|
* @return Return the created SceneComponent of the BoundingBox |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Debug") |
||||
|
USceneComponent* DebugCreateBoundingBoxComponentsOfFBox(const FBox BoundingBox, const FTransform Orientation, AActor* Actor, bool bSetMinAtZero = true, float Thickness = 3.f); |
||||
|
|
||||
|
// --- Merging
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Merge the given actors |
||||
|
* @param UObjects The UObjects to Merge |
||||
|
* @param PivotAlignment The Pivot Alignment rule |
||||
|
* @param bMergeSiblings Set to true to merge the siblings of the root Actors |
||||
|
* @param MergedActors The returned merged Actor |
||||
|
* @return Returns true if the merging was successful and MergedActor points to a valid Actor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Merging") |
||||
|
bool MergeUObjects(const TArray<UObject*>& UObjects, EPivotPointAlignmentMode PivotAlignment, bool bMergeSiblings, TArray<AStaticMeshActor*>& MergedActors); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Merge the given branch of the hierarchy |
||||
|
* @param Branch Merge the Given Branch |
||||
|
* @param PivotTransform THe World Transform of the Pivot of the merged Actor |
||||
|
* @param bMergeSiblings Set to true to also merge siblings of the root Actor |
||||
|
* @param MergedActor The returned merged Actor |
||||
|
* @return Returns true if the merging was successful and MergedActor points to a valid Actor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Merging") |
||||
|
bool MergeBranch(UBranchActor* Branch, FTransform PivotTransform, bool bMergeSiblings, AStaticMeshActor*& MergedActor); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Compute the World Transform of the Pivot of the Branch as per the given alignment rule |
||||
|
* @param PActor The Branch to compute the Pivot Alignment from |
||||
|
* @param AlignmentMode The Alignment Rule |
||||
|
* @return Returns the proper World Transform of the Pivot |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Merging") |
||||
|
static FTransform ComputeBranchPivotAlignment(const UBranchActor* PActor, const EPivotPointAlignmentMode AlignmentMode); |
||||
|
|
||||
|
// --- Simplify Hierarchy
|
||||
|
|
||||
|
/**
|
||||
|
* @brief By defaults, Dataprep creates an Actor for a Sketchup Group or Component, and a child StaticMeshActor for its Mesh. This replaces the Group Actor with the StaticMeshActor |
||||
|
* @param UObjects The Objects to simplify |
||||
|
* @param TagName The name of the Tag that matches between the Actors and their StaticMeshActors |
||||
|
* @param bMergeMeshes If a Group Actor is composed of multiple meshes and this option is set to true, merge the Meshes into one, otherwise leaves it untouched |
||||
|
* @return Returns true if the hierarchy was simplified |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool SimplifyHierarchy(const TArray<UObject*>& UObjects, FString TagName, const bool bMergeMeshes); |
||||
|
|
||||
|
// --- Replace Camera
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Replaces CineCameraActors with ArchVisCineCameraActors and keep their properties |
||||
|
* @param UObjects The Objects to look into |
||||
|
* @param NewActors Array that will hold the newly created actors |
||||
|
* @return Returns true if at least one camera was replaced and no error occured |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool ReplaceCameras(const TArray<UObject*>& UObjects, TArray<class AArchVisCineCameraActor*>& NewActors); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Replace the given CineCameraActor with an ArchVisCineCameraActor |
||||
|
* @param OldCameraActor The CineCameraActor to replace |
||||
|
* @param bKeepOriginalActor if set to true, the given OldCameraActor will be deleted |
||||
|
* @return Returns the newly created ArchVisCineCameraActor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
class AArchVisCineCameraActor* ReplaceCamera(class ACineCameraActor* OldCameraActor, const bool bKeepOriginalActor = false); |
||||
|
|
||||
|
|
||||
|
// --- Build Adjacency
|
||||
|
|
||||
|
UE_DEPRECATED(5.0, "Tesselation is not suported in UE5.") |
||||
|
/**
|
||||
|
* @brief Sets the Build Adjacency Setting of the meshes of the given Actors to true which allows crack free tesselation |
||||
|
* @param UObjects The Objects to look into |
||||
|
* @param ModifiedMeshes Array that will hold the modified Static Meshes |
||||
|
* @return Returns true if at least one Static Mesh was modified |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool BuildAdjacency(const TArray<UObject*>& UObjects, TArray<UStaticMesh*>& ModifiedMeshes); |
||||
|
|
||||
|
UE_DEPRECATED(5.0, "Tesselation is not suported in UE5.") |
||||
|
/**
|
||||
|
* @brief Sets the Build Adjacency Setting of the mesh to true which allows crack free tesselation. Doesn't work yet with UE5 |
||||
|
* @param StaticMesh The Static Mesh to look into |
||||
|
* @return Returns true if the Static Mesh was modified |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool BuildAdjacencyOfMesh(UStaticMesh* StaticMesh); |
||||
|
|
||||
|
|
||||
|
// --- Explode
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Flatten the Hierarchy of the given actors by bringing their children one step higher into the hierarchy. |
||||
|
* @param UObjects The Objects to explode |
||||
|
* @param bDeleteExplodedActors If set to true the exploded Actors will be deleted |
||||
|
* @return Returns true if at least one Actor was "exploded" |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool Explode(const TArray<UObject*>& UObjects, bool bDeleteExplodedActors); |
||||
|
|
||||
|
|
||||
|
// --- Face Camera
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Add a FaceCamera Component to matching Actors which makes the Actor follow the Camera |
||||
|
* @param UObjects The Objects to add the component to |
||||
|
* @param Tag The Tag required on the Actor to add the component. Give NAME_NONE to give it regardless. |
||||
|
* @param MatchingActors The reference to the Actors who where given the components |
||||
|
* @param FaceCameraComponents The reference to the newly created UFaceCameraComponents |
||||
|
* @return Returns true if at least one Actor got a FaceCameraComponent |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool AddFaceCameraComponentIfMatchesTag(const TArray<UObject*>& UObjects, FName Tag, TArray<AActor*>& MatchingActors, TArray<UFaceCameraComponent*>& FaceCameraComponents); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Add a FaceCamera Component to the Actor which makes it follow the Camera, if the Actor doesn't have one. |
||||
|
* @param Actor The Actor to add the component to |
||||
|
* @param FaceCameraComponent The reference to the newly created UFaceCameraComponent |
||||
|
* @return Returns true if the FaceCameraComponent was added |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools") |
||||
|
bool AddFaceCameraComponentToActor(AActor* Actor, UFaceCameraComponent*& FaceCameraComponent); |
||||
|
|
||||
|
|
||||
|
// --- Helpers
|
||||
|
|
||||
|
/**
|
||||
|
* @brief Create and register properly a new SceneComponent. |
||||
|
* @tparam T Class of Component to create. Should inherit from UActorComponent |
||||
|
* @param Owner The Actor that will own the Component |
||||
|
* @param Name The Name of the Component. ENSURE the Name Given is not already in use (case insensitive), else it will replace the previous one. |
||||
|
* @param ParentComponent The Component the created Component will attach to. If nullptr is passed, the created component will become the new Root Component |
||||
|
* @param RelativeTransform The Relative Transform of the Component |
||||
|
* @return The newly created Component |
||||
|
*/ |
||||
|
template <class T> |
||||
|
T* CreateNewComponent(AActor* Owner, FName Name, USceneComponent* ParentComponent = nullptr, const FTransform RelativeTransform = FTransform::Identity); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Create and register properly a new Component. |
||||
|
* @tparam T Class of Component to create. Should inherit from UActorComponent |
||||
|
* @param Owner The Actor that will own the Component |
||||
|
* @param ComponentClass The Class of the Component to create |
||||
|
* @param Name The Name of the Component. ENSURE the Name Given is not already in use (case insensitive), else it will replace the previous one. |
||||
|
* @param ParentComponent The Component the created Component will attach to. If nullptr is passed, the created component will become the new Root Component |
||||
|
* @param RelativeTransform The Relative Transform of the Component |
||||
|
* @return The newly created Component |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers", |
||||
|
meta = (DisplayName = "Create New Component Of Class", DeterminesOutputType = "ComponentClass", AutoCreateRefTerm="ParentComponent,RelativeTransform")) |
||||
|
UActorComponent* K2_CreateNewComponentOfClass(AActor* Owner, TSubclassOf<class UActorComponent> ComponentClass, FName Name, USceneComponent* ParentComponent, |
||||
|
const FTransform RelativeTransform); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Load and add the Static Mesh Asset from the given Path if StaticMesh is null. Using this functions allows the creation of only one new Asset. |
||||
|
* @return Returns true if it was loaded properly |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
bool LoadStaticMesh(UStaticMesh*& StaticMesh, const FString& AssetName, const FString& AssetPath); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Return the valid Actor References from the given Array. |
||||
|
* @param UObjects The Array of UObjects |
||||
|
* @return Returns the Array of valid Actors. |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static TArray<AActor*> GetValidActors(const TArray<UObject*>& UObjects); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Returns the value of the given Tag. Tags don't really have values but are sometimes written like "SU.GUID.1234", in which case asking for the value of the tag "SU.GUID." will return "1234" |
||||
|
* @param Actor The Actor to find the Tag from |
||||
|
* @param TagName The Name of the Tag |
||||
|
* @param SearchCase The Search Case of the TagName |
||||
|
* @return Returns the value of the first Tag starting with TagName |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static FString GetTagValue(const AActor* Actor, FString TagName, ESearchCase::Type SearchCase = ESearchCase::CaseSensitive); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Puts the NewActor at the position of the OriginalActor in the hierarchy. |
||||
|
* Meant to be used when replacing OriginalActor with NewActor and just before deleting OriginalActor to ensure the hierarchy is kept intact. |
||||
|
* @param OriginalActor The Original Actor to be replaced in the hierarchy |
||||
|
* @param NewActor The New Actor to replace the original one in the hierarchy |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Helpers") |
||||
|
static void ReplaceActorHierarchy(AActor* OriginalActor, AActor* NewActor); |
||||
|
|
||||
|
private: |
||||
|
/**
|
||||
|
* @brief Mesh pointing to '/ArchVisTools/DebugMaterials/SM_Axis.SM_Axis'. Should use GetAxisStaticMesh to retrieve it. |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
UStaticMesh* AxisStaticMesh; |
||||
|
/**
|
||||
|
* @brief Mesh pointing to '/ArchVisTools/DebugMaterials/SM_Unit_CylinderX.SM_Unit_CylinderX'. Should use GetCylinderStaticMeshX to retrieve it. |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
UStaticMesh* CylinderStaticMeshX; |
||||
|
/**
|
||||
|
* @brief Mesh pointing to '/ArchVisTools/DebugMaterials/SM_Unit_CylinderY.SM_Unit_CylinderY'. Should use GetCylinderStaticMeshY to retrieve it. |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
UStaticMesh* CylinderStaticMeshY; |
||||
|
/**
|
||||
|
* @brief Mesh pointing to '/ArchVisTools/DebugMaterials/SM_Unit_CylinderZ.SM_Unit_CylinderZ'. Should use GetCylinderStaticMeshZ to retrieve it. |
||||
|
*/ |
||||
|
UPROPERTY() |
||||
|
UStaticMesh* CylinderStaticMeshZ; |
||||
|
|
||||
|
protected: |
||||
|
// --- Merging
|
||||
|
|
||||
|
/**
|
||||
|
* Gets the components that can be merged from the given UObjects. |
||||
|
* Copy of DatasmithEditingOperationsUtils::GetComponentsToMerge (\Engine\Plugins\Enterprise\DataprepEditor\Source\DataprepLibraries\Private\DataprepEditingOperations.cpp) |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Merging") |
||||
|
void GetComponentsToMergeFromUObjects(UWorld*& World, const TArray<UObject*>& InObjects, TArray<UStaticMeshComponent*>& ComponentsToMerge); |
||||
|
|
||||
|
/**
|
||||
|
* @brief Merge the given StaticMeshComponents |
||||
|
* @param World The World in which the Merged Actor will live |
||||
|
* @param ComponentsToMerge The Array of StaticMeshComponents to Merge |
||||
|
* @param MergedActor The returned Merged Actor |
||||
|
* @param MergedActorName The Name of the merged Actor\ |
||||
|
* @return Returns true if the merging was successful and MergedActor points to a valid Actor |
||||
|
*/ |
||||
|
UFUNCTION(BlueprintCallable, Category="Dataprep | Editing Operation| ArchVisTools| Merging") |
||||
|
bool MergeStaticMeshComponents(UWorld* World, const TArray<UStaticMeshComponent*>& ComponentsToMerge, AStaticMeshActor*& MergedActor, FString MergedActorName = FString("")); |
||||
|
|
||||
|
/**
|
||||
|
* Collects the Objects to delete after merging. Copy of |
||||
|
* Copy of DatasmithEditingOperationsUtils::CollectObjectsToDeleteAfterMeshMerging (\Engine\Plugins\Enterprise\DataprepEditor\Source\DataprepLibraries\Private\DataprepEditingOperations.cpp) |
||||
|
*/ |
||||
|
UFUNCTION() |
||||
|
TArray<UObject*> CollectObjectsToDeleteAfterMeshMerging(const TArray<UStaticMeshComponent*>& InMergedComponents); |
||||
|
/**
|
||||
|
* @brief Collects the Actor to be deleted and rearranges the hierarchy in preparation |
||||
|
* @param Branch The Branch to delete |
||||
|
* @param MergedActor The newly merged Actor |
||||
|
* @param bMergeSiblings Set if the siblings of the given branch were also merged and should be deleted |
||||
|
* @return Returns the Array of Objects to be deleted |
||||
|
*/ |
||||
|
UFUNCTION() |
||||
|
TArray<UObject*> CollectActorsToDeleteAfterMeshMerging(UBranchActor* Branch, AActor* MergedActor, bool bMergeSiblings); |
||||
|
}; |
||||
@ -0,0 +1,72 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.h" |
||||
|
#include "Graph/Nodes/MovieGraphImagePassBaseNode.h" |
||||
|
#include "UObject/UObjectAnnotation.h" |
||||
|
#include "MovieGraphArchVisObjectIdNode.generated.h" |
||||
|
|
||||
|
|
||||
|
enum class EArchVisMoviePipelineObjectIdPassIdType : uint8; |
||||
|
|
||||
|
/** A node which outputs layers compatible with the Cryptomatte standard. */ |
||||
|
// todo: UE 5.6 does not allow the MRG Object ID to be overriden, hopefully fixed in 5.7. The default Object ID pass should work
|
||||
|
UCLASS(BlueprintType, Deprecated) |
||||
|
class UDEPRECATED_MovieGraphArchVisObjectIdNode : public UMovieGraphImagePassBaseNode |
||||
|
{ |
||||
|
GENERATED_BODY() |
||||
|
|
||||
|
public: |
||||
|
UDEPRECATED_MovieGraphArchVisObjectIdNode() = default; |
||||
|
|
||||
|
static FUObjectAnnotationSparse<ArchVisMoviePipeline::FObjectIdAccelerationData, true>& GetManifestAnnotation(); |
||||
|
|
||||
|
// UMovieGraphImagePassBaseNode Interface
|
||||
|
virtual FEngineShowFlags GetShowFlags() const override; |
||||
|
virtual EViewModeIndex GetViewModeIndex() const override; |
||||
|
virtual bool GetAllowsShowFlagsCustomization() const override; |
||||
|
// ~UMovieGraphImagePassBaseNode Interface
|
||||
|
|
||||
|
// UMovieGraphNode Interface
|
||||
|
#if WITH_EDITOR |
||||
|
virtual FText GetNodeTitle(const bool bGetDescriptive = false) const override; |
||||
|
virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override; |
||||
|
#endif |
||||
|
// ~UMovieGraphNode Interface
|
||||
|
|
||||
|
virtual void UpdateTelemetry(FMoviePipelineShotRenderTelemetry* InTelemetry) const override; |
||||
|
|
||||
|
protected: |
||||
|
// UMovieGraphImagePassBaseNode Interface
|
||||
|
virtual TUniquePtr<UE::MovieGraph::Rendering::FMovieGraphImagePassBase> CreateInstance() const override; |
||||
|
// ~UMovieGraphImagePassBaseNode Interface
|
||||
|
|
||||
|
// UMovieGraphRenderPassNode Interface
|
||||
|
virtual FString GetRendererNameImpl() const override; |
||||
|
virtual void SetupImpl(const FMovieGraphRenderPassSetupData& InSetupData) override; |
||||
|
virtual void TeardownImpl() override; |
||||
|
// ~UMovieGraphRenderPassNode Interface
|
||||
|
|
||||
|
public: |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (InlineEditConditionToggle)) |
||||
|
uint8 bOverride_IdType : 1; |
||||
|
|
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Overrides, meta = (InlineEditConditionToggle)) |
||||
|
uint8 bOverride_bIncludeTranslucentObjects : 1; |
||||
|
|
||||
|
/** How objects are grouped together within the Cryptomatte. */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings", DisplayName = "ID Type", meta = (EditCondition="bOverride_IdType")) |
||||
|
EArchVisMoviePipelineObjectIdPassIdType IdType; |
||||
|
|
||||
|
/** If true, translucent objects will be included in the ObjectId pass (but as an opaque layer due to limitations). False will omit translucent objects. */ |
||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings", meta = (EditCondition="bOverride_bIncludeTranslucentObjects")) |
||||
|
bool bIncludeTranslucentObjects; |
||||
|
|
||||
|
private: |
||||
|
/**
|
||||
|
* The prior value of the project's bAllowSelectTranslucent setting. This node will temporarily change the value of this property to the value |
||||
|
* set in bIncludeTranslucentObjects while rendering. |
||||
|
*/ |
||||
|
bool bPrevAllowSelectTranslucent; |
||||
|
}; |
||||
@ -0,0 +1,56 @@ |
|||||
|
// Copyright Voulz 2021-2025. All Rights Reserved.
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "ArchVisMoviePipelineObjectIdUtils.h" |
||||
|
#include "Graph/Renderers/MovieGraphDeferredPass.h" |
||||
|
#include "Graph/Renderers/MovieGraphImagePassBase.h" |
||||
|
#include "UObject/UObjectAnnotation.h" |
||||
|
|
||||
|
|
||||
|
/**
|
||||
|
* This needs to inherit from FMovieGraphRenderDataAccumulationArgs because there's a static cast in the shared baseclass used for post-render accumulation. |
||||
|
*/ |
||||
|
struct FMovieGraphArchVisObjectIdMaskSampleAccumulationArgs : UE::MovieGraph::Rendering::FMovieGraphRenderDataAccumulationArgs |
||||
|
{ |
||||
|
/** The number of layers that the accumulator will be generating. */ |
||||
|
int32 NumOutputLayers; |
||||
|
|
||||
|
/** The mapping of a HitProxy index to the data associated with the HitProxy. */ |
||||
|
TSharedPtr<TMap<int32, ArchVisMoviePipeline::FMoviePipelineHitProxyCacheValue>> CacheData; |
||||
|
|
||||
|
/** The node that is using this accumulator. */ |
||||
|
TWeakObjectPtr<UMovieGraphRenderPassNode> RenderPassNode; |
||||
|
}; |
||||
|
|
||||
|
/** The pass type that is capable of generating Object ID (Cryptomatte) data. */ |
||||
|
struct FMovieGraphArchVisObjectIdPass : UE::MovieGraph::Rendering::FMovieGraphDeferredPass |
||||
|
{ |
||||
|
FMovieGraphArchVisObjectIdPass(); |
||||
|
|
||||
|
// FMovieGraphImagePassBase Interface
|
||||
|
virtual void Setup(TWeakObjectPtr<UMovieGraphDefaultRenderer> InRenderer, TWeakObjectPtr<UMovieGraphImagePassBaseNode> InRenderPassNode, const FMovieGraphRenderPassLayerData& InLayer) override; |
||||
|
virtual void Teardown() override; |
||||
|
virtual void GatherOutputPasses(UMovieGraphEvaluatedConfig* InConfig, TArray<FMovieGraphRenderDataIdentifier>& OutExpectedPasses) const override; |
||||
|
virtual UMovieGraphImagePassBaseNode* GetParentNode(UMovieGraphEvaluatedConfig* InConfig) const override; |
||||
|
virtual TSharedRef<MoviePipeline::IMoviePipelineAccumulationArgs> GetOrCreateAccumulator(TObjectPtr<UMovieGraphDefaultRenderer> InGraphRenderer, const UE::MovieGraph::FMovieGraphSampleState& InSampleState) const override; |
||||
|
virtual FAccumulatorSampleFunc GetAccumulateSampleFunction() const override; |
||||
|
// ~FMovieGraphImagePassBase Interface
|
||||
|
|
||||
|
// FMovieGraphDeferredPass Interface
|
||||
|
virtual void Render(const FMovieGraphTraversalContext& InFrameTraversalContext, const FMovieGraphTimeStepData& InTimeData) override; |
||||
|
// ~FMovieGraphDeferredPass Interface
|
||||
|
|
||||
|
/** Gets the ObjectID acceleration data for a specific branch (will be nullptr if not found). */ |
||||
|
static ArchVisMoviePipeline::FObjectIdAccelerationData* GetAccelerationData(const FName& InBranchName); |
||||
|
|
||||
|
protected: |
||||
|
virtual UE::MovieGraph::DefaultRenderer::FRenderTargetInitParams GetRenderTargetInitParams(const FMovieGraphTimeStepData& InTimeData, const FIntPoint& InResolution) override; |
||||
|
|
||||
|
protected: |
||||
|
/** The identifiers for all object ID layers that will be generated. */ |
||||
|
TArray<FMovieGraphRenderDataIdentifier> RenderDataIdentifiers; |
||||
|
|
||||
|
/** ObjectID acceleration data that needs to be consistent throughout a render (cached per branch). */ |
||||
|
inline static TMap<FName, ArchVisMoviePipeline::FObjectIdAccelerationData> AccelerationDataByBranch; |
||||
|
}; |
||||
Loading…
Reference in new issue