Refactoring
This commit is contained in:
@@ -8,7 +8,7 @@ public class FunnyGirl : ModuleRules
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "MediaAssets", "UMG" });
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "HTTP", "Json", "JsonUtilities", "AssetRegistry", "MediaAssets", "UMG", "IKRig" });
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "Morph/MorphControllerComponent.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Engine/World.h"
|
||||
@@ -71,6 +73,16 @@ USkeletalMeshComponent* UMorphControllerComponent::ResolveMeshComponent() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UMorphControllerComponent::ResolveAnimationMeshComponent() const
|
||||
{
|
||||
if (AnimationMeshComponent)
|
||||
{
|
||||
return AnimationMeshComponent;
|
||||
}
|
||||
|
||||
return ResolveMeshComponent();
|
||||
}
|
||||
|
||||
bool UMorphControllerComponent::PresetHasAnyPositiveCurveValue(const UMorphPresetDataAsset& Preset) const
|
||||
{
|
||||
for (const FMorphTimelineClip& Clip : Preset.MorphTargets)
|
||||
@@ -206,6 +218,11 @@ bool UMorphControllerComponent::AddPreset(UMorphPresetDataAsset* Preset, float W
|
||||
UE_LOG(LogMorphController, Warning, TEXT("AddPreset accepted on %s, but no SkeletalMeshComponent was found. Set TargetMeshComponent or place the component on an actor with a skeletal mesh."), *GetNameSafe(GetOwner()));
|
||||
}
|
||||
|
||||
if (bPlayPresetMontages)
|
||||
{
|
||||
PlayPresetMontage(Preset);
|
||||
}
|
||||
|
||||
if (!PresetHasAnyPositiveCurveValue(*Preset) && bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("AddPreset accepted on %s, but preset %s has no positive curve key values. Default curve keys are 0, so raise keys in the clip curve editor to see morph output."), *GetNameSafe(GetOwner()), *GetNameSafe(Preset));
|
||||
@@ -250,6 +267,54 @@ bool UMorphControllerComponent::PlayPreset(UMorphPresetDataAsset* Preset, float
|
||||
return AddPreset(Preset, Weight);
|
||||
}
|
||||
|
||||
bool UMorphControllerComponent::PlayPresetMontage(UMorphPresetDataAsset* Preset)
|
||||
{
|
||||
if (!Preset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UAnimMontage* Montage = Preset->GetMontage();
|
||||
if (!Montage)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* Mesh = ResolveAnimationMeshComponent();
|
||||
if (!Mesh)
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("PlayPresetMontage ignored on %s: no animation mesh component was found for montage %s."), *GetNameSafe(GetOwner()), *GetNameSafe(Montage));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UAnimInstance* AnimInstance = Mesh->GetAnimInstance();
|
||||
if (!AnimInstance)
|
||||
{
|
||||
if (bLogWarnings)
|
||||
{
|
||||
UE_LOG(LogMorphController, Warning, TEXT("PlayPresetMontage ignored on %s: animation mesh %s has no AnimInstance."), *GetNameSafe(GetOwner()), *GetNameSafe(Mesh));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const float PlayRate = FMath::Max(Preset->MontagePlayRate, 0.01f);
|
||||
const float Duration = AnimInstance->Montage_Play(Montage, PlayRate, EMontagePlayReturnType::MontageLength, 0.0f);
|
||||
return Duration > 0.0f;
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::SetMontageMeshComponent(USkeletalMeshComponent* InMontageMeshComponent)
|
||||
{
|
||||
AnimationMeshComponent = InMontageMeshComponent;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UMorphControllerComponent::GetMontageMeshComponent() const
|
||||
{
|
||||
return ResolveAnimationMeshComponent();
|
||||
}
|
||||
|
||||
void UMorphControllerComponent::RemovePreset(UMorphPresetDataAsset* Preset)
|
||||
{
|
||||
if (!Preset) return;
|
||||
@@ -301,6 +366,10 @@ void UMorphControllerComponent::SetActivePresets(const TArray<FActiveMorphPreset
|
||||
if (NewSlot.TimelineDuration > 0.0f)
|
||||
{
|
||||
ActivePresets.Add(NewSlot);
|
||||
if (bPlayPresetMontages)
|
||||
{
|
||||
PlayPresetMontage(NewSlot.Preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
|
||||
#include "Animation/AnimMontage.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float MorphPresetMinMorphTargetValue = -1.0f;
|
||||
@@ -64,6 +66,10 @@ float UMorphPresetDataAsset::GetTimelineLength() const
|
||||
MaxEnd = FMath::Max(MaxEnd, Clip.StartTime + Clip.Duration);
|
||||
}
|
||||
}
|
||||
if (const UAnimMontage* PreviewMontage = GetMontage())
|
||||
{
|
||||
MaxEnd = FMath::Max(MaxEnd, FMath::Max(MontageStartTime, 0.0f) + PreviewMontage->GetPlayLength());
|
||||
}
|
||||
return MaxEnd;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,22 @@ bool SaveLightSaveObject(const TSharedRef<FJsonObject>& SaveObject, FString& Out
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteDirectoryIfExists(const FString& Directory, FString& OutError)
|
||||
{
|
||||
if (!IFileManager::Get().DirectoryExists(*Directory))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IFileManager::Get().DeleteDirectory(*Directory, false, true))
|
||||
{
|
||||
OutError = FString::Printf(TEXT("Could not delete save directory: %s"), *Directory);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool UFunnyGirlUtilityBlueprintLibrary::SaveMorphTargetNamesToFile(
|
||||
@@ -361,6 +377,25 @@ bool UFunnyGirlUtilityBlueprintLibrary::DeleteLightString(const FString& Field,
|
||||
return SaveLightSaveObject(SaveObject.ToSharedRef(), OutError);
|
||||
}
|
||||
|
||||
bool UFunnyGirlUtilityBlueprintLibrary::ClearFunnyGirlSaves(FString& OutError)
|
||||
{
|
||||
OutError.Reset();
|
||||
|
||||
const FString ChatDirectory = FPaths::ProjectSavedDir() / TEXT("Chat");
|
||||
if (!DeleteDirectoryIfExists(ChatDirectory, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FString LightSaveDirectory = FPaths::GetPath(GetLightSaveFilePath());
|
||||
if (!DeleteDirectoryIfExists(LightSaveDirectory, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFunnyGirlUtilityBlueprintLibrary::OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url)
|
||||
{
|
||||
if (!MediaPlayer || !StreamMediaSource || !Url.Contains(TEXT("://")))
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "MorphControllerComponent.generated.h"
|
||||
|
||||
class USkeletalMeshComponent;
|
||||
class UAnimMontage;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMorphLayerBlendMode : uint8
|
||||
@@ -62,6 +63,18 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller")
|
||||
TObjectPtr<USkeletalMeshComponent> TargetMeshComponent;
|
||||
|
||||
/** Mesh that owns body animation montages. Use this when the visible girl mesh retargets/copies motion from a separate animation mesh. Falls back to Target Mesh Component when unset. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Montage", meta = (
|
||||
DisplayName = "Montage Mesh Component",
|
||||
ToolTip = "Skeletal mesh component where preset montages are played. Leave empty to use the morph target mesh."))
|
||||
TObjectPtr<USkeletalMeshComponent> AnimationMeshComponent;
|
||||
|
||||
/** Automatically play a preset's optional Montage on Animation Mesh Component when the preset starts. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller|Montage", meta = (
|
||||
DisplayName = "Play Preset Montages",
|
||||
ToolTip = "When enabled, Add Preset, Play Morph Preset, and Set Active Presets play the montage stored in each preset."))
|
||||
bool bPlayPresetMontages = true;
|
||||
|
||||
/** Speed used when blending preset weight toward the requested weight. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Morph Controller", meta = (ClampMin = "0.1"))
|
||||
float BlendSpeed = 4.0f;
|
||||
@@ -112,6 +125,18 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Play Morph Preset"))
|
||||
bool PlayPreset(UMorphPresetDataAsset* Preset, float Weight = 1.0f);
|
||||
|
||||
/** Play the montage assigned inside a morph preset on the Animation Mesh Component. */
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Play Preset Montage"))
|
||||
bool PlayPresetMontage(UMorphPresetDataAsset* Preset);
|
||||
|
||||
/** Set the mesh component used for preset montage playback. */
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Set Montage Mesh Component"))
|
||||
void SetMontageMeshComponent(USkeletalMeshComponent* InMontageMeshComponent);
|
||||
|
||||
/** Get the explicit montage mesh component, or the resolved fallback mesh when none is set. */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "FunnyGirl|Morph Controller|Montage", meta = (DisplayName = "Get Montage Mesh Component"))
|
||||
USkeletalMeshComponent* GetMontageMeshComponent() const;
|
||||
|
||||
/** Remove a preset from active playback immediately. */
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Morph Controller", meta = (DisplayName = "Remove Preset"))
|
||||
void RemovePreset(UMorphPresetDataAsset* Preset);
|
||||
@@ -205,6 +230,7 @@ private:
|
||||
bool bMissingMeshWarningIssued = false;
|
||||
|
||||
USkeletalMeshComponent* ResolveMeshComponent() const;
|
||||
USkeletalMeshComponent* ResolveAnimationMeshComponent() const;
|
||||
bool PresetHasAnyPositiveCurveValue(const UMorphPresetDataAsset& Preset) const;
|
||||
void UpdateMorphTargets(float DeltaTime);
|
||||
void BlendMorphLayer(const TMap<FName, float>& LayerValues, float LayerWeight, EMorphLayerBlendMode BlendMode, TMap<FName, float>& InOutFinalValues) const;
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Curves/CurveFloat.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Retargeter/IKRetargeter.h"
|
||||
#include "MorphPresetDataAsset.generated.h"
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
@@ -41,6 +43,11 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Preview", meta = (DisplayName = "Preview Mesh"))
|
||||
TSoftObjectPtr<USkeletalMesh> PreviewMesh;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Preview|Montage", meta = (
|
||||
DisplayName = "Preview IK Retargeter",
|
||||
ToolTip = "IK Retargeter used by preview to drive the girl mesh from the animation mesh, matching the Retarget Pose From Mesh AnimBP path."))
|
||||
TSoftObjectPtr<UIKRetargeter> PreviewIKRetargeter;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset", meta = (DisplayName = "Display Name"))
|
||||
FText DisplayName;
|
||||
|
||||
@@ -52,6 +59,23 @@ public:
|
||||
ToolTip = "Morph clips placed on the preset timeline."))
|
||||
TArray<FMorphTimelineClip> MorphTargets;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Montage", meta = (
|
||||
DisplayName = "Montage",
|
||||
ToolTip = "Optional body montage associated with this emotion. Runtime should play it on the animation/source mesh, while morphs stay on the girl mesh."))
|
||||
TSoftObjectPtr<UAnimMontage> Montage;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Montage", meta = (
|
||||
DisplayName = "Runtime Montage Play Rate",
|
||||
ToolTip = "Play rate used when the morph controller plays this montage at runtime. The editor timeline preview keeps the block at the montage asset's real length.",
|
||||
ClampMin = "0.01"))
|
||||
float MontagePlayRate = 1.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset|Montage", meta = (
|
||||
DisplayName = "Montage Timeline Start",
|
||||
ToolTip = "Time on the morph preset timeline where the montage block starts in preview.",
|
||||
ClampMin = "0"))
|
||||
float MontageStartTime = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Morph Preset", meta = (
|
||||
DisplayName = "Duration",
|
||||
ToolTip = "Preset duration in seconds. 0 means auto-calculate from clips.",
|
||||
@@ -81,6 +105,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
float GetTimelineLength() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset|Montage")
|
||||
UAnimMontage* GetMontage() const { return Montage.LoadSynchronous(); }
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Morph Preset")
|
||||
bool HasPlayableContent() const { return IsValidPreset(); }
|
||||
|
||||
|
||||
@@ -56,6 +56,11 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Save")
|
||||
static bool DeleteLightString(const FString& Field, FString& OutError);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Save", meta = (
|
||||
DisplayName = "Clear Funny Girl Saves",
|
||||
ToolTip = "Deletes runtime save data written by Funny Girl systems: chat/player/semantic memory and light string saves. Exported files are not deleted."))
|
||||
static bool ClearFunnyGirlSaves(FString& OutError);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FunnyGirl|Media")
|
||||
static bool OpenStreamMediaSourceUrl(UMediaPlayer* MediaPlayer, UStreamMediaSource* StreamMediaSource, const FString& Url);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,6 @@ public class FunnyGirlEditor : ModuleRules
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "FunnyGirl" });
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "AdvancedPreviewScene", "AnimationDataController", "AnimGraph", "AssetTools", "AssetRegistry", "ContentBrowser", "EditorWidgets", "InputCore", "Json", "JsonUtilities", "KismetWidgets", "LevelEditor", "Persona", "PropertyEditor", "SequencerWidgets", "Slate", "SlateCore", "ToolMenus", "UnrealEd" });
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "AdvancedPreviewScene", "AnimationDataController", "AnimGraph", "AssetTools", "AssetRegistry", "ContentBrowser", "EditorWidgets", "InputCore", "IKRig", "Json", "JsonUtilities", "KismetWidgets", "LevelEditor", "Persona", "PropertyEditor", "SequencerWidgets", "Slate", "SlateCore", "ToolMenus", "UnrealEd" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+111
@@ -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())
|
||||
|
||||
@@ -8,7 +8,12 @@ class FUICommandList;
|
||||
class AActor;
|
||||
class IPersonaPreviewScene;
|
||||
class IPersonaToolkit;
|
||||
class UAnimationAsset;
|
||||
class UAnimMontage;
|
||||
class UAnimSequence;
|
||||
class UDebugSkelMeshComponent;
|
||||
class UMorphPresetRetargetPreviewAnimInstance;
|
||||
class UIKRetargeter;
|
||||
class ULipSyncComponent;
|
||||
class ULipSyncMappingDataAsset;
|
||||
class UMorphControllerComponent;
|
||||
@@ -27,6 +32,11 @@ public:
|
||||
UMorphPresetDataAsset* GetPreset() const;
|
||||
USkeletalMesh* GetPreviewMesh() const;
|
||||
bool SetPreviewMesh(USkeletalMesh* InPreviewMesh);
|
||||
USkeletalMesh* GetAnimationPreviewMesh() const;
|
||||
UAnimMontage* GetPreviewMontage() const;
|
||||
bool SetPreviewMontage(UAnimMontage* InMontage);
|
||||
UIKRetargeter* GetPreviewIKRetargeter() const;
|
||||
bool SetPreviewIKRetargeter(UIKRetargeter* InRetargeter);
|
||||
|
||||
TSharedPtr<IPersonaToolkit> GetPersonaToolkit() const { return PersonaToolkit; }
|
||||
TSharedPtr<IPersonaPreviewScene> GetPreviewScene() const;
|
||||
@@ -65,8 +75,15 @@ public:
|
||||
private:
|
||||
void RebuildPreviewScene();
|
||||
void RebuildPreviewAnimation();
|
||||
void RebuildAnimationPreviewMesh();
|
||||
void DestroyAnimationPreviewMesh();
|
||||
void ApplyPresetToPreview();
|
||||
void RefreshPreviewAnimationPose();
|
||||
void RefreshAnimationPreviewPose();
|
||||
void ConfigureRetargetPreview();
|
||||
void ClearRetargetPreview(bool bRestorePreviewAnimation);
|
||||
UAnimationAsset* ResolveMontagePreviewAnimation(float& OutAssetTime, float& OutAssetLength) const;
|
||||
bool HasRetargetPreview() const;
|
||||
void RebuildLipSyncPreviewComponents();
|
||||
void DestroyLipSyncPreviewComponents();
|
||||
void TickLipSyncPreviewComponents(float DeltaTime);
|
||||
@@ -79,6 +96,10 @@ private:
|
||||
TStrongObjectPtr<UAnimSequence> PreviewAnimation;
|
||||
TStrongObjectPtr<ULipSyncMappingDataAsset> DefaultLipSyncPreviewMapping;
|
||||
TWeakObjectPtr<USkeletalMesh> ActivePreviewMesh;
|
||||
TWeakObjectPtr<USkeletalMesh> ActiveAnimationPreviewMesh;
|
||||
TWeakObjectPtr<USkeletalMeshComponent> AnimationPreviewMeshComponent;
|
||||
TWeakObjectPtr<UMorphPresetRetargetPreviewAnimInstance> RetargetPreviewInstance;
|
||||
TWeakObjectPtr<UIKRetargeter> ActivePreviewIKRetargeter;
|
||||
TWeakObjectPtr<ULipSyncMappingDataAsset> LipSyncPreviewMapping;
|
||||
TWeakObjectPtr<USoundBase> LipSyncPreviewSound;
|
||||
TWeakObjectPtr<AActor> LipSyncPreviewActor;
|
||||
@@ -87,6 +108,7 @@ private:
|
||||
bool bIsPlaying = false;
|
||||
bool bLipSyncPreviewAudioActive = false;
|
||||
float CurrentTime = 0.0f;
|
||||
uint64 LastTickFrameNumber = MAX_uint64;
|
||||
float LipSyncPreviewSmoothness = 0.55f;
|
||||
FText LipSyncPreviewTranscript;
|
||||
FText PreviewStatusText;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Animation/AnimInstanceProxy.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "AnimNodes/AnimNode_RetargetPoseFromMesh.h"
|
||||
#include "MorphPresetRetargetPreviewAnimInstance.generated.h"
|
||||
|
||||
class UIKRetargeter;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
USTRUCT()
|
||||
struct FMorphPresetRetargetPreviewAnimInstanceProxy : public FAnimInstanceProxy
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FMorphPresetRetargetPreviewAnimInstanceProxy() = default;
|
||||
FMorphPresetRetargetPreviewAnimInstanceProxy(UAnimInstance* InAnimInstance, FAnimNode_RetargetPoseFromMesh* InRetargetNode);
|
||||
|
||||
virtual void Initialize(UAnimInstance* InAnimInstance) override;
|
||||
virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override;
|
||||
virtual void CacheBones() override;
|
||||
virtual bool Evaluate(FPoseContext& Output) override;
|
||||
virtual void UpdateAnimationNode(const FAnimationUpdateContext& InContext) override;
|
||||
|
||||
void Configure(UIKRetargeter* InRetargeter, TWeakObjectPtr<USkeletalMeshComponent> InSourceMeshComponent);
|
||||
|
||||
FAnimNode_RetargetPoseFromMesh* RetargetNode = nullptr;
|
||||
};
|
||||
|
||||
UCLASS(Transient, NotBlueprintable)
|
||||
class FUNNYGIRLEDITOR_API UMorphPresetRetargetPreviewAnimInstance : public UAnimInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UMorphPresetRetargetPreviewAnimInstance(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
void Configure(UIKRetargeter* InRetargeter, TWeakObjectPtr<USkeletalMeshComponent> InSourceMeshComponent);
|
||||
|
||||
protected:
|
||||
virtual void NativeInitializeAnimation() override;
|
||||
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
FAnimNode_RetargetPoseFromMesh RetargetNode;
|
||||
};
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class FMorphPresetEditorController;
|
||||
class UAnimMontage;
|
||||
class UIKRetargeter;
|
||||
class ULipSyncMappingDataAsset;
|
||||
class USoundBase;
|
||||
class USkeletalMesh;
|
||||
@@ -24,9 +26,13 @@ public:
|
||||
private:
|
||||
void RebuildContent();
|
||||
void OnPreviewMeshChanged(const struct FAssetData& AssetData);
|
||||
void OnPreviewMontageChanged(const struct FAssetData& AssetData);
|
||||
void OnPreviewIKRetargeterChanged(const struct FAssetData& AssetData);
|
||||
void OnLipSyncSoundChanged(const struct FAssetData& AssetData);
|
||||
void OnLipSyncMappingChanged(const struct FAssetData& AssetData);
|
||||
FString GetSelectedPreviewMeshPath() const;
|
||||
FString GetSelectedPreviewMontagePath() const;
|
||||
FString GetSelectedPreviewIKRetargeterPath() const;
|
||||
FString GetSelectedLipSyncSoundPath() const;
|
||||
FString GetSelectedLipSyncMappingPath() const;
|
||||
FReply OnPlayLipSyncClicked();
|
||||
|
||||
Reference in New Issue
Block a user