Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Private/Evaluation/MovieSceneCameraShakePreviewer.cpp
2025-05-18 13:04:45 +08:00

375 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Evaluation/MovieSceneCameraShakePreviewer.h"
#if WITH_EDITOR
#include "Camera/CameraModifier_CameraShake.h"
#include "Camera/CameraShakeSourceComponent.h"
#include "LevelEditorViewport.h"
#include "MovieSceneFwd.h"
FCameraShakePreviewer::FCameraShakePreviewer(UWorld* InWorld)
: World(InWorld)
, LastDeltaTime(0.f)
, LastLocationModifier(FVector::ZeroVector)
, LastRotationModifier(FRotator::ZeroRotator)
, LastFOVModifier(0.f)
{
// Handle camera shakes being recompiled.
FCoreUObjectDelegates::OnObjectsReplaced.AddRaw(this, &FCameraShakePreviewer::OnObjectsReplaced);
}
FCameraShakePreviewer::~FCameraShakePreviewer()
{
if (!ensureMsgf(RegisteredViewportClients.Num() == 0, TEXT("Forgot to call UnRegisterViewModifiers!")))
{
UnRegisterViewModifiers();
}
FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this);
}
UCameraShakeBase* FCameraShakePreviewer::AddCameraShake(const FCameraShakePreviewerAddParams& Params)
{
FCameraShakeBaseStartParams StartParams;
StartParams.Scale = Params.Scale;
StartParams.PlaySpace = Params.PlaySpace;
StartParams.UserPlaySpaceRot = Params.UserPlaySpaceRot;
StartParams.DurationOverride = Params.DurationOverride;
UCameraShakeBase* NewShake = NewObject<UCameraShakeBase>(World, Params.ShakeClass);
NewShake->StartShake(StartParams);
ActiveShakes.Add({ StartParams, NewShake, Params.SourceComponent, Params.GlobalStartTime });
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::AddCameraShake '%s'"), *GetNameSafe(NewShake));
return NewShake;
}
void FCameraShakePreviewer::RemoveCameraShake(UCameraShakeBase* ShakeInstance)
{
const bool bImmediately = true;
for (int32 i = ActiveShakes.Num() - 1; i >= 0; --i)
{
FPreviewCameraShakeInfo& ActiveShake = ActiveShakes[i];
if (ActiveShake.ShakeInstance == ShakeInstance)
{
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::RemoveCameraShake '%s'"), *GetNameSafe(ActiveShake.ShakeInstance));
ActiveShake.ShakeInstance->StopShake(bImmediately);
ActiveShake.ShakeInstance->TeardownShake();
ActiveShakes.RemoveAt(i, 1);
break;
}
}
}
void FCameraShakePreviewer::RemoveAllCameraShakesFromSource(const UCameraShakeSourceComponent* SourceComponent)
{
int32 NumRemoved = 0;
const bool bImmediately = true;
for (int32 i = ActiveShakes.Num() - 1; i >= 0; --i)
{
FPreviewCameraShakeInfo& ActiveShake = ActiveShakes[i];
if (ActiveShake.SourceComponent.Get() == SourceComponent && ActiveShake.ShakeInstance != nullptr)
{
ActiveShake.ShakeInstance->StopShake(bImmediately);
ActiveShake.ShakeInstance->TeardownShake();
ActiveShakes.RemoveAt(i, 1);
++NumRemoved;
}
}
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::RemoveAllCameraShakesFromSource '%s', %d removed"),
*GetNameSafe(SourceComponent), NumRemoved);
}
void FCameraShakePreviewer::RemoveAllCameraShakes()
{
const bool bImmediately = true;
for (FPreviewCameraShakeInfo& ActiveShake : ActiveShakes)
{
if (ActiveShake.ShakeInstance)
{
ActiveShake.ShakeInstance->StopShake(bImmediately);
ActiveShake.ShakeInstance->TeardownShake();
}
}
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::RemoveAllCameraShakes, %d removed"), ActiveShakes.Num());
ActiveShakes.Empty();
}
void FCameraShakePreviewer::GetActiveCameraShakes(TArray<FActiveCameraShakeInfo>& ActiveCameraShakes) const
{
for (const FPreviewCameraShakeInfo& ActiveShake : ActiveShakes)
{
FActiveCameraShakeInfo ShakeInfo;
ShakeInfo.ShakeInstance = ActiveShake.ShakeInstance;
ShakeInfo.ShakeSource = ActiveShake.SourceComponent;
ActiveCameraShakes.Add(ShakeInfo);
}
}
void FCameraShakePreviewer::Update(float DeltaTime, bool bIsPlaying)
{
LastDeltaTime = DeltaTime;
LastScrubTime.Reset();
if (!bIsPlaying)
{
ResetModifiers();
}
}
void FCameraShakePreviewer::Scrub(float ScrubTime)
{
LastDeltaTime.Reset();
LastScrubTime = ScrubTime;
ResetModifiers();
}
void FCameraShakePreviewer::ResetModifiers()
{
LastLocationModifier = FVector::ZeroVector;
LastRotationModifier = FRotator::ZeroRotator;
LastFOVModifier = 0.f;
LastPostProcessSettings.Reset();
LastPostProcessBlendWeights.Reset();
}
void FCameraShakePreviewer::ModifyView(FEditorViewportViewModifierParams& Params)
{
OnModifyView(Params);
}
void FCameraShakePreviewer::OnModifyView(FEditorViewportViewModifierParams& Params)
{
FMinimalViewInfo& InOutPOV(Params.ViewInfo);
const FMinimalViewInfo OriginalPOV(Params.ViewInfo);
// This is a simpler version of what UCameraModifier_CameraShake does, with extra
// support for scrubbing.
if (LastDeltaTime.IsSet() || LastScrubTime.IsSet())
{
LastPostProcessSettings.Reset();
LastPostProcessBlendWeights.Reset();
for (FPreviewCameraShakeInfo& ActiveShake : ActiveShakes)
{
if (ActiveShake.ShakeInstance != nullptr)
{
float CurShakeAlpha = 1.f;
if (ActiveShake.SourceComponent.IsValid())
{
const UCameraShakeSourceComponent* SourceComponent = ActiveShake.SourceComponent.Get();
const float AttenuationFactor = SourceComponent->GetAttenuationFactor(InOutPOV.Location);
CurShakeAlpha *= AttenuationFactor;
}
if (LastDeltaTime.IsSet())
{
ActiveShake.ShakeInstance->UpdateAndApplyCameraShake(LastDeltaTime.GetValue(), CurShakeAlpha, InOutPOV);
}
else if (LastScrubTime.IsSet())
{
float RelativeScrubTime = LastScrubTime.GetValue() - ActiveShake.StartTime;
ActiveShake.ShakeInstance->ScrubAndApplyCameraShake(RelativeScrubTime, CurShakeAlpha, InOutPOV);
}
if (InOutPOV.PostProcessBlendWeight > 0.f)
{
Params.AddPostProcessBlend(InOutPOV.PostProcessSettings, InOutPOV.PostProcessBlendWeight);
LastPostProcessSettings.Add(InOutPOV.PostProcessSettings);
LastPostProcessBlendWeights.Add(InOutPOV.PostProcessBlendWeight);
}
InOutPOV.PostProcessSettings = FPostProcessSettings();
InOutPOV.PostProcessBlendWeight = 0.f;
}
}
LastLocationModifier = InOutPOV.Location - OriginalPOV.Location;
LastRotationModifier = InOutPOV.Rotation - OriginalPOV.Rotation;
LastFOVModifier = InOutPOV.FOV - OriginalPOV.FOV;
LastDeltaTime.Reset();
LastScrubTime.Reset();
// Delete any obsolete shakes.
for (int32 i = ActiveShakes.Num() - 1; i >= 0; i--)
{
const FPreviewCameraShakeInfo& ShakeInfo = ActiveShakes[i];
if (ShakeInfo.ShakeInstance == nullptr || ShakeInfo.ShakeInstance->IsFinished() || ShakeInfo.SourceComponent.IsStale())
{
if (ShakeInfo.ShakeInstance != nullptr)
{
ShakeInfo.ShakeInstance->TeardownShake();
}
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::OnModifyView, '%s' finished or stale"),
*GetNameSafe(ShakeInfo.ShakeInstance));
ActiveShakes.RemoveAt(i, 1);
}
}
}
else
{
InOutPOV.Location += LastLocationModifier;
InOutPOV.Rotation += LastRotationModifier;
InOutPOV.FOV += LastFOVModifier;
for (int32 PPIndex = 0; PPIndex < LastPostProcessSettings.Num(); ++PPIndex)
{
Params.AddPostProcessBlend(LastPostProcessSettings[PPIndex], LastPostProcessBlendWeights[PPIndex]);
}
}
}
void FCameraShakePreviewer::RegisterViewModifiers(bool bIgnoreDuplicateRegistration)
{
RegisterViewModifiers([](FLevelEditorViewportClient*) { return true; }, bIgnoreDuplicateRegistration);
}
void FCameraShakePreviewer::RegisterViewModifiers(FViewportFilter ViewportFilter, bool bIgnoreDuplicateRegistration)
{
if (GEditor == nullptr)
{
return;
}
// Register our view modifier on all appropriate viewports, and remember which viewports we did that on.
// We will later make sure to unregister on the same list, except for any viewport that somehow disappeared since,
// which we will be notified about with the OnLevelViewportClientListChanged event.
for (FLevelEditorViewportClient* LevelVC : GEditor->GetLevelViewportClients())
{
if (LevelVC &&
LevelVC->GetViewMode() != VMI_Unknown &&
LevelVC->GetWorld() == World &&
ViewportFilter(LevelVC))
{
RegisterViewModifier(LevelVC, bIgnoreDuplicateRegistration);
}
}
}
void FCameraShakePreviewer::RegisterViewModifier(FLevelEditorViewportClient* ViewportClient, bool bIgnoreDuplicateRegistration)
{
if (RegisteredViewportClients.Contains(ViewportClient))
{
// Already registered to this viewport.
ensureMsgf(bIgnoreDuplicateRegistration, TEXT("Given viewport is already registered."));
return;
}
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::RegisterViewModifier"));
RegisteredViewportClients.Add(ViewportClient);
ViewportClient->ViewModifiers.AddRaw(this, &FCameraShakePreviewer::OnModifyView);
if (RegisteredViewportClients.Num() == 1)
{
// If this is the first viewport, start listening to viewports changing.
GEditor->OnLevelViewportClientListChanged().AddRaw(this, &FCameraShakePreviewer::OnLevelViewportClientListChanged);
}
}
void FCameraShakePreviewer::UnRegisterViewModifiers()
{
if (GEditor == nullptr)
{
return;
}
GEditor->OnLevelViewportClientListChanged().RemoveAll(this);
for (FLevelEditorViewportClient* ViewportClient : RegisteredViewportClients)
{
ViewportClient->ViewModifiers.RemoveAll(this);
}
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::UnRegisterViewModifiers, %d unregistered"),
RegisteredViewportClients.Num());
RegisteredViewportClients.Reset();
}
void FCameraShakePreviewer::UnRegisterViewModifier(FLevelEditorViewportClient* ViewportClient)
{
const int32 NumRemoved = RegisteredViewportClients.Remove(ViewportClient);
if (ensureMsgf(NumRemoved > 0, TEXT("The given viewport client wasn't registered.")))
{
UE_LOG(LogMovieScene, Verbose, TEXT("CameraShakePreviewer::UnRegisterViewModifier"));
// If this is the last viewport, stop listening to viewports changing.
if (RegisteredViewportClients.IsEmpty())
{
GEditor->OnLevelViewportClientListChanged().RemoveAll(this);
}
ViewportClient->ViewModifiers.RemoveAll(this);
}
}
void FCameraShakePreviewer::OnLevelViewportClientListChanged()
{
if (GEditor != nullptr)
{
// If any viewports were removed while we were playing, simply get rid of them from our list of
// registered viewports.
TSet<FLevelEditorViewportClient*> PreviousViewportClients(RegisteredViewportClients);
TSet<FLevelEditorViewportClient*> NewViewportClients(GEditor->GetLevelViewportClients());
RegisteredViewportClients = PreviousViewportClients.Intersect(NewViewportClients).Array();
// Unregister ourselves from the viewport-change callback if we don't have any registered
// viewports anymore.
if (RegisteredViewportClients.IsEmpty())
{
GEditor->OnLevelViewportClientListChanged().RemoveAll(this);
}
// Remove ourselves from the viewport that went away. It's probably not necessary since they
// will be destroyed, but better be safe.
TSet<FLevelEditorViewportClient*> RemovedViewportClients = PreviousViewportClients.Difference(NewViewportClients);
for (FLevelEditorViewportClient* RemovedViewportClient : RemovedViewportClients)
{
RemovedViewportClient->ViewModifiers.RemoveAll(this);
}
}
}
void FCameraShakePreviewer::AddReferencedObjects(FReferenceCollector& Collector)
{
for (FPreviewCameraShakeInfo& ActiveShake : ActiveShakes)
{
if (ActiveShake.ShakeInstance)
{
Collector.AddReferencedObject(ActiveShake.ShakeInstance);
}
}
}
void FCameraShakePreviewer::OnObjectsReplaced(const TMap<UObject*, UObject*>& ReplacementMap)
{
const bool bImmediately = true;
for (int32 i = ActiveShakes.Num() - 1; i >= 0; i--)
{
FPreviewCameraShakeInfo& ActiveShake = ActiveShakes[i];
if (UObject* const * NewShakeInstance = ReplacementMap.Find(ActiveShake.ShakeInstance))
{
// If a camera shake gets recompiled, stop the old version, and start the
// new version with the same parameters.
ActiveShake.ShakeInstance->StopShake(bImmediately);
ActiveShake.ShakeInstance->TeardownShake();
ActiveShake.ShakeInstance = Cast<UCameraShakeBase>(*NewShakeInstance);
if (ensure(ActiveShake.ShakeInstance))
{
ActiveShake.ShakeInstance->StartShake(ActiveShake.StartParams);
}
}
}
}
#endif