Refactoring

This commit is contained in:
Ivan Anikin
2026-05-21 12:02:28 +07:00
parent 86baa476f4
commit 46cf6a4396
906 changed files with 2434 additions and 6456 deletions
@@ -2,6 +2,8 @@
#include "FunnyGirlEditorModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "AssetTypeActions/AssetTypeActions_DirectorPrompt.h"
#include "AssetTypeActions/AssetTypeActions_Identity.h"
#include "AssetTypeActions/AssetTypeActions_LLMProvider.h"
@@ -11,12 +13,16 @@
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "IAssetTypeActions.h"
#include "Misc/FileHelper.h"
#include "Misc/MessageDialog.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Morph/Editor/LipSyncMappingAssetTypeActions.h"
#include "Morph/Editor/MorphPresetAssetTypeActions.h"
#include "Morph/MorphPresetDataAsset.h"
#include "Morph/Editor/MorphPresetDetailsCustomization.h"
#include "PropertyEditorModule.h"
#include "ToolMenus.h"
#define LOCTEXT_NAMESPACE "FunnyGirlEditor"
@@ -44,10 +50,18 @@ void FFunnyGirlEditorModule::StartupModule()
FOnGetDetailCustomizationInstance::CreateStatic(&FMorphPresetDetailsCustomization::MakeInstance));
PropertyEditorModule.NotifyCustomizationModuleChanged();
bMorphPresetCustomizationRegistered = true;
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FFunnyGirlEditorModule::RegisterMenus));
}
void FFunnyGirlEditorModule::ShutdownModule()
{
if (UObjectInitialized() && FModuleManager::Get().IsModuleLoaded(TEXT("ToolMenus")))
{
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetTools")))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
@@ -88,6 +102,91 @@ void FFunnyGirlEditorModule::RegisterAssetTypeAction(const TSharedRef<IAssetType
RegisteredAssetTypeActions.Add(AssetTypeAction);
}
void FFunnyGirlEditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* ToolsMenu = UToolMenus::Get()->ExtendMenu(TEXT("LevelEditor.MainMenu.Tools"));
if (!ToolsMenu)
{
return;
}
FToolMenuSection& Section = ToolsMenu->FindOrAddSection(TEXT("FunnyGirl"));
Section.Label = LOCTEXT("FunnyGirlToolsSection", "Funny Girl");
Section.AddMenuEntry(
TEXT("ExportMorphPresetDescriptions"),
LOCTEXT("ExportMorphPresetDescriptionsLabel", "Export Morph Preset Descriptions"),
LOCTEXT("ExportMorphPresetDescriptionsTooltip", "Export all Morph Preset assets to Saved/Exports/MorphPresetDescriptions.tsv."),
FSlateIcon(),
FUIAction(FExecuteAction::CreateRaw(this, &FFunnyGirlEditorModule::ExportMorphPresetDescriptions)));
}
void FFunnyGirlEditorModule::ExportMorphPresetDescriptions()
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
AssetRegistry.SearchAllAssets(true);
FARFilter Filter;
Filter.ClassPaths.Add(UMorphPresetDataAsset::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
TArray<FAssetData> PresetAssets;
AssetRegistry.GetAssets(Filter, PresetAssets);
PresetAssets.Sort([](const FAssetData& A, const FAssetData& B)
{
return A.GetSoftObjectPath().ToString() < B.GetSoftObjectPath().ToString();
});
FString Output;
Output += TEXT("AssetName\tPresetName\tDescription\n");
for (const FAssetData& AssetData : PresetAssets)
{
const UMorphPresetDataAsset* Preset = Cast<UMorphPresetDataAsset>(AssetData.GetAsset());
if (!Preset)
{
continue;
}
FString AssetName = AssetData.AssetName.ToString();
FString PresetName = Preset->DisplayName.ToString().TrimStartAndEnd();
FString Description = Preset->Description.ToString().TrimStartAndEnd();
if (PresetName.IsEmpty())
{
PresetName = AssetName;
}
AssetName.ReplaceInline(TEXT("\t"), TEXT(" "));
PresetName.ReplaceInline(TEXT("\t"), TEXT(" "));
Description.ReplaceInline(TEXT("\t"), TEXT(" "));
Description.ReplaceInline(TEXT("\r\n"), TEXT(" "));
Description.ReplaceInline(TEXT("\n"), TEXT(" "));
Description.ReplaceInline(TEXT("\r"), TEXT(" "));
Output += FString::Printf(TEXT("%s\t%s\t%s\n"), *AssetName, *PresetName, *Description);
}
const FString ExportDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Exports"));
IFileManager::Get().MakeDirectory(*ExportDir, true);
const FString ExportPath = FPaths::Combine(ExportDir, TEXT("MorphPresetDescriptions.tsv"));
if (!FFileHelper::SaveStringToFile(Output, *ExportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
{
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(
LOCTEXT("ExportMorphPresetDescriptionsFailed", "Failed to write Morph Preset descriptions to:\n{0}"),
FText::FromString(ExportPath)));
return;
}
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(
LOCTEXT("ExportMorphPresetDescriptionsSuccess", "Exported {0} Morph Preset assets to:\n{1}"),
FText::AsNumber(PresetAssets.Num()),
FText::FromString(ExportPath)));
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FFunnyGirlEditorModule, FunnyGirlEditor)
@@ -24,4 +24,6 @@ private:
bool bMorphPresetCustomizationRegistered = false;
void RegisterAssetTypeAction(const TSharedRef<IAssetTypeActions>& AssetTypeAction);
void RegisterMenus();
void ExportMorphPresetDescriptions();
};
@@ -60,6 +60,10 @@ namespace
TargetPreset.DisplayName = SourcePreset.DisplayName;
TargetPreset.Description = SourcePreset.Description;
TargetPreset.MorphTargets = SourcePreset.MorphTargets;
TargetPreset.Montage = SourcePreset.Montage;
TargetPreset.MontagePlayRate = SourcePreset.MontagePlayRate;
TargetPreset.MontageStartTime = SourcePreset.MontageStartTime;
TargetPreset.PreviewIKRetargeter = SourcePreset.PreviewIKRetargeter;
TargetPreset.Duration = SourcePreset.Duration;
TargetPreset.bLoop = SourcePreset.bLoop;
TargetPreset.LoopDelay = SourcePreset.LoopDelay;
@@ -3,6 +3,9 @@
#include "Morph/Editor/MorphPresetEditorController.h"
#include "Animation/AnimData/IAnimationDataController.h"
#include "Animation/AnimCompositeBase.h"
#include "Animation/AnimMontage.h"
#include "Animation/AnimSingleNodeInstance.h"
#include "AnimPreviewInstance.h"
#include "Animation/AnimSequence.h"
#include "Animation/DebugSkelMeshComponent.h"
@@ -18,8 +21,10 @@
#include "Morph/LipSyncMappingDataAsset.h"
#include "Morph/MorphControllerComponent.h"
#include "Morph/MorphPresetDataAsset.h"
#include "Morph/Editor/MorphPresetRetargetPreviewAnimInstance.h"
#include "PersonaModule.h"
#include "ReferenceSkeleton.h"
#include "Retargeter/IKRetargeter.h"
#include "Sound/SoundBase.h"
DEFINE_LOG_CATEGORY_STATIC(LogMorphPresetEditorPreview, Log, All);
@@ -31,6 +36,7 @@ namespace
IAnimationDataController& Controller = AnimSequence.GetController();
Controller.SetNumberOfFrames(FMath::Max(Controller.ConvertSecondsToFrameNumber(PlayLength).Value, 1), bShouldTransact);
}
}
FMorphPresetEditorController::FMorphPresetEditorController(UMorphPresetDataAsset* InPreset)
@@ -45,6 +51,7 @@ FMorphPresetEditorController::~FMorphPresetEditorController()
{
StopLipSyncPreviewAudio();
DestroyLipSyncPreviewComponents();
DestroyAnimationPreviewMesh();
if (PersonaToolkit.IsValid())
{
@@ -76,6 +83,36 @@ USkeletalMesh* FMorphPresetEditorController::GetPreviewMesh() const
return nullptr;
}
USkeletalMesh* FMorphPresetEditorController::GetAnimationPreviewMesh() const
{
if (UIKRetargeter* Retargeter = GetPreviewIKRetargeter())
{
return Retargeter->GetPreviewMesh(ERetargetSourceOrTarget::Source);
}
return nullptr;
}
UAnimMontage* FMorphPresetEditorController::GetPreviewMontage() const
{
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
{
return PresetAsset->Montage.LoadSynchronous();
}
return nullptr;
}
UIKRetargeter* FMorphPresetEditorController::GetPreviewIKRetargeter() const
{
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
{
return PresetAsset->PreviewIKRetargeter.LoadSynchronous();
}
return nullptr;
}
bool FMorphPresetEditorController::SetPreviewMesh(USkeletalMesh* InPreviewMesh)
{
UMorphPresetDataAsset* PresetAsset = Preset.Get();
@@ -93,7 +130,71 @@ bool FMorphPresetEditorController::SetPreviewMesh(USkeletalMesh* InPreviewMesh)
PresetAsset->PreviewMesh = InPreviewMesh;
PresetAsset->MarkPackageDirty();
RebuildPreviewScene();
if (!InPreviewMesh || !GetPreviewScene().IsValid())
{
RebuildPreviewScene();
PresetChanged.Broadcast();
return true;
}
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene())
{
StopLipSyncPreviewAudio();
DestroyLipSyncPreviewComponents();
DestroyAnimationPreviewMesh();
PreviewScene->SetPreviewMesh(InPreviewMesh, false);
ActivePreviewMesh = InPreviewMesh;
RebuildPreviewAnimation();
RebuildAnimationPreviewMesh();
RebuildLipSyncPreviewComponents();
SetCurrentTime(CurrentTime);
PresetChanged.Broadcast();
}
return true;
}
bool FMorphPresetEditorController::SetPreviewMontage(UAnimMontage* InMontage)
{
UMorphPresetDataAsset* PresetAsset = Preset.Get();
if (!PresetAsset)
{
return false;
}
if (PresetAsset->Montage.Get() == InMontage)
{
return false;
}
PresetAsset->Modify();
PresetAsset->Montage = InMontage;
PresetAsset->MarkPackageDirty();
RebuildAnimationPreviewMesh();
PlaybackStateChanged.Broadcast();
return true;
}
bool FMorphPresetEditorController::SetPreviewIKRetargeter(UIKRetargeter* InRetargeter)
{
UMorphPresetDataAsset* PresetAsset = Preset.Get();
if (!PresetAsset)
{
return false;
}
if (PresetAsset->PreviewIKRetargeter.Get() == InRetargeter)
{
return false;
}
PresetAsset->Modify();
PresetAsset->PreviewIKRetargeter = InRetargeter;
PresetAsset->MarkPackageDirty();
ConfigureRetargetPreview();
PlaybackStateChanged.Broadcast();
return true;
}
@@ -112,7 +213,12 @@ float FMorphPresetEditorController::GetTimelineLength() const
{
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
{
return FMath::Max(PresetAsset->GetTimelineLength(), 0.1f);
float TimelineLength = FMath::Max(PresetAsset->GetTimelineLength(), 0.1f);
if (const UAnimMontage* Montage = PresetAsset->GetMontage())
{
TimelineLength = FMath::Max(TimelineLength, FMath::Max(PresetAsset->MontageStartTime, 0.0f) + Montage->GetPlayLength());
}
return FMath::Max(TimelineLength, 0.1f);
}
return 1.0f;
@@ -134,8 +240,11 @@ void FMorphPresetEditorController::SetCurrentTime(float InTime)
if (PreviewMeshComponent->PreviewInstance)
{
PreviewMeshComponent->PreviewInstance->SetPlaying(false);
PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false);
RefreshPreviewAnimationPose();
if (!HasRetargetPreview())
{
PreviewMeshComponent->PreviewInstance->SetPosition(CurrentTime, false);
RefreshPreviewAnimationPose();
}
}
}
}
@@ -224,6 +333,12 @@ void FMorphPresetEditorController::NotifyPresetEdited(bool bBroadcastPresetChang
return;
}
if (ActiveAnimationPreviewMesh.Get() != GetAnimationPreviewMesh() ||
ActivePreviewIKRetargeter.Get() != GetPreviewIKRetargeter())
{
RebuildAnimationPreviewMesh();
}
if (!PreviewAnimation.IsValid())
{
RebuildPreviewAnimation();
@@ -261,6 +376,12 @@ void FMorphPresetEditorController::TogglePlaying()
void FMorphPresetEditorController::Tick(float DeltaTime)
{
if (LastTickFrameNumber == GFrameCounter)
{
return;
}
LastTickFrameNumber = GFrameCounter;
if (bIsPlaying)
{
float NewTime = CurrentTime + DeltaTime;
@@ -422,8 +543,12 @@ void FMorphPresetEditorController::RebuildPreviewScene()
{
StopLipSyncPreviewAudio();
DestroyLipSyncPreviewComponents();
DestroyAnimationPreviewMesh();
PersonaToolkit.Reset();
PreviewAnimation.Reset();
AnimationPreviewMeshComponent.Reset();
ActiveAnimationPreviewMesh.Reset();
ActivePreviewIKRetargeter.Reset();
USkeletalMesh* PreviewMesh = GetPreviewMesh();
ActivePreviewMesh = PreviewMesh;
@@ -438,6 +563,7 @@ void FMorphPresetEditorController::RebuildPreviewScene()
PersonaToolkit = PersonaModule.CreatePersonaToolkit(PreviewMesh, ToolkitArgs);
RebuildPreviewAnimation();
RebuildAnimationPreviewMesh();
RebuildLipSyncPreviewComponents();
SetCurrentTime(CurrentTime);
PreviewSceneChanged.Broadcast();
@@ -483,6 +609,205 @@ void FMorphPresetEditorController::RebuildPreviewAnimation()
}
}
void FMorphPresetEditorController::RebuildAnimationPreviewMesh()
{
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
if (!PreviewScene.IsValid())
{
return;
}
DestroyAnimationPreviewMesh();
UMorphPresetDataAsset* PresetAsset = Preset.Get();
USkeletalMesh* AnimationMesh = GetAnimationPreviewMesh();
ActiveAnimationPreviewMesh = AnimationMesh;
PreviewStatusText = FText::GetEmpty();
if (!PresetAsset || !AnimationMesh)
{
ConfigureRetargetPreview();
return;
}
AActor* PreviewActor = PreviewScene->GetActor();
if (!PreviewActor)
{
return;
}
USkeletalMeshComponent* AnimationMeshComponent = NewObject<USkeletalMeshComponent>(PreviewActor, NAME_None, RF_Transient);
if (!AnimationMeshComponent)
{
return;
}
AnimationMeshComponent->SetSkeletalMesh(AnimationMesh);
AnimationMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode);
AnimationMeshComponent->bSelectable = false;
AnimationMeshComponent->SetVisibility(false, true);
AnimationMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
const FVector SourceMeshOffset = GetPreviewIKRetargeter() ? GetPreviewIKRetargeter()->SourceMeshOffset : FVector(0.0f, 80.0f, 0.0f);
PreviewScene->AddComponent(AnimationMeshComponent, FTransform(SourceMeshOffset));
AnimationPreviewMeshComponent = AnimationMeshComponent;
if (UAnimMontage* Montage = GetPreviewMontage())
{
float MontageAssetTime = 0.0f;
float MontageAssetLength = Montage->GetPlayLength();
UAnimationAsset* PreviewAsset = ResolveMontagePreviewAnimation(MontageAssetTime, MontageAssetLength);
AnimationMeshComponent->SetAnimation(PreviewAsset ? PreviewAsset : Montage);
AnimationMeshComponent->SetPlayRate(1.0f);
AnimationMeshComponent->SetPosition(FMath::Clamp(MontageAssetTime, 0.0f, MontageAssetLength), false);
if (UAnimSingleNodeInstance* SingleNodeInstance = AnimationMeshComponent->GetSingleNodeInstance())
{
SingleNodeInstance->SetLooping(PresetAsset->bLoop);
SingleNodeInstance->SetPlaying(false);
}
}
else
{
PreviewStatusText = FText::FromString(TEXT("Animation Preview Mesh is set, but this preset has no Montage assigned."));
}
ConfigureRetargetPreview();
RefreshAnimationPreviewPose();
}
void FMorphPresetEditorController::DestroyAnimationPreviewMesh()
{
ClearRetargetPreview(false);
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene())
{
if (USkeletalMeshComponent* ExistingComponent = AnimationPreviewMeshComponent.Get())
{
PreviewScene->RemoveComponent(ExistingComponent);
ExistingComponent->DestroyComponent();
}
}
AnimationPreviewMeshComponent.Reset();
ActiveAnimationPreviewMesh.Reset();
}
void FMorphPresetEditorController::ConfigureRetargetPreview()
{
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
if (!PreviewScene.IsValid())
{
return;
}
UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent();
USkeletalMeshComponent* SourceMeshComponent = AnimationPreviewMeshComponent.Get();
UIKRetargeter* Retargeter = GetPreviewIKRetargeter();
if (!PreviewMeshComponent || !SourceMeshComponent || !Retargeter)
{
ClearRetargetPreview(true);
return;
}
ClearRetargetPreview(false);
const FName InstanceName = MakeUniqueObjectName(PreviewMeshComponent, UMorphPresetRetargetPreviewAnimInstance::StaticClass(), TEXT("MorphPresetTargetRetargetPreviewInstance"));
UMorphPresetRetargetPreviewAnimInstance* RetargetAnimInstance = NewObject<UMorphPresetRetargetPreviewAnimInstance>(PreviewMeshComponent, InstanceName, RF_Transient);
if (!RetargetAnimInstance)
{
return;
}
PreviewMeshComponent->SetAnimationMode(EAnimationMode::AnimationCustomMode);
PreviewMeshComponent->AnimScriptInstance = RetargetAnimInstance;
PreviewMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
PreviewMeshComponent->SetProcessRootMotionMode(EProcessRootMotionMode::Ignore);
RetargetAnimInstance->Configure(Retargeter, SourceMeshComponent);
RetargetAnimInstance->InitializeAnimation();
RetargetPreviewInstance = RetargetAnimInstance;
ActivePreviewIKRetargeter = Retargeter;
}
void FMorphPresetEditorController::ClearRetargetPreview(bool bRestorePreviewAnimation)
{
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene.IsValid() ? PreviewScene->GetPreviewMeshComponent() : nullptr;
UMorphPresetRetargetPreviewAnimInstance* RetargetAnimInstance = RetargetPreviewInstance.Get();
if (PreviewMeshComponent && RetargetAnimInstance && PreviewMeshComponent->AnimScriptInstance == RetargetAnimInstance)
{
PreviewMeshComponent->ClearAnimScriptInstance();
}
RetargetPreviewInstance.Reset();
if (ActivePreviewIKRetargeter.IsValid())
{
ActivePreviewIKRetargeter.Reset();
if (bRestorePreviewAnimation)
{
RebuildPreviewAnimation();
}
}
}
UAnimationAsset* FMorphPresetEditorController::ResolveMontagePreviewAnimation(float& OutAssetTime, float& OutAssetLength) const
{
OutAssetTime = 0.0f;
OutAssetLength = 0.0f;
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
UAnimMontage* Montage = GetPreviewMontage();
if (!PresetAsset || !Montage)
{
return nullptr;
}
const float MontageTrackTime = FMath::Clamp(CurrentTime - FMath::Max(PresetAsset->MontageStartTime, 0.0f), 0.0f, Montage->GetPlayLength());
const FAnimSegment* FirstSegment = nullptr;
const FAnimSegment* LastSegment = nullptr;
for (const FSlotAnimationTrack& SlotTrack : Montage->SlotAnimTracks)
{
for (const FAnimSegment& Segment : SlotTrack.AnimTrack.AnimSegments)
{
if (!Segment.GetAnimReference())
{
continue;
}
if (!FirstSegment)
{
FirstSegment = &Segment;
}
LastSegment = &Segment;
if (Segment.IsInRange(MontageTrackTime))
{
OutAssetTime = Segment.ConvertTrackPosToAnimPos(MontageTrackTime);
OutAssetLength = Segment.GetAnimReference()->GetPlayLength();
return Segment.GetAnimReference();
}
}
}
const FAnimSegment* FallbackSegment = MontageTrackTime <= 0.0f ? FirstSegment : LastSegment;
if (FallbackSegment && FallbackSegment->GetAnimReference())
{
const float SegmentTrackTime = FMath::Clamp(MontageTrackTime, FallbackSegment->StartPos, FallbackSegment->GetEndPos());
OutAssetTime = FallbackSegment->ConvertTrackPosToAnimPos(SegmentTrackTime);
OutAssetLength = FallbackSegment->GetAnimReference()->GetPlayLength();
return FallbackSegment->GetAnimReference();
}
OutAssetLength = Montage->GetPlayLength();
OutAssetTime = MontageTrackTime;
return Montage;
}
bool FMorphPresetEditorController::HasRetargetPreview() const
{
return ActivePreviewIKRetargeter.IsValid() && AnimationPreviewMeshComponent.IsValid() && RetargetPreviewInstance.IsValid();
}
void FMorphPresetEditorController::RefreshPreviewAnimationPose()
{
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
@@ -503,6 +828,36 @@ void FMorphPresetEditorController::RefreshPreviewAnimationPose()
PreviewScene->InvalidateViews();
}
void FMorphPresetEditorController::RefreshAnimationPreviewPose()
{
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
USkeletalMeshComponent* AnimationMeshComponent = AnimationPreviewMeshComponent.Get();
if (!PresetAsset || !PreviewScene.IsValid() || !AnimationMeshComponent)
{
return;
}
if (UAnimSingleNodeInstance* SingleNodeInstance = AnimationMeshComponent->GetSingleNodeInstance())
{
float MontageAssetTime = 0.0f;
float MontageAssetLength = 0.0f;
UAnimationAsset* PreviewAsset = ResolveMontagePreviewAnimation(MontageAssetTime, MontageAssetLength);
if (PreviewAsset && SingleNodeInstance->GetCurrentAsset() != PreviewAsset)
{
SingleNodeInstance->SetAnimationAsset(PreviewAsset, PresetAsset->bLoop);
}
SingleNodeInstance->SetPlaying(false);
SingleNodeInstance->SetPosition(FMath::Clamp(MontageAssetTime, 0.0f, MontageAssetLength), false);
}
AnimationMeshComponent->TickAnimation(0.0f, false);
AnimationMeshComponent->RefreshBoneTransforms();
AnimationMeshComponent->MarkRenderStateDirty();
PreviewScene->InvalidateViews();
}
void FMorphPresetEditorController::ApplyPresetToPreview()
{
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
@@ -538,6 +893,7 @@ void FMorphPresetEditorController::ApplyPresetToPreview()
PreviewMeshComponent->SetMorphTarget(Pair.Key, Pair.Value, false);
}
RefreshAnimationPreviewPose();
RefreshPreviewAnimationPose();
}
@@ -0,0 +1,111 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Morph/Editor/MorphPresetRetargetPreviewAnimInstance.h"
#include "Retargeter/IKRetargeter.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MorphPresetRetargetPreviewAnimInstance)
FMorphPresetRetargetPreviewAnimInstanceProxy::FMorphPresetRetargetPreviewAnimInstanceProxy(
UAnimInstance* InAnimInstance,
FAnimNode_RetargetPoseFromMesh* InRetargetNode)
: FAnimInstanceProxy(InAnimInstance)
, RetargetNode(InRetargetNode)
{
}
void FMorphPresetRetargetPreviewAnimInstanceProxy::Initialize(UAnimInstance* InAnimInstance)
{
FAnimInstanceProxy::Initialize(InAnimInstance);
FAnimationInitializeContext InitContext(this);
RetargetNode->Initialize_AnyThread(InitContext);
}
void FMorphPresetRetargetPreviewAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds)
{
FAnimInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds);
if (RetargetNode && RetargetNode->HasPreUpdate())
{
RetargetNode->PreUpdate(InAnimInstance);
}
}
void FMorphPresetRetargetPreviewAnimInstanceProxy::CacheBones()
{
if (bBoneCachesInvalidated && RetargetNode)
{
FAnimationCacheBonesContext Context(this);
RetargetNode->CacheBones_AnyThread(Context);
bBoneCachesInvalidated = false;
}
}
bool FMorphPresetRetargetPreviewAnimInstanceProxy::Evaluate(FPoseContext& Output)
{
FAnimInstanceProxy::Evaluate(Output);
if (RetargetNode)
{
RetargetNode->Evaluate_AnyThread(Output);
}
return true;
}
void FMorphPresetRetargetPreviewAnimInstanceProxy::UpdateAnimationNode(const FAnimationUpdateContext& InContext)
{
FAnimInstanceProxy::UpdateAnimationNode(InContext);
UpdateCounter.Increment();
if (RetargetNode)
{
RetargetNode->Update_AnyThread(InContext);
}
}
void FMorphPresetRetargetPreviewAnimInstanceProxy::Configure(
UIKRetargeter* InRetargeter,
TWeakObjectPtr<USkeletalMeshComponent> InSourceMeshComponent)
{
if (!RetargetNode)
{
return;
}
RetargetNode->IKRetargeterAsset = InRetargeter;
RetargetNode->RetargetFrom = ERetargetSourceMode::CustomSkeletalMeshComponent;
RetargetNode->SourceMeshComponent = InSourceMeshComponent;
RetargetNode->CustomRetargetProfile = FRetargetProfile();
RetargetNode->bSuppressWarnings = false;
if (FIKRetargetProcessor* Processor = RetargetNode->GetRetargetProcessor())
{
Processor->SetNeedsInitialized();
}
}
UMorphPresetRetargetPreviewAnimInstance::UMorphPresetRetargetPreviewAnimInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bUseMultiThreadedAnimationUpdate = false;
}
void UMorphPresetRetargetPreviewAnimInstance::Configure(
UIKRetargeter* InRetargeter,
TWeakObjectPtr<USkeletalMeshComponent> InSourceMeshComponent)
{
FMorphPresetRetargetPreviewAnimInstanceProxy& Proxy = GetProxyOnGameThread<FMorphPresetRetargetPreviewAnimInstanceProxy>();
Proxy.Configure(InRetargeter, InSourceMeshComponent);
}
void UMorphPresetRetargetPreviewAnimInstance::NativeInitializeAnimation()
{
FMorphPresetRetargetPreviewAnimInstanceProxy& Proxy = GetProxyOnGameThread<FMorphPresetRetargetPreviewAnimInstanceProxy>();
Proxy.Initialize(this);
}
FAnimInstanceProxy* UMorphPresetRetargetPreviewAnimInstance::CreateAnimInstanceProxy()
{
return new FMorphPresetRetargetPreviewAnimInstanceProxy(this, &RetargetNode);
}
@@ -9,6 +9,7 @@
#include "Morph/MorphPresetDataAsset.h"
#include "Morph/Editor/MorphPresetEditorController.h"
#include "Morph/AnimTimeline/SAnimEditorBase.h"
#include "Animation/AnimMontage.h"
#include "Animation/MorphTarget.h"
#include "Curves/RichCurve.h"
#include "Framework/Application/SlateApplication.h"
@@ -159,6 +160,152 @@ namespace
TWeakPtr<FMorphPresetEditorController> Controller;
};
class SMorphPresetMontageLane : public SLeafWidget
{
public:
SLATE_BEGIN_ARGS(SMorphPresetMontageLane) {}
SLATE_ARGUMENT(TWeakPtr<FMorphPresetEditorController>, Controller)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
Controller = InArgs._Controller;
}
virtual FVector2D ComputeDesiredSize(float) const override
{
return FVector2D(320.0f, 44.0f);
}
virtual int32 OnPaint(const FPaintArgs&, const FGeometry& AllottedGeometry, const FSlateRect&, FSlateWindowElementList& OutDrawElements,
int32 LayerId, const FWidgetStyle&, bool) const override
{
const TSharedPtr<FMorphPresetEditorController> PinnedController = Controller.Pin();
const UMorphPresetDataAsset* Preset = PinnedController.IsValid() ? PinnedController->GetPreset() : nullptr;
const UAnimMontage* Montage = Preset ? Preset->GetMontage() : nullptr;
const float TimelineLength = PinnedController.IsValid() ? FMath::Max(PinnedController->GetTimelineLength(), 0.01f) : 1.0f;
const float Width = AllottedGeometry.GetLocalSize().X;
const float Height = AllottedGeometry.GetLocalSize().Y;
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), FAppStyle::GetBrush("WhiteBrush"),
ESlateDrawEffect::None, FLinearColor(0.075f, 0.08f, 0.09f, 1.0f));
if (Preset && Montage)
{
const float MontageDuration = Montage->GetPlayLength();
const float StartTime = FMath::Max(Preset->MontageStartTime, 0.0f);
const float StartX = Width * (StartTime / TimelineLength);
const float EndX = Width * ((StartTime + MontageDuration) / TimelineLength);
const float BlockWidth = FMath::Max(EndX - StartX, 2.0f);
const FLinearColor BlockColor = bIsDragging ? FLinearColor(0.55f, 0.38f, 0.11f, 1.0f) : FLinearColor(0.36f, 0.22f, 0.48f, 1.0f);
FSlateDrawElement::MakeBox(OutDrawElements, LayerId + 1,
AllottedGeometry.ToPaintGeometry(FVector2f(BlockWidth, Height - 6.0f), FSlateLayoutTransform(FVector2f(StartX, 3.0f))),
FAppStyle::GetBrush("WhiteBrush"), ESlateDrawEffect::None, BlockColor);
const FString Label = FString::Printf(TEXT("%s %.2fs"), *Montage->GetName(), MontageDuration);
FSlateDrawElement::MakeText(
OutDrawElements,
LayerId + 2,
AllottedGeometry.ToPaintGeometry(FVector2f(FMath::Max(BlockWidth - 10.0f, 1.0f), Height), FSlateLayoutTransform(FVector2f(StartX + 6.0f, 12.0f))),
Label,
FAppStyle::GetFontStyle("SmallFont"),
ESlateDrawEffect::None,
FLinearColor::White);
}
if (PinnedController.IsValid())
{
const float CurrentX = Width * (PinnedController->GetCurrentTime() / TimelineLength);
TArray<FVector2D> Points{ FVector2D(CurrentX, 0.0f), FVector2D(CurrentX, Height) };
FSlateDrawElement::MakeLines(OutDrawElements, LayerId + 3, AllottedGeometry.ToPaintGeometry(), Points,
ESlateDrawEffect::None, FLinearColor(1.0f, 0.5f, 0.25f, 1.0f), true, 2.0f);
}
return LayerId + 3;
}
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
{
if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton)
{
return FReply::Unhandled();
}
const TSharedPtr<FMorphPresetEditorController> PinnedController = Controller.Pin();
UMorphPresetDataAsset* Preset = PinnedController.IsValid() ? PinnedController->GetPreset() : nullptr;
if (!Preset || !Preset->GetMontage())
{
return FReply::Unhandled();
}
const float Width = FMath::Max(MyGeometry.GetLocalSize().X, 1.0f);
const float TimelineLength = FMath::Max(PinnedController->GetTimelineLength(), 0.01f);
const float LocalX = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X;
const float MontageDuration = Preset->GetMontage()->GetPlayLength();
const float MontageStartX = Width * (FMath::Max(Preset->MontageStartTime, 0.0f) / TimelineLength);
const float MontageEndX = Width * ((FMath::Max(Preset->MontageStartTime, 0.0f) + MontageDuration) / TimelineLength);
if (LocalX < MontageStartX || LocalX > MontageEndX)
{
return FReply::Unhandled();
}
Preset->Modify();
InitialStartTime = FMath::Max(Preset->MontageStartTime, 0.0f);
InitialTimelineLength = TimelineLength;
DragStartLocalX = LocalX;
bIsDragging = true;
PinnedController->SetPlaying(false);
return FReply::Handled().CaptureMouse(AsShared()).PreventThrottling();
}
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
{
if (!bIsDragging || !HasMouseCapture())
{
return FReply::Unhandled();
}
const TSharedPtr<FMorphPresetEditorController> PinnedController = Controller.Pin();
UMorphPresetDataAsset* Preset = PinnedController.IsValid() ? PinnedController->GetPreset() : nullptr;
if (!Preset)
{
return FReply::Unhandled();
}
const float Width = FMath::Max(MyGeometry.GetLocalSize().X, 1.0f);
const float DeltaX = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()).X - DragStartLocalX;
const float DeltaTime = (DeltaX / Width) * InitialTimelineLength;
Preset->MontageStartTime = FMath::Max(InitialStartTime + DeltaTime, 0.0f);
PinnedController->NotifyPresetEdited(false);
Invalidate(EInvalidateWidgetReason::Paint);
return FReply::Handled().PreventThrottling();
}
virtual FReply OnMouseButtonUp(const FGeometry&, const FPointerEvent& MouseEvent) override
{
if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton || !bIsDragging)
{
return FReply::Unhandled();
}
bIsDragging = false;
if (const TSharedPtr<FMorphPresetEditorController> PinnedController = Controller.Pin())
{
PinnedController->NotifyPresetEdited(true);
}
return FReply::Handled().ReleaseMouseCapture();
}
private:
TWeakPtr<FMorphPresetEditorController> Controller;
float DragStartLocalX = 0.0f;
float InitialStartTime = 0.0f;
float InitialTimelineLength = 1.0f;
bool bIsDragging = false;
};
class SMorphPresetClipLane : public SLeafWidget
{
public:
@@ -1000,6 +1147,29 @@ namespace
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(TimelineEdgePadding, 0.0f, TimelineEdgePadding, 6.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(FMargin(0.0f, 0.0f, 8.0f, 0.0f))
[
SNew(SBox)
.WidthOverride(TimelineNameColumnWidth)
[
SNew(STextBlock)
.Text(LOCTEXT("HeaderMontage", "Montage"))
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SMorphPresetMontageLane)
.Controller(Controller)
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SAssignNew(LocalScrollBox, SScrollBox)
@@ -4,6 +4,7 @@
#include "AssetRegistry/AssetData.h"
#include "AdvancedPreviewScene.h"
#include "Animation/AnimMontage.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "EditorViewportClient.h"
#include "Engine/SkeletalMesh.h"
@@ -11,6 +12,7 @@
#include "Morph/LipSyncMappingDataAsset.h"
#include "Morph/Editor/MorphPresetEditorController.h"
#include "PropertyCustomizationHelpers.h"
#include "Retargeter/IKRetargeter.h"
#include "SEditorViewport.h"
#include "Sound/SoundBase.h"
#include "ToolMenus.h"
@@ -200,7 +202,6 @@ void SMorphPresetViewportPanel::Construct(const FArguments& InArgs)
if (Controller.IsValid())
{
Controller->OnPreviewSceneChanged().AddSP(this, &SMorphPresetViewportPanel::RebuildContent);
Controller->OnPresetChanged().AddSP(this, &SMorphPresetViewportPanel::RebuildContent);
}
ChildSlot
@@ -218,7 +219,6 @@ SMorphPresetViewportPanel::~SMorphPresetViewportPanel()
if (Controller.IsValid())
{
Controller->OnPreviewSceneChanged().RemoveAll(this);
Controller->OnPresetChanged().RemoveAll(this);
}
}
@@ -268,6 +268,51 @@ void SMorphPresetViewportPanel::RebuildContent()
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("PreviewMontageLabel", "Montage"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(UAnimMontage::StaticClass())
.ObjectPath(this, &SMorphPresetViewportPanel::GetSelectedPreviewMontagePath)
.OnObjectChanged(this, &SMorphPresetViewportPanel::OnPreviewMontageChanged)
.DisplayUseSelected(true)
.DisplayBrowse(true)
.AllowClear(true)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(12.0f, 0.0f, 8.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("PreviewRetargeterLabel", "IK Retargeter"))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SObjectPropertyEntryBox)
.AllowedClass(UIKRetargeter::StaticClass())
.ObjectPath(this, &SMorphPresetViewportPanel::GetSelectedPreviewIKRetargeterPath)
.OnObjectChanged(this, &SMorphPresetViewportPanel::OnPreviewIKRetargeterChanged)
.DisplayUseSelected(true)
.DisplayBrowse(true)
.AllowClear(true)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
@@ -380,6 +425,22 @@ void SMorphPresetViewportPanel::OnPreviewMeshChanged(const FAssetData& AssetData
}
}
void SMorphPresetViewportPanel::OnPreviewMontageChanged(const FAssetData& AssetData)
{
if (Controller.IsValid())
{
Controller->SetPreviewMontage(Cast<UAnimMontage>(AssetData.GetAsset()));
}
}
void SMorphPresetViewportPanel::OnPreviewIKRetargeterChanged(const FAssetData& AssetData)
{
if (Controller.IsValid())
{
Controller->SetPreviewIKRetargeter(Cast<UIKRetargeter>(AssetData.GetAsset()));
}
}
void SMorphPresetViewportPanel::OnLipSyncSoundChanged(const FAssetData& AssetData)
{
if (Controller.IsValid())
@@ -409,6 +470,32 @@ FString SMorphPresetViewportPanel::GetSelectedPreviewMeshPath() const
return FString();
}
FString SMorphPresetViewportPanel::GetSelectedPreviewMontagePath() const
{
if (Controller.IsValid())
{
if (UAnimMontage* Montage = Controller->GetPreviewMontage())
{
return Montage->GetPathName();
}
}
return FString();
}
FString SMorphPresetViewportPanel::GetSelectedPreviewIKRetargeterPath() const
{
if (Controller.IsValid())
{
if (UIKRetargeter* Retargeter = Controller->GetPreviewIKRetargeter())
{
return Retargeter->GetPathName();
}
}
return FString();
}
FString SMorphPresetViewportPanel::GetSelectedLipSyncSoundPath() const
{
if (Controller.IsValid())