Files
UnrealEngine/Engine/Source/Editor/Persona/Private/AnimationEditorPreviewScene.cpp
2025-05-18 13:04:45 +08:00

1417 lines
44 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimationEditorPreviewScene.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Misc/MessageDialog.h"
#include "Modules/ModuleManager.h"
#include "Components/StaticMeshComponent.h"
#include "Styling/AppStyle.h"
#include "Animation/AnimBlueprint.h"
#include "AnimPreviewInstance.h"
#include "IEditableSkeleton.h"
#include "IPersonaToolkit.h"
#include "PersonaUtils.h"
#include "ComponentAssetBroker.h"
#include "Engine/PreviewMeshCollection.h"
#include "PersonaPreviewSceneDescription.h"
#include "PersonaPreviewSceneDefaultController.h"
#include "Components/WindDirectionalSourceComponent.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "PersonaModule.h"
#include "GameFramework/WorldSettings.h"
#include "Particles/ParticleSystemComponent.h"
#include "Factories/PreviewMeshCollectionFactory.h"
#include "AnimPreviewAttacheInstance.h"
#include "AnimCustomInstanceHelper.h"
#include "AnimViewportPlaybackCommands.h"
#include "Animation/PreviewCollectionInterface.h"
#include "ScopedTransaction.h"
#include "Preferences/PersonaOptions.h"
#include "ISkeletonTreeItem.h"
#include "UICommandList_Pinnable.h"
#include "Engine/SkeletalMeshSocket.h"
#define LOCTEXT_NAMESPACE "AnimationEditorPreviewScene"
/////////////////////////////////////////////////////////////////////////
// FAnimationEditorPreviewScene
FAnimationEditorPreviewScene::FAnimationEditorPreviewScene(const ConstructionValues& CVS, const TSharedPtr<IEditableSkeleton>& InEditableSkeleton, const TSharedRef<IPersonaToolkit>& InPersonaToolkit)
: IPersonaPreviewScene(CVS)
, Actor(nullptr)
, SkeletalMeshComponent(nullptr)
, EditableSkeletonPtr(InEditableSkeleton)
, PersonaToolkit(InPersonaToolkit)
, DefaultMode(EPreviewSceneDefaultAnimationMode::ReferencePose)
, PrevWindLocation(100.0f, 100.0f, 100.0f)
, PrevWindRotation(0.0f, 0.0f, 0.0f)
, PrevWindStrength(0.2f)
, GravityScale(0.25f)
, SelectedBoneIndex(INDEX_NONE)
, bEnableMeshHitProxies(true)
, LastTickTime(0.0)
, bSelecting(false)
, bAllowAdditionalMeshes(true)
, bAdditionalMeshesSelectable(true)
{
if (GEditor)
{
GEditor->RegisterForUndo(this);
}
FloorBounds = FloorMeshComponent->CalcBounds(FloorMeshComponent->GetRelativeTransform());
if(InEditableSkeleton.IsValid())
{
InEditableSkeleton->LoadAdditionalPreviewSkeletalMeshes();
}
// create the preview scene description
PreviewSceneDescription = NewObject<UPersonaPreviewSceneDescription>(GetTransientPackage());
PreviewSceneDescription->SetFlags(RF_Transactional);
PreviewSceneDescription->SetPreviewController(UPersonaPreviewSceneDefaultController::StaticClass(), this);
PreviewSceneDescription->PreviewMesh = InPersonaToolkit->GetPreviewMesh();
if(InEditableSkeleton.IsValid())
{
PreviewSceneDescription->AdditionalMeshes = InEditableSkeleton->GetSkeleton().GetAdditionalPreviewSkeletalMeshes();
}
if(UAnimBlueprint* AnimBlueprint = InPersonaToolkit->GetAnimBlueprint())
{
PreviewSceneDescription->PreviewAnimationBlueprint = AnimBlueprint->GetPreviewAnimationBlueprint();
PreviewSceneDescription->ApplicationMethod = AnimBlueprint->GetPreviewAnimationBlueprintApplicationMethod();
PreviewSceneDescription->LinkedAnimGraphTag = AnimBlueprint->GetPreviewAnimationBlueprintTag();
}
// create a default additional mesh collection so we dont always have to create an asset to edit additional meshes
UPreviewMeshCollectionFactory* FactoryToUse = NewObject<UPreviewMeshCollectionFactory>();
if(InEditableSkeleton.IsValid())
{
FactoryToUse->CurrentSkeleton = MakeWeakObjectPtr(const_cast<USkeleton*>(&InEditableSkeleton->GetSkeleton()));
}
PreviewSceneDescription->DefaultAdditionalMeshes = CastChecked<UPreviewMeshCollection>(FactoryToUse->FactoryCreateNew(UPreviewMeshCollection::StaticClass(), PreviewSceneDescription, "UnsavedCollection", RF_Transient | RF_Transactional, nullptr, nullptr));
if (!PreviewSceneDescription->AdditionalMeshes.IsValid())
{
PreviewSceneDescription->AdditionalMeshes = PreviewSceneDescription->DefaultAdditionalMeshes;
}
// Disable killing actors outside of the world
AWorldSettings* WorldSettings = GetWorld()->GetWorldSettings(true);
WorldSettings->bEnableWorldBoundsChecks = false;
BindCommands();
}
FAnimationEditorPreviewScene::~FAnimationEditorPreviewScene()
{
bool bInRecording = false;
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>(TEXT("Persona"));
PersonaModule.OnIsRecordingActive().ExecuteIfBound(SkeletalMeshComponent, bInRecording);
if (bInRecording)
{
PersonaModule.OnStopRecording().ExecuteIfBound(SkeletalMeshComponent);
}
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
UDebugSkelMeshComponent* MeshComponent = GetPreviewMeshComponent();
if (MeshComponent)
{
MeshComponent->SelectionOverrideDelegate.Unbind();
}
}
void FAnimationEditorPreviewScene::UnregisterForUndo()
{
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
}
TArray<UDebugSkelMeshComponent*> FAnimationEditorPreviewScene::GetAllPreviewMeshComponents() const
{
TArray<UDebugSkelMeshComponent*> PreviewMeshComponents;
const AActor* MyActor = GetActor();
if (MyActor)
{
MyActor->GetComponents(PreviewMeshComponents, true);
}
return PreviewMeshComponents;
}
void FAnimationEditorPreviewScene::ForEachPreviewMesh(TFunction<void (UDebugSkelMeshComponent*)> PerMeshFunction)
{
TArray<UDebugSkelMeshComponent*> PreviewMeshComponents;
const AActor* MyActor = GetActor();
if (MyActor)
{
MyActor->GetComponents(PreviewMeshComponents, true);
for (UDebugSkelMeshComponent* PreviewMesh : PreviewMeshComponents)
{
PerMeshFunction(PreviewMesh);
}
}
}
void FAnimationEditorPreviewScene::SetPreviewMeshComponent(UDebugSkelMeshComponent* InSkeletalMeshComponent)
{
SkeletalMeshComponent = InSkeletalMeshComponent;
if(SkeletalMeshComponent)
{
SkeletalMeshComponent->SelectionOverrideDelegate = UPrimitiveComponent::FSelectionOverride::CreateRaw(this, &FAnimationEditorPreviewScene::PreviewComponentSelectionOverride);
SkeletalMeshComponent->PushSelectionToProxy();
}
}
void FAnimationEditorPreviewScene::SetPreviewMesh(USkeletalMesh* NewPreviewMesh, bool bAllowOverrideBaseMesh)
{
if (NewPreviewMesh != nullptr && GetEditableSkeleton().IsValid() && !GetEditableSkeleton()->GetSkeleton().IsCompatibleMesh(NewPreviewMesh))
{
const USkeleton& Skeleton = GetEditableSkeleton()->GetSkeleton();
const bool bSkipSkeletonCompatibility = PersonaToolkit.IsValid() && PersonaToolkit.Pin()->CanPreviewMeshUseDifferentSkeleton();
if (NewPreviewMesh->GetSkeleton() && (bSkipSkeletonCompatibility || Skeleton.IsCompatibleForEditor(NewPreviewMesh->GetSkeleton())))
{
SetPreviewMeshInternal(NewPreviewMesh);
}
else
{
// message box, ask if they'd like to regenerate skeleton
if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT("RenerateSkeleton", "The preview mesh hierarchy doesn't match with Skeleton anymore. Would you like to regenerate skeleton?")) == EAppReturnType::Yes)
{
GetEditableSkeleton()->RecreateBoneTree(NewPreviewMesh);
SetPreviewMeshInternal(NewPreviewMesh);
}
else
{
// Send a notification that the skeletal mesh cannot work with the skeleton
FFormatNamedArguments Args;
Args.Add(TEXT("PreviewMeshName"), FText::FromString(NewPreviewMesh->GetName()));
Args.Add(TEXT("TargetSkeletonName"), FText::FromString(Skeleton.GetName()));
FNotificationInfo Info(FText::Format(LOCTEXT("SkeletalMeshIncompatible", "Skeletal Mesh \"{PreviewMeshName}\" incompatible with Skeleton \"{TargetSkeletonName}\""), Args));
Info.ExpireDuration = 3.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if (Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Fail);
}
}
}
}
else
{
SetPreviewMeshInternal(NewPreviewMesh);
}
// changing the main skeletal mesh may mean re-applying the additional meshes
// as the mesh on the main component may have been substituted by one of the additional meshes
// we just set main mesh, do not replace
RefreshAdditionalMeshes(bAllowOverrideBaseMesh);
}
void FAnimationEditorPreviewScene::SetPreviewAnimationBlueprint(UAnimBlueprint* InAnimBlueprint, UAnimBlueprint* InOverlayOrSubAnimBlueprint)
{
if (InOverlayOrSubAnimBlueprint)
{
SkeletalMeshComponent->SetAnimInstanceClass(InAnimBlueprint ? InAnimBlueprint->GeneratedClass : nullptr);
EPreviewAnimationBlueprintApplicationMethod ApplicationMethod = InOverlayOrSubAnimBlueprint->GetPreviewAnimationBlueprintApplicationMethod();
if(ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedLayers)
{
SkeletalMeshComponent->LinkAnimClassLayers(InOverlayOrSubAnimBlueprint ? InOverlayOrSubAnimBlueprint->GeneratedClass.Get() : nullptr);
}
else if(ApplicationMethod == EPreviewAnimationBlueprintApplicationMethod::LinkedAnimGraph)
{
SkeletalMeshComponent->LinkAnimGraphByTag(InOverlayOrSubAnimBlueprint->GetPreviewAnimationBlueprintTag(), InOverlayOrSubAnimBlueprint ? InOverlayOrSubAnimBlueprint->GeneratedClass.Get() : nullptr);
}
}
else
{
SkeletalMeshComponent->SetAnimInstanceClass(InAnimBlueprint ? InAnimBlueprint->GeneratedClass : nullptr);
}
}
USkeletalMesh* FAnimationEditorPreviewScene::GetPreviewMesh() const
{
return PreviewSceneDescription->PreviewMesh.Get();
}
void FAnimationEditorPreviewScene::SetPreviewMeshInternal(USkeletalMesh* NewPreviewMesh)
{
USkeletalMesh* OldPreviewMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
// Store off the old skel mesh we are debugging
USkeletalMeshComponent* DebuggedSkeletalMeshComponent = nullptr;
if(SkeletalMeshComponent->GetAnimInstance())
{
if(PersonaToolkit.IsValid())
{
UAnimBlueprint* SourceBlueprint = PersonaToolkit.Pin()->GetAnimBlueprint();
if(SourceBlueprint)
{
UAnimInstance* DebuggedAnimInstance = Cast<UAnimInstance>(SourceBlueprint->GetObjectBeingDebugged());
if(DebuggedAnimInstance)
{
DebuggedSkeletalMeshComponent = DebuggedAnimInstance->GetSkelMeshComponent();
}
}
}
}
// Make sure the desc is up to date as this may have not come from a call to set the value in the desc
PreviewSceneDescription->PreviewMesh = NewPreviewMesh;
//Persona skeletal mesh component is the only component that can highlight a particular section
SkeletalMeshComponent->bCanHighlightSelectedSections = true;
ValidatePreviewAttachedAssets(NewPreviewMesh);
if (NewPreviewMesh != SkeletalMeshComponent->GetSkeletalMeshAsset())
{
// setting skeletalmesh unregister/re-register,
// so I have to save the animation settings and resetting after setting mesh
UAnimationAsset* AnimAssetToPlay = nullptr;
float PlayPosition = 0.f;
bool bPlaying = false;
bool bNeedsToCopyAnimationData = bIsBeingConstructed == false && SkeletalMeshComponent->GetAnimInstance() && SkeletalMeshComponent->GetAnimInstance() == SkeletalMeshComponent->PreviewInstance;
if (bNeedsToCopyAnimationData && NewPreviewMesh)
{
AnimAssetToPlay = SkeletalMeshComponent->PreviewInstance->GetCurrentAsset();
PlayPosition = SkeletalMeshComponent->PreviewInstance->GetCurrentTime();
bPlaying = SkeletalMeshComponent->PreviewInstance->IsPlaying();
}
SkeletalMeshComponent->EmptyOverrideMaterials();
SkeletalMeshComponent->SetSkeletalMesh(NewPreviewMesh);
if (bNeedsToCopyAnimationData && (NewPreviewMesh == nullptr || AnimAssetToPlay != nullptr))
{
SetPreviewAnimationAsset(AnimAssetToPlay);
SkeletalMeshComponent->PreviewInstance->SetPosition(PlayPosition);
SkeletalMeshComponent->PreviewInstance->SetPlaying(bPlaying);
}
}
else
{
SkeletalMeshComponent->InitAnim(true);
}
if (NewPreviewMesh != nullptr)
{
AddComponent(SkeletalMeshComponent, FTransform::Identity);
// Set up the mesh for transactions
NewPreviewMesh->SetFlags(RF_Transactional);
AddPreviewAttachedObjects();
SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
}
// Setting the skeletal mesh to in the PreviewScene can change AnimScriptInstance so we must re register it
// with the AnimBlueprint
if (const TSharedPtr<IPersonaToolkit>& PersonaToolkitPinned = PersonaToolkit.Pin())
{
if (UAnimBlueprint* SourceBlueprint = PersonaToolkitPinned->GetAnimBlueprint())
{
if (DebuggedSkeletalMeshComponent && DebuggedSkeletalMeshComponent->GetAnimInstance() && DebuggedSkeletalMeshComponent->GetAnimInstance()->IsA(SourceBlueprint->GeneratedClass))
{
PersonaUtils::SetObjectBeingDebugged(SourceBlueprint, DebuggedSkeletalMeshComponent->GetAnimInstance());
}
// If we didn't have a preview mesh before and we select one now, set it up as the object being debugged
if (DebuggedSkeletalMeshComponent == nullptr && NewPreviewMesh != nullptr && SkeletalMeshComponent->GetAnimInstance() && SkeletalMeshComponent->GetAnimInstance()->IsA(SourceBlueprint->GeneratedClass))
{
PersonaUtils::SetObjectBeingDebugged(SourceBlueprint, SkeletalMeshComponent->GetAnimInstance());
}
}
}
OnPreviewMeshChanged.Broadcast(OldPreviewMesh, NewPreviewMesh);
}
void FAnimationEditorPreviewScene::ValidatePreviewAttachedAssets(USkeletalMesh* PreviewSkeletalMesh)
{
// Validate the skeleton/meshes attached objects and display a notification to the user if any were broken
int32 NumBrokenAssets = GetEditableSkeleton().IsValid() ? GetEditableSkeleton()->ValidatePreviewAttachedObjects() : 0;
if (PreviewSkeletalMesh)
{
NumBrokenAssets += PreviewSkeletalMesh->ValidatePreviewAttachedObjects();
}
if (NumBrokenAssets > 0)
{
// Tell the user that there were assets that could not be loaded
FFormatNamedArguments Args;
Args.Add(TEXT("NumBrokenAssets"), NumBrokenAssets);
FNotificationInfo Info(FText::Format(LOCTEXT("MissingPreviewAttachedAssets", "{NumBrokenAssets} attached assets could not be found on loading and were removed"), Args));
Info.bUseLargeFont = false;
Info.ExpireDuration = 5.0f;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if (Notification.IsValid())
{
Notification->SetCompletionState(SNotificationItem::CS_Fail);
}
}
}
void FAnimationEditorPreviewScene::SetAdditionalMeshes(class UDataAsset* InAdditionalMeshes)
{
if(GetEditableSkeleton().IsValid())
{
GetEditableSkeleton()->SetAdditionalPreviewSkeletalMeshes(InAdditionalMeshes);
}
RefreshAdditionalMeshes(true);
}
void FAnimationEditorPreviewScene::SetAdditionalMeshesSelectable(bool bSelectable)
{
bAdditionalMeshesSelectable = bSelectable;
}
void FAnimationEditorPreviewScene::RefreshAdditionalMeshes(bool bAllowOverrideBaseMesh)
{
// remove all components
for (USkeletalMeshComponent* Component : AdditionalMeshes)
{
UAnimInstance* AnimInst = Component->GetAnimInstance();
if (AnimInst && AnimInst->IsA(UAnimPreviewAttacheInstance::StaticClass()))
{
AnimInst->Montage_Stop(0.0f);
FAnimCustomInstanceHelper::UnbindFromSkeletalMeshComponent<UAnimPreviewAttacheInstance>(Component);
}
RemoveComponent(Component);
}
AdditionalMeshes.Empty();
// add new components
UDataAsset* PreviewSceneAdditionalMeshes = GetEditableSkeleton().IsValid() ? GetEditableSkeleton()->GetSkeleton().GetAdditionalPreviewSkeletalMeshes() : nullptr;
if (PreviewSceneAdditionalMeshes == nullptr)
{
PreviewSceneAdditionalMeshes = PreviewSceneDescription->DefaultAdditionalMeshes;
}
if (PreviewSceneAdditionalMeshes != nullptr)
{
const bool bUseCustomAnimBP = GetMutableDefault<UPersonaOptions>()->bAllowPreviewMeshCollectionsToUseCustomAnimBP;
// get preview interface
const IPreviewCollectionInterface* PreviewCollection = Cast<IPreviewCollectionInterface>(PreviewSceneAdditionalMeshes);
if (PreviewCollection)
{
if (bAllowOverrideBaseMesh)
{
USkeletalMesh* BaseMesh = PreviewCollection->GetPreviewBaseMesh();
if (BaseMesh)
{
SetPreviewMeshInternal(BaseMesh);
}
}
if (bAllowAdditionalMeshes)
{
TArray<USkeletalMesh*> ValidMeshes;
TArray<TSubclassOf<UAnimInstance>> AnimInstances;
PreviewCollection->GetPreviewSkeletalMeshes(ValidMeshes, AnimInstances);
const int32 NumMeshes = ValidMeshes.Num();
for (int32 MeshIndex = 0; MeshIndex < NumMeshes; ++MeshIndex)
{
USkeletalMesh* SkeletalMesh = ValidMeshes[MeshIndex];
if (SkeletalMesh)
{
USkeletalMeshComponent* NewComp = NewObject<USkeletalMeshComponent>(Actor);
NewComp->bSelectable = bAdditionalMeshesSelectable;
NewComp->RegisterComponent();
NewComp->SetSkeletalMesh(SkeletalMesh);
AddComponent(NewComp, FTransform::Identity, true);
// Use the attach parent bounds if it is valid, as empty bounds would cause mesh to flicker from frustum culling.
if (NewComp->GetAttachParent() && NewComp->GetAttachParent()->Bounds.SphereRadius > 0)
{
NewComp->bUseAttachParentBound = true;
}
if (bUseCustomAnimBP && AnimInstances.IsValidIndex(MeshIndex) && AnimInstances[MeshIndex] != nullptr)
{
NewComp->SetAnimInstanceClass(AnimInstances[MeshIndex]);
}
if (NewComp->GetAnimInstance() == nullptr)
{
bool bWasCreated = false;
FAnimCustomInstanceHelper::BindToSkeletalMeshComponent<UAnimPreviewAttacheInstance>(NewComp,bWasCreated);
}
AdditionalMeshes.Add(NewComp);
}
}
}
}
}
}
void FAnimationEditorPreviewScene::AddPreviewAttachedObjects()
{
if(PersonaToolkit.IsValid())
{
// Load up mesh attachments...
USkeletalMesh* Mesh = PersonaToolkit.Pin()->GetMesh();
if ( Mesh )
{
FPreviewAssetAttachContainer& PreviewAssetAttachContainer = Mesh->GetPreviewAttachedAssetContainer();
for(int32 Index = 0; Index < PreviewAssetAttachContainer.Num(); Index++)
{
FPreviewAttachedObjectPair& PreviewAttachedObject = PreviewAssetAttachContainer[Index];
AttachObjectToPreviewComponent(PreviewAttachedObject.GetAttachedObject(), PreviewAttachedObject.AttachedTo);
}
}
}
if(GetEditableSkeleton().IsValid())
{
const USkeleton& Skeleton = GetEditableSkeleton()->GetSkeleton();
// ...and then skeleton attachments
for(int32 i = 0; i < Skeleton.PreviewAttachedAssetContainer.Num(); i++)
{
const FPreviewAttachedObjectPair& PreviewAttachedObject = Skeleton.PreviewAttachedAssetContainer[i];
AttachObjectToPreviewComponent(PreviewAttachedObject.GetAttachedObject(), PreviewAttachedObject.AttachedTo);
}
}
}
bool FAnimationEditorPreviewScene::AttachObjectToPreviewComponent( UObject* Object, FName AttachTo )
{
if(PersonaUtils::GetComponentForAttachedObject(SkeletalMeshComponent, Object, AttachTo))
{
return false; // Already have this asset attached at this location
}
TSubclassOf<UActorComponent> ComponentClass = FComponentAssetBrokerage::GetPrimaryComponentForAsset(Object->GetClass());
if(SkeletalMeshComponent && *ComponentClass && ComponentClass->IsChildOf(USceneComponent::StaticClass()))
{
//set up world info for undo
AWorldSettings* WorldSettings = GetWorld()->GetWorldSettings();
WorldSettings->SetFlags(RF_Transactional);
WorldSettings->Modify();
USceneComponent* SceneComponent = NewObject<USceneComponent>(WorldSettings, ComponentClass, NAME_None, RF_Transactional);
FComponentAssetBrokerage::AssignAssetToComponent(SceneComponent, Object);
if(UParticleSystemComponent* NewPSysComp = Cast<UParticleSystemComponent>(SceneComponent))
{
//maybe this should happen in FComponentAssetBrokerage::AssignAssetToComponent?
NewPSysComp->SetTickGroup(TG_PostUpdateWork);
}
//set up preview component for undo
SkeletalMeshComponent->SetFlags(RF_Transactional);
SkeletalMeshComponent->Modify();
// Attach component to the preview component
SceneComponent->SetupAttachment(SkeletalMeshComponent, AttachTo);
SceneComponent->RegisterComponent();
return true;
}
return false;
}
void FAnimationEditorPreviewScene::RemoveAttachedObjectFromPreviewComponent(UObject* Object, FName AttachedTo)
{
// clean up components
if (SkeletalMeshComponent)
{
AWorldSettings* WorldSettings = GetWorld()->GetWorldSettings();
WorldSettings->SetFlags(RF_Transactional);
WorldSettings->Modify();
//set up preview component for undo
SkeletalMeshComponent->SetFlags(RF_Transactional);
SkeletalMeshComponent->Modify();
for (int32 I= SkeletalMeshComponent->GetAttachChildren().Num()-1; I >= 0; --I) // Iterate backwards because CleanupComponent will remove from AttachChildren
{
USceneComponent* ChildComponent = SkeletalMeshComponent->GetAttachChildren()[I];
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(ChildComponent);
if( Asset == Object && ChildComponent->GetAttachSocketName() == AttachedTo)
{
// PreviewComponet will be cleaned up by PreviewScene,
// but if anything is attached, it won't be cleaned up,
// so we'll need to clean them up manually
CleanupComponent(SkeletalMeshComponent->GetAttachChildren()[I]);
break;
}
}
}
}
void FAnimationEditorPreviewScene::InvalidateViews()
{
OnInvalidateViews.Broadcast();
}
void FAnimationEditorPreviewScene::FocusViews()
{
OnFocusViews.Broadcast();
}
void FAnimationEditorPreviewScene::RemoveAttachedComponent( bool bRemovePreviewAttached /* = true */ )
{
TMap<UObject*, TArray<FName>> PreviewAttachedObjects;
if( !bRemovePreviewAttached )
{
if(GetEditableSkeleton().IsValid())
{
const USkeleton& Skeleton = GetEditableSkeleton()->GetSkeleton();
for(auto Iter = Skeleton.PreviewAttachedAssetContainer.CreateConstIterator(); Iter; ++Iter)
{
PreviewAttachedObjects.FindOrAdd(Iter->GetAttachedObject()).Add(Iter->AttachedTo);
}
}
if ( USkeletalMesh* PreviewMesh = PersonaToolkit.Pin()->GetMesh() )
{
for(auto Iter = PreviewMesh->GetPreviewAttachedAssetContainer().CreateConstIterator(); Iter; ++Iter)
{
PreviewAttachedObjects.FindOrAdd(Iter->GetAttachedObject()).Add(Iter->AttachedTo);
}
}
}
// clean up components
if (SkeletalMeshComponent)
{
for (int32 I= SkeletalMeshComponent->GetAttachChildren().Num()-1; I >= 0; --I) // Iterate backwards because CleanupComponent will remove from AttachChildren
{
USceneComponent* ChildComponent = SkeletalMeshComponent->GetAttachChildren()[I];
UObject* Asset = FComponentAssetBrokerage::GetAssetFromComponent(ChildComponent);
bool bRemove = true;
//are we supposed to leave assets that came from the skeleton
if( !bRemovePreviewAttached )
{
//could this asset have come from the skeleton
if(PreviewAttachedObjects.Contains(Asset))
{
if(PreviewAttachedObjects.Find(Asset)->Contains(ChildComponent->GetAttachSocketName()))
{
bRemove = false;
}
}
}
// if this component is added by additional meshes, do not remove it.
if (AdditionalMeshes.Contains(ChildComponent))
{
bRemove = false;
}
// you can use delegate to avoid being removed
if (OnRemoveAttachedComponentFilter.IsBound() && !OnRemoveAttachedComponentFilter.Execute(ChildComponent))
{
bRemove = false;
}
if(bRemove)
{
// PreviewComponet will be cleaned up by PreviewScene,
// but if anything is attached, it won't be cleaned up,
// so we'll need to clean them up manually
CleanupComponent(SkeletalMeshComponent->GetAttachChildren()[I]);
}
}
if( bRemovePreviewAttached )
{
check(SkeletalMeshComponent->GetAttachChildren().Num() == 0);
}
}
}
void FAnimationEditorPreviewScene::CleanupComponent(USceneComponent* Component)
{
if (Component)
{
for (int32 I = Component->GetAttachChildren().Num() - 1; I >= 0; --I) // Iterate backwards because CleanupComponent will remove from AttachChildren
{
CleanupComponent(Component->GetAttachChildren()[I]);
}
check(Component->GetAttachChildren().Num() == 0);
// make sure to remove from component list
RemoveComponent(Component);
Component->DestroyComponent();
}
}
void FAnimationEditorPreviewScene::SetPreviewAnimationAsset(UAnimationAsset* AnimAsset, bool bEnablePreview /*= true*/)
{
if (SkeletalMeshComponent)
{
const USkeleton* Skeleton = GetEditableSkeleton().IsValid() ? &GetEditableSkeleton()->GetSkeleton() : nullptr;
RemoveAttachedComponent(false);
if (AnimAsset != nullptr)
{
// Early out if the new preview asset is the same as the current one, to avoid replaying from the beginning, etc...
if (AnimAsset == GetPreviewAnimationAsset() && SkeletalMeshComponent->IsPreviewOn())
{
return;
}
// Treat it as invalid if it's got a bogus skeleton pointer
if (AnimAsset->GetSkeleton() == nullptr)
{
return;
}
}
SkeletalMeshComponent->EnablePreview(bEnablePreview, AnimAsset);
}
OnAnimChanged.Broadcast(AnimAsset);
}
UAnimationAsset* FAnimationEditorPreviewScene::GetPreviewAnimationAsset() const
{
if (SkeletalMeshComponent)
{
// if same, do not overwrite. It will reset time and everything
if (SkeletalMeshComponent->PreviewInstance != nullptr)
{
return SkeletalMeshComponent->PreviewInstance->GetCurrentAsset();
}
}
return nullptr;
}
void FAnimationEditorPreviewScene::SetFloorLocation(const FVector& InPosition)
{
FloorMeshComponent->SetWorldTransform(FTransform(FQuat::Identity, InPosition, FVector(3.0f, 3.0f, 1.0f)));
}
void FAnimationEditorPreviewScene::ShowReferencePose(bool bShowRefPose, bool bResetBoneTransforms)
{
if(SkeletalMeshComponent)
{
SkeletalMeshComponent->ShowReferencePose(bShowRefPose);
// Also reset bone transforms
if(bResetBoneTransforms && SkeletalMeshComponent->GetSkeletalMeshAsset() != nullptr)
{
bool bModified = false;
FScopedTransaction Transaction(LOCTEXT("ResetBoneTransforms", "Reset Bone Transforms"));
int32 NumBones = SkeletalMeshComponent->GetSkeletalMeshAsset()->GetRefSkeleton().GetNum();
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
FName BoneName = SkeletalMeshComponent->GetSkeletalMeshAsset()->GetRefSkeleton().GetBoneName(BoneIndex);
const FAnimNode_ModifyBone* ModifiedBone = SkeletalMeshComponent->PreviewInstance->FindModifiedBone(BoneName);
if (ModifiedBone != nullptr)
{
if (!bModified)
{
SkeletalMeshComponent->PreviewInstance->SetFlags(RF_Transactional);
SkeletalMeshComponent->PreviewInstance->Modify();
bModified = true;
}
SkeletalMeshComponent->PreviewInstance->RemoveBoneModification(BoneName);
}
}
}
}
}
bool FAnimationEditorPreviewScene::IsShowReferencePoseEnabled() const
{
return SkeletalMeshComponent->IsReferencePoseShown();
}
void FAnimationEditorPreviewScene::SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode Mode, bool bShowNow)
{
DefaultMode = Mode;
if (bShowNow)
{
ShowDefaultMode();
}
}
void FAnimationEditorPreviewScene::ShowDefaultMode()
{
switch (DefaultMode)
{
case EPreviewSceneDefaultAnimationMode::ReferencePose:
{
ShowReferencePose(true);
break;
}
case EPreviewSceneDefaultAnimationMode::Animation:
{
UObject* PreviewAsset = PersonaToolkit.Pin()->GetAnimationAsset();
if (PreviewAsset)
{
SkeletalMeshComponent->EnablePreview(true, Cast<UAnimationAsset>(PreviewAsset));
}
}
break;
case EPreviewSceneDefaultAnimationMode::AnimationBlueprint:
{
SkeletalMeshComponent->EnablePreview(false, nullptr);
UAnimBlueprint* AnimBlueprint = PersonaToolkit.Pin()->GetAnimBlueprint();
if (AnimBlueprint)
{
UAnimBlueprint* PreviewAnimBlueprint = AnimBlueprint->GetPreviewAnimationBlueprint();
if (PreviewAnimBlueprint)
{
SetPreviewAnimationBlueprint(PreviewAnimBlueprint, AnimBlueprint);
}
else
{
SetPreviewAnimationBlueprint(AnimBlueprint, nullptr);
}
}
}
break;
case EPreviewSceneDefaultAnimationMode::Custom:
{
SkeletalMeshComponent->SetCustomDefaultPose();
break;
}
}
}
FText FAnimationEditorPreviewScene::GetPreviewAssetTooltip(bool bEditingAnimBlueprint) const
{
// if already looking at ref pose
if (IsShowReferencePoseEnabled())
{
FText PreviewFormat(LOCTEXT("PreviewFormat", "Preview {0}"));
if (bEditingAnimBlueprint)
{
UAnimBlueprint* AnimBP = PersonaToolkit.Pin()->GetAnimBlueprint();
if (AnimBP)
{
return FText::Format(PreviewFormat, FText::FromString(AnimBP->GetName()));
}
}
else
{
UObject* PreviewAsset = PersonaToolkit.Pin()->GetAnimationAsset();
if (PreviewAsset)
{
return FText::Format(PreviewFormat, FText::FromString(PreviewAsset->GetName()));
}
}
return LOCTEXT("NoPreviewAvailable", "None Available. Please select asset to preview.");
}
else
{
return FText::Format(LOCTEXT("CurrentlyPreviewingFormat", "Currently previewing {0}"), FText::FromString(SkeletalMeshComponent->GetPreviewText()));
}
}
void FAnimationEditorPreviewScene::ClearSelectedBone()
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
SelectedBoneIndex = INDEX_NONE;
if (SkeletalMeshComponent)
{
SkeletalMeshComponent->BonesOfInterest.Empty();
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
OnSelectedBoneChanged_DEPRECATED.Broadcast({NAME_None}, ESelectInfo::Direct);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
OnSelectedBonesChanged.Broadcast({NAME_None}, ESelectInfo::Direct);
InvalidateViews();
}
void FAnimationEditorPreviewScene::SetSelectedBone(const FName& BoneName, ESelectInfo::Type InSelectInfo)
{
const int32 BoneIndex = GetEditableSkeleton().IsValid() ? GetEditableSkeleton()->GetSkeleton().GetReferenceSkeleton().FindBoneIndex(BoneName) : INDEX_NONE;
if (BoneIndex != INDEX_NONE)
{
SetSelectedBones({BoneName}, InSelectInfo);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
OnSelectedBoneChanged_DEPRECATED.Broadcast(BoneName, InSelectInfo);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
}
void FAnimationEditorPreviewScene::SetSelectedBones(const TArray<FName>& InBoneNames, ESelectInfo::Type InSelectInfo)
{
if (!GetEditableSkeleton().IsValid())
{
return;
}
const FReferenceSkeleton& ReferenceSkeleton = GetEditableSkeleton()->GetSkeleton().GetReferenceSkeleton();
TArray<FName> BoneNames;
Algo::CopyIf(InBoneNames, BoneNames, [&ReferenceSkeleton](const FName& BoneName)
{
return ReferenceSkeleton.FindBoneIndex(BoneName) != INDEX_NONE;
});
if (BoneNames.IsEmpty())
{
return;
}
TGuardValue<bool> RecursionGuard(bSelecting, true);
ClearSelectedBone();
ClearSelectedSocket();
ClearSelectedActor();
// need to get mesh bone base since BonesOfInterest is saved in SkeletalMeshComponent
// and it is used by renderer. It is not Skeleton base
for (const FName& BoneName : BoneNames)
{
const int32 MeshBoneIndex = SkeletalMeshComponent->GetBoneIndex(BoneName);
if (MeshBoneIndex != INDEX_NONE)
{
if (SelectedBoneIndex == INDEX_NONE)
{
SelectedBoneIndex = MeshBoneIndex;
}
SkeletalMeshComponent->BonesOfInterest.Add(SelectedBoneIndex);
}
}
InvalidateViews();
OnSelectedBonesChanged.Broadcast(BoneNames, InSelectInfo);
}
void FAnimationEditorPreviewScene::SetSelectedSocket(const FSelectedSocketInfo& SocketInfo)
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
ClearSelectedBone();
ClearSelectedActor();
SelectedSocket = SocketInfo;
OnSelectedSocketChanged.Broadcast(SelectedSocket);
InvalidateViews();
}
void FAnimationEditorPreviewScene::ClearSelectedSocket()
{
TGuardValue<bool> RecursionGuard(bSelecting, true);
SelectedSocket.Reset();
OnSelectedSocketChanged.Broadcast(SelectedSocket);
InvalidateViews();
}
void FAnimationEditorPreviewScene::SetSelectedActor(AActor* InActor)
{
ClearSelectedBone();
ClearSelectedSocket();
SelectedActor = InActor;
InvalidateViews();
}
void FAnimationEditorPreviewScene::ClearSelectedActor()
{
SelectedActor = nullptr;
InvalidateViews();
}
void FAnimationEditorPreviewScene::DeselectAll()
{
ClearSelectedBone();
ClearSelectedSocket();
ClearSelectedActor();
OnDeselectAll.Broadcast();
InvalidateViews();
}
bool FAnimationEditorPreviewScene::IsRecordAvailable() const
{
// make sure mesh exists
return (SkeletalMeshComponent->GetSkeletalMeshAsset() != nullptr);
}
FSlateIcon FAnimationEditorPreviewScene::GetRecordStatusImage() const
{
if (IsRecording())
{
return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.StopRecordAnimation");
}
return FSlateIcon(FAppStyle::GetAppStyleSetName(), "Persona.StartRecordAnimation");
}
FText FAnimationEditorPreviewScene::GetRecordMenuLabel() const
{
if (IsRecording())
{
return LOCTEXT("Persona_StopRecordAnimationMenuLabel", "Stop Record Animation");
}
return LOCTEXT("Persona_StartRecordAnimationMenuLabel", "Start Record Animation");
}
FText FAnimationEditorPreviewScene::GetRecordStatusLabel() const
{
bool bInRecording = false;
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>(TEXT("Persona"));
PersonaModule.OnIsRecordingActive().ExecuteIfBound(SkeletalMeshComponent, bInRecording);
if (bInRecording)
{
return LOCTEXT("Persona_StopRecordAnimationLabel", "Stop");
}
return LOCTEXT("Persona_StartRecordAnimationLabel", "Record");
}
FText FAnimationEditorPreviewScene::GetRecordStatusTooltip() const
{
bool bInRecording = false;
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>(TEXT("Persona"));
PersonaModule.OnIsRecordingActive().ExecuteIfBound(SkeletalMeshComponent, bInRecording);
if (bInRecording)
{
return LOCTEXT("Persona_StopRecordAnimation", "Stop Record Animation");
}
return LOCTEXT("Persona_StartRecordAnimation", "Start Record Animation");
}
void FAnimationEditorPreviewScene::RecordAnimation()
{
bool bInRecording = false;
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>(TEXT("Persona"));
PersonaModule.OnIsRecordingActive().ExecuteIfBound(SkeletalMeshComponent, bInRecording);
if (bInRecording)
{
PersonaModule.OnStopRecording().ExecuteIfBound(SkeletalMeshComponent);
}
else
{
PersonaModule.OnRecord().ExecuteIfBound(SkeletalMeshComponent);
}
OnRecordingStateChangedDelegate.Broadcast();
}
bool FAnimationEditorPreviewScene::IsRecording() const
{
bool bInRecording = false;
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.OnIsRecordingActive().ExecuteIfBound(SkeletalMeshComponent, bInRecording);
return bInRecording;
}
void FAnimationEditorPreviewScene::StopRecording()
{
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.OnStopRecording().ExecuteIfBound(SkeletalMeshComponent);
OnRecordingStateChangedDelegate.Broadcast();
}
UAnimSequence* FAnimationEditorPreviewScene::GetCurrentRecording() const
{
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
UAnimSequence* Recording = nullptr;
PersonaModule.OnGetCurrentRecording().ExecuteIfBound(SkeletalMeshComponent, Recording);
return Recording;
}
float FAnimationEditorPreviewScene::GetCurrentRecordingTime() const
{
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
float RecordingTime = 0.0f;
PersonaModule.OnGetCurrentRecordingTime().ExecuteIfBound(SkeletalMeshComponent, RecordingTime);
return RecordingTime;
}
bool FAnimationEditorPreviewScene::PreviewComponentSelectionOverride(const UPrimitiveComponent* InComponent) const
{
if (InComponent == GetPreviewMeshComponent())
{
const USkeletalMeshComponent* Component = CastChecked<USkeletalMeshComponent>(InComponent);
return (Component->GetSelectedEditorSection() != INDEX_NONE || Component->GetSelectedEditorMaterial() != INDEX_NONE);
}
return false;
}
TWeakObjectPtr<AWindDirectionalSource> FAnimationEditorPreviewScene::CreateWindActor(UWorld* World)
{
TWeakObjectPtr<AWindDirectionalSource> Wind = World->SpawnActor<AWindDirectionalSource>(PrevWindLocation, PrevWindRotation);
check(Wind.IsValid());
//initial wind strength value
Wind->GetComponent()->Speed = PrevWindStrength;
return Wind;
}
void FAnimationEditorPreviewScene::OnSetPlaybackSpeed(int32 InPlaybackSpeedMode)
{
SetAnimationPlaybackSpeedMode(static_cast<EAnimationPlaybackSpeeds::Type>(InPlaybackSpeedMode));
}
void FAnimationEditorPreviewScene::BindCommands()
{
IPersonaPreviewScene::BindCommands();
FUICommandList_Pinnable& CommandList = *GetPinnedCommandList();
FAnimViewportPlaybackCommands::Register();
const FAnimViewportPlaybackCommands& ViewportPlaybackCommands = FAnimViewportPlaybackCommands::Get();
// Create a menu item for each playback speed in EAnimationPlaybackSpeeds
for (int32 i = 0; i < static_cast<int>(EAnimationPlaybackSpeeds::NumPlaybackSpeeds); ++i)
{
CommandList.FUICommandList::MapAction(
ViewportPlaybackCommands.PlaybackSpeedCommands[i],
FExecuteAction::CreateRaw(this, &FAnimationEditorPreviewScene::OnSetPlaybackSpeed, i),
FCanExecuteAction(),
FIsActionChecked::CreateRaw(this, &FAnimationEditorPreviewScene::IsPlaybackSpeedSelected, i)
);
}
}
TSharedRef<FUICommandList> FAnimationEditorPreviewScene::CreateCommandList()
{
return MakeShared<FUICommandList_Pinnable>();
}
TSharedPtr<FUICommandList_Pinnable> FAnimationEditorPreviewScene::GetPinnedCommandList() const
{
return StaticCastSharedPtr<FUICommandList_Pinnable>(UICommandList);
}
void FAnimationEditorPreviewScene::EnableWind(bool bEnableWind)
{
UWorld* World = GetWorld();
check(World);
if (bEnableWind)
{
if (!WindSourceActor.IsValid())
{
WindSourceActor = CreateWindActor(World);
}
}
else if (WindSourceActor.IsValid())
{
PrevWindLocation = WindSourceActor->GetActorLocation();
PrevWindRotation = WindSourceActor->GetActorRotation();
PrevWindStrength = WindSourceActor->GetComponent()->Strength;
World->DestroyActor(WindSourceActor.Get());
}
}
bool FAnimationEditorPreviewScene::IsWindEnabled() const
{
return WindSourceActor.IsValid();
}
void FAnimationEditorPreviewScene::SetWindStrength(float SliderValue)
{
if (WindSourceActor.IsValid())
{
WindSourceActor->GetComponent()->Speed = SliderValue;
//to apply this new wind strength
WindSourceActor->UpdateComponentTransforms();
}
}
float FAnimationEditorPreviewScene::GetWindStrength() const
{
if (WindSourceActor.IsValid())
{
return WindSourceActor->GetComponent()->Speed;
}
return 0;
}
void FAnimationEditorPreviewScene::SetGravityScale(float InGravityScale)
{
GravityScale = InGravityScale;
float RealGravityScale = InGravityScale * 4.0f;
UWorld* World = GetWorld();
AWorldSettings* Setting = World->GetWorldSettings();
Setting->WorldGravityZ = UPhysicsSettings::Get()->DefaultGravityZ*RealGravityScale;
Setting->bWorldGravitySet = true;
}
float FAnimationEditorPreviewScene::GetGravityScale() const
{
return GravityScale;
}
AActor* FAnimationEditorPreviewScene::GetSelectedActor() const
{
return SelectedActor.Get();
}
FSelectedSocketInfo FAnimationEditorPreviewScene::GetSelectedSocket() const
{
return SelectedSocket;
}
int32 FAnimationEditorPreviewScene::GetSelectedBoneIndex() const
{
return SelectedBoneIndex;
}
void FAnimationEditorPreviewScene::TogglePlayback()
{
if (SkeletalMeshComponent)
{
if (SkeletalMeshComponent->IsPreviewOn() && SkeletalMeshComponent->PreviewInstance)
{
SkeletalMeshComponent->PreviewInstance->SetPlaying(!SkeletalMeshComponent->PreviewInstance->IsPlaying());
}
else
{
SkeletalMeshComponent->GlobalAnimRateScale = (SkeletalMeshComponent->GlobalAnimRateScale > 0.0f) ? 0.0f : 1.0f;
}
}
}
void FAnimationEditorPreviewScene::SetActor(AActor* InActor)
{
check(Actor == nullptr || !Actor->IsRooted());
Actor = InActor;
}
AActor* FAnimationEditorPreviewScene::GetActor() const
{
return Actor;
}
bool FAnimationEditorPreviewScene::AllowMeshHitProxies() const
{
return bEnableMeshHitProxies;
}
void FAnimationEditorPreviewScene::SetAllowMeshHitProxies(bool bState)
{
bEnableMeshHitProxies = bState;
if (GetPreviewMeshComponent())
{
GetPreviewMeshComponent()->bSelectable = bEnableMeshHitProxies;
GetPreviewMeshComponent()->MarkRenderStateDirty();
}
}
void FAnimationEditorPreviewScene::FlagTickable()
{
// Set the last tick time so we tick when we are visible in a viewport
LastTickTime = FPlatformTime::Seconds();
}
void FAnimationEditorPreviewScene::HandleSkeletonTreeSelectionChanged(const TArrayView<TSharedPtr<ISkeletonTreeItem>>& InSelectedItems, ESelectInfo::Type InSelectInfo)
{
if(!bSelecting)
{
if(InSelectedItems.Num() == 0)
{
DeselectAll();
}
else
{
for(const TSharedPtr<ISkeletonTreeItem>& Item : InSelectedItems)
{
if(Item->IsOfTypeByName("FSkeletonTreeBoneItem"))
{
SetSelectedBone(Item->GetRowItemName(), InSelectInfo);
}
else if(Item->IsOfTypeByName("FSkeletonTreeSocketItem"))
{
FSelectedSocketInfo SocketInfo;
SocketInfo.Socket = CastChecked<USkeletalMeshSocket>(Item->GetObject());
SocketInfo.bSocketIsOnSkeleton = !SocketInfo.Socket->GetOuter()->IsA<USkeletalMesh>();
SetSelectedSocket(SocketInfo);
}
}
}
}
}
void FAnimationEditorPreviewScene::Tick(float InDeltaTime)
{
OnPreTickDelegate.Broadcast();
IPersonaPreviewScene::Tick(InDeltaTime);
GetWorld()->Tick(LEVELTICK_All, InDeltaTime);
if (SkeletalMeshComponent)
{
// Handle updating the preview component to represent the effects of root motion
const FBoxSphereBounds& Bounds = GetFloorBounds();
SkeletalMeshComponent->ConsumeRootMotion(Bounds.GetBox().Min, Bounds.GetBox().Max);
if (LastCachedLODForPreviewComponent != SkeletalMeshComponent->GetPredictedLODLevel())
{
OnLODChanged.Broadcast();
LastCachedLODForPreviewComponent = SkeletalMeshComponent->GetPredictedLODLevel();
}
}
OnPostTickDelegate.Broadcast();
}
bool FAnimationEditorPreviewScene::IsTickable() const
{
const float VisibilityTimeThreshold = 0.25f;
// The preview scene is tickable if any viewport can see it
return LastTickTime == 0.0 || // Never been ticked
FPlatformTime::Seconds() - LastTickTime <= VisibilityTimeThreshold; // Ticked recently
}
void FAnimationEditorPreviewScene::AddComponent(class UActorComponent* Component, const FTransform& LocalToWorld, bool bAttachToRoot /*= false*/)
{
if (bAttachToRoot)
{
if (USceneComponent* SceneComponent = Cast<USceneComponent>(Component))
{
SceneComponent->AttachToComponent(Actor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform);
}
}
Actor->AddOwnedComponent(Component);
IPersonaPreviewScene::AddComponent(Component, LocalToWorld);
}
void FAnimationEditorPreviewScene::RemoveComponent(class UActorComponent* Component)
{
if (USceneComponent* SceneComponent = Cast<USceneComponent>(Component))
{
SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
}
Actor->RemoveOwnedComponent(Component);
IPersonaPreviewScene::RemoveComponent(Component);
}
void FAnimationEditorPreviewScene::PostUndo(bool bSuccess)
{
// refresh skeletal mesh & animation
if (PreviewSceneDescription)
{
SetPreviewMesh(PreviewSceneDescription->PreviewMesh.Get());
PreviewSceneDescription->PreviewControllerInstance->InitializeView(PreviewSceneDescription, this);
}
}
void FAnimationEditorPreviewScene::PostRedo(bool bSuccess)
{
PostUndo(bSuccess);
}
void FAnimationEditorPreviewScene::AddReferencedObjects( FReferenceCollector& Collector )
{
IPersonaPreviewScene::AddReferencedObjects(Collector);
Collector.AddReferencedObject(PreviewSceneDescription);
Collector.AddReferencedObject(Actor);
Collector.AddReferencedObject(SkeletalMeshComponent);
Collector.AddReferencedObjects(AdditionalMeshes);
}
TOptional<float> FAnimationEditorPreviewScene::GetCurrentTime() const
{
if (SkeletalMeshComponent && SkeletalMeshComponent->PreviewInstance)
{
return SkeletalMeshComponent->PreviewInstance->GetCurrentTime();
}
return {};
}
void FAnimationEditorPreviewScene::SetCustomAnimationSpeed(float InAnimationSpeed)
{
CustomAnimationSpeed = InAnimationSpeed;
SetAnimationPlaybackSpeedMode(EAnimationPlaybackSpeeds::Custom);
}
float FAnimationEditorPreviewScene::GetCustomAnimationSpeed() const
{
return CustomAnimationSpeed;
}
EAnimationPlaybackSpeeds::Type FAnimationEditorPreviewScene::GetAnimationPlaybackSpeedMode() const
{
return AnimationPlaybackSpeedMode;
}
void FAnimationEditorPreviewScene::SetAnimationPlaybackSpeedMode(EAnimationPlaybackSpeeds::Type InMode)
{
AnimationPlaybackSpeedMode = InMode;
if (GetWorld())
{
const float AnimationSpeed = (InMode == EAnimationPlaybackSpeeds::Custom)
? GetCustomAnimationSpeed()
: EAnimationPlaybackSpeeds::Values[AnimationPlaybackSpeedMode];
GetWorld()->GetWorldSettings()->TimeDilation = AnimationSpeed;
}
}
bool FAnimationEditorPreviewScene::IsPlaybackSpeedSelected(int32 InPlaybackSpeedMode) const
{
return InPlaybackSpeedMode == AnimationPlaybackSpeedMode;
}
#undef LOCTEXT_NAMESPACE