Add Games File
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class FunnyGirlEditor : ModuleRules
|
||||
{
|
||||
public FunnyGirlEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
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" });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_DirectorPrompt.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/DirectorPromptDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_DirectorPrompt"
|
||||
|
||||
FText FAssetTypeActions_DirectorPrompt::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "Director Prompt");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_DirectorPrompt::GetTypeColor() const
|
||||
{
|
||||
return FColor(120, 90, 210);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_DirectorPrompt::GetSupportedClass() const
|
||||
{
|
||||
return UDirectorPromptDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_DirectorPrompt::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_DirectorPrompt : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_Identity.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/IdentityDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_Identity"
|
||||
|
||||
FText FAssetTypeActions_Identity::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "Identity");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_Identity::GetTypeColor() const
|
||||
{
|
||||
return FColor(48, 190, 150);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_Identity::GetSupportedClass() const
|
||||
{
|
||||
return UIdentityDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_Identity::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_Identity : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_LLMProvider.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/LLMProviderDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_LLMProvider"
|
||||
|
||||
FText FAssetTypeActions_LLMProvider::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "LLM Provider");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_LLMProvider::GetTypeColor() const
|
||||
{
|
||||
return FColor(72, 128, 255);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_LLMProvider::GetSupportedClass() const
|
||||
{
|
||||
return ULLMProviderDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_LLMProvider::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetLLMAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_LLMProvider : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_PersonalityTrait.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "Identity/PersonalityTraitDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_PersonalityTrait"
|
||||
|
||||
FText FAssetTypeActions_PersonalityTrait::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "Personality Trait");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_PersonalityTrait::GetTypeColor() const
|
||||
{
|
||||
return FColor(225, 150, 65);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_PersonalityTrait::GetSupportedClass() const
|
||||
{
|
||||
return UPersonalityTraitDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_PersonalityTrait::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_PersonalityTrait : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_SystemEvent.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/SystemEventDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_SystemEvent"
|
||||
|
||||
FText FAssetTypeActions_SystemEvent::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "System Event");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_SystemEvent::GetTypeColor() const
|
||||
{
|
||||
return FColor(205, 95, 205);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_SystemEvent::GetSupportedClass() const
|
||||
{
|
||||
return USystemEventDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_SystemEvent::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_SystemEvent : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_WorldContext.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "World/WorldContextDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "AssetTypeActions_WorldContext"
|
||||
|
||||
FText FAssetTypeActions_WorldContext::GetName() const
|
||||
{
|
||||
return LOCTEXT("Name", "World Context");
|
||||
}
|
||||
|
||||
FColor FAssetTypeActions_WorldContext::GetTypeColor() const
|
||||
{
|
||||
return FColor(80, 170, 220);
|
||||
}
|
||||
|
||||
UClass* FAssetTypeActions_WorldContext::GetSupportedClass() const
|
||||
{
|
||||
return UWorldContextDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FAssetTypeActions_WorldContext::GetCategories()
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FAssetTypeActions_WorldContext : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/DirectorPromptDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/DirectorPromptDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "DirectorPromptDataAssetFactory"
|
||||
|
||||
UDirectorPromptDataAssetFactory::UDirectorPromptDataAssetFactory()
|
||||
{
|
||||
SupportedClass = UDirectorPromptDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UDirectorPromptDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UDirectorPromptDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText UDirectorPromptDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "Director Prompt");
|
||||
}
|
||||
|
||||
uint32 UDirectorPromptDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool UDirectorPromptDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "DirectorPromptDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class UDirectorPromptDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UDirectorPromptDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/DirectorToolBlueprintFactory.h"
|
||||
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/BlueprintGeneratedClass.h"
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "LLM/BrainToolBase.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "DirectorToolBlueprintFactory"
|
||||
|
||||
UDirectorToolBlueprintFactory::UDirectorToolBlueprintFactory()
|
||||
{
|
||||
SupportedClass = UBlueprint::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UDirectorToolBlueprintFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return FKismetEditorUtilities::CreateBlueprint(UBrainToolBase::StaticClass(), InParent, InName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass());
|
||||
}
|
||||
|
||||
FText UDirectorToolBlueprintFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "Brain Tool");
|
||||
}
|
||||
|
||||
uint32 UDirectorToolBlueprintFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool UDirectorToolBlueprintFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "DirectorToolBlueprintFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class UDirectorToolBlueprintFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UDirectorToolBlueprintFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/IdentityDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/IdentityDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "IdentityDataAssetFactory"
|
||||
|
||||
UIdentityDataAssetFactory::UIdentityDataAssetFactory()
|
||||
{
|
||||
SupportedClass = UIdentityDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UIdentityDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UIdentityDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText UIdentityDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "Identity");
|
||||
}
|
||||
|
||||
uint32 UIdentityDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool UIdentityDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "IdentityDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class UIdentityDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UIdentityDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/LLMProviderDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/LLMProviderDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "LLMProviderDataAssetFactory"
|
||||
|
||||
ULLMProviderDataAssetFactory::ULLMProviderDataAssetFactory()
|
||||
{
|
||||
SupportedClass = ULLMProviderDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* ULLMProviderDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<ULLMProviderDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText ULLMProviderDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "LLM Provider");
|
||||
}
|
||||
|
||||
uint32 ULLMProviderDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetLLMAssetCategory();
|
||||
}
|
||||
|
||||
bool ULLMProviderDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "LLMProviderDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class ULLMProviderDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULLMProviderDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/PersonalityTraitDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "Identity/PersonalityTraitDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "PersonalityTraitDataAssetFactory"
|
||||
|
||||
UPersonalityTraitDataAssetFactory::UPersonalityTraitDataAssetFactory()
|
||||
{
|
||||
SupportedClass = UPersonalityTraitDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UPersonalityTraitDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UPersonalityTraitDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText UPersonalityTraitDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "Personality Trait");
|
||||
}
|
||||
|
||||
uint32 UPersonalityTraitDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool UPersonalityTraitDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "PersonalityTraitDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class UPersonalityTraitDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPersonalityTraitDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/SystemEventDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "LLM/SystemEventDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "SystemEventDataAssetFactory"
|
||||
|
||||
USystemEventDataAssetFactory::USystemEventDataAssetFactory()
|
||||
{
|
||||
SupportedClass = USystemEventDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* USystemEventDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<USystemEventDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText USystemEventDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "System Event");
|
||||
}
|
||||
|
||||
uint32 USystemEventDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool USystemEventDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "SystemEventDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class USystemEventDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
USystemEventDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Factories/WorldContextDataAssetFactory.h"
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
#include "World/WorldContextDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "WorldContextDataAssetFactory"
|
||||
|
||||
UWorldContextDataAssetFactory::UWorldContextDataAssetFactory()
|
||||
{
|
||||
SupportedClass = UWorldContextDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UWorldContextDataAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UWorldContextDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText UWorldContextDataAssetFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("DisplayName", "World Context");
|
||||
}
|
||||
|
||||
uint32 UWorldContextDataAssetFactory::GetMenuCategories() const
|
||||
{
|
||||
return FFunnyGirlEditorModule::GetNarrativeAssetCategory();
|
||||
}
|
||||
|
||||
bool UWorldContextDataAssetFactory::ShouldShowInNewMenu() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "WorldContextDataAssetFactory.generated.h"
|
||||
|
||||
UCLASS(hidecategories = Object)
|
||||
class UWorldContextDataAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UWorldContextDataAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FText GetDisplayName() const override;
|
||||
virtual uint32 GetMenuCategories() const override;
|
||||
virtual bool ShouldShowInNewMenu() const override;
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "FunnyGirlEditorModule.h"
|
||||
|
||||
#include "AssetTypeActions/AssetTypeActions_DirectorPrompt.h"
|
||||
#include "AssetTypeActions/AssetTypeActions_Identity.h"
|
||||
#include "AssetTypeActions/AssetTypeActions_LLMProvider.h"
|
||||
#include "AssetTypeActions/AssetTypeActions_PersonalityTrait.h"
|
||||
#include "AssetTypeActions/AssetTypeActions_SystemEvent.h"
|
||||
#include "AssetTypeActions/AssetTypeActions_WorldContext.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "IAssetTools.h"
|
||||
#include "IAssetTypeActions.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"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FunnyGirlEditor"
|
||||
|
||||
EAssetTypeCategories::Type FFunnyGirlEditorModule::LLMAssetCategory = EAssetTypeCategories::Misc;
|
||||
EAssetTypeCategories::Type FFunnyGirlEditorModule::NarrativeAssetCategory = EAssetTypeCategories::Misc;
|
||||
|
||||
void FFunnyGirlEditorModule::StartupModule()
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
LLMAssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("LLM")), LOCTEXT("LLMAssetCategory", "LLM"));
|
||||
NarrativeAssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Narrative")), LOCTEXT("NarrativeAssetCategory", "Narrative"));
|
||||
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_LLMProvider>());
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_DirectorPrompt>());
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_Identity>());
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_PersonalityTrait>());
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_SystemEvent>());
|
||||
RegisterAssetTypeAction(MakeShared<FAssetTypeActions_WorldContext>());
|
||||
RegisterAssetTypeAction(MakeShared<FMorphPresetAssetTypeActions>());
|
||||
RegisterAssetTypeAction(MakeShared<FLipSyncMappingAssetTypeActions>());
|
||||
|
||||
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
|
||||
PropertyEditorModule.RegisterCustomClassLayout(
|
||||
UMorphPresetDataAsset::StaticClass()->GetFName(),
|
||||
FOnGetDetailCustomizationInstance::CreateStatic(&FMorphPresetDetailsCustomization::MakeInstance));
|
||||
PropertyEditorModule.NotifyCustomizationModuleChanged();
|
||||
bMorphPresetCustomizationRegistered = true;
|
||||
}
|
||||
|
||||
void FFunnyGirlEditorModule::ShutdownModule()
|
||||
{
|
||||
if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetTools")))
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
for (const TSharedPtr<IAssetTypeActions>& AssetTypeAction : RegisteredAssetTypeActions)
|
||||
{
|
||||
if (AssetTypeAction.IsValid())
|
||||
{
|
||||
AssetTools.UnregisterAssetTypeActions(AssetTypeAction.ToSharedRef());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegisteredAssetTypeActions.Empty();
|
||||
|
||||
if (bMorphPresetCustomizationRegistered && FModuleManager::Get().IsModuleLoaded(TEXT("PropertyEditor")))
|
||||
{
|
||||
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
|
||||
PropertyEditorModule.UnregisterCustomClassLayout(UMorphPresetDataAsset::StaticClass()->GetFName());
|
||||
PropertyEditorModule.NotifyCustomizationModuleChanged();
|
||||
bMorphPresetCustomizationRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
EAssetTypeCategories::Type FFunnyGirlEditorModule::GetLLMAssetCategory()
|
||||
{
|
||||
return LLMAssetCategory;
|
||||
}
|
||||
|
||||
EAssetTypeCategories::Type FFunnyGirlEditorModule::GetNarrativeAssetCategory()
|
||||
{
|
||||
return NarrativeAssetCategory;
|
||||
}
|
||||
|
||||
void FFunnyGirlEditorModule::RegisterAssetTypeAction(const TSharedRef<IAssetTypeActions>& AssetTypeAction)
|
||||
{
|
||||
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
AssetTools.RegisterAssetTypeActions(AssetTypeAction);
|
||||
RegisteredAssetTypeActions.Add(AssetTypeAction);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FFunnyGirlEditorModule, FunnyGirlEditor)
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "AssetTypeCategories.h"
|
||||
#include "Modules/ModuleInterface.h"
|
||||
|
||||
class IAssetTypeActions;
|
||||
|
||||
class FFunnyGirlEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
static EAssetTypeCategories::Type GetLLMAssetCategory();
|
||||
static EAssetTypeCategories::Type GetNarrativeAssetCategory();
|
||||
|
||||
private:
|
||||
static EAssetTypeCategories::Type LLMAssetCategory;
|
||||
static EAssetTypeCategories::Type NarrativeAssetCategory;
|
||||
|
||||
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
|
||||
bool bMorphPresetCustomizationRegistered = false;
|
||||
|
||||
void RegisterAssetTypeAction(const TSharedRef<IAssetTypeActions>& AssetTypeAction);
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/AnimTimeline/SAnimEditorBase.h"
|
||||
|
||||
#include "AnimPreviewInstance.h"
|
||||
#include "Animation/AnimSequenceBase.h"
|
||||
#include "Animation/BlendSpace.h"
|
||||
#include "Animation/DebugSkelMeshComponent.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/SOverlay.h"
|
||||
|
||||
void SAnimEditorBase::Construct(const FArguments& InArgs, const TSharedPtr<IPersonaPreviewScene>& InPreviewScene)
|
||||
{
|
||||
PreviewScenePtr = InPreviewScene;
|
||||
OnObjectsSelectedDelegate = InArgs._OnObjectsSelected;
|
||||
SetInputViewRange(0.0f, GetSequenceLength());
|
||||
|
||||
const TSharedRef<SWidget> TimelineWidget = InArgs._DisplayAnimTimeline
|
||||
? CreateAnimTimelineWidget()
|
||||
: SNullWidget::NullWidget;
|
||||
|
||||
TSharedPtr<SVerticalBox> RootBox;
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(RootBox, SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
[
|
||||
SNew(SOverlay)
|
||||
+ SOverlay::Slot()
|
||||
[
|
||||
SAssignNew(NonScrollEditorPanels, SVerticalBox)
|
||||
]
|
||||
+ SOverlay::Slot()
|
||||
[
|
||||
TimelineWidget
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if (InArgs._DisplayAnimScrubBar)
|
||||
{
|
||||
RootBox->AddSlot()
|
||||
.AutoHeight()
|
||||
[
|
||||
ConstructAnimScrubPanel(InArgs._DisplayAnimScrubBarEditing)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
void SAnimEditorBase::SetInputViewRange(float InViewMinInput, float InViewMaxInput)
|
||||
{
|
||||
ViewMaxInput = FMath::Min(InViewMaxInput, GetSequenceLength());
|
||||
ViewMinInput = FMath::Max(InViewMinInput, 0.0f);
|
||||
}
|
||||
|
||||
float SAnimEditorBase::GetScrubValue() const
|
||||
{
|
||||
if (PreviewScenePtr.IsValid())
|
||||
{
|
||||
if (UDebugSkelMeshComponent* PreviewMesh = PreviewScenePtr.Pin()->GetPreviewMeshComponent())
|
||||
{
|
||||
if (PreviewMesh->PreviewInstance)
|
||||
{
|
||||
return PreviewMesh->PreviewInstance->GetCurrentTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void SAnimEditorBase::OnSelectionChanged(const TArray<UObject*>& SelectedItems)
|
||||
{
|
||||
OnObjectsSelectedDelegate.ExecuteIfBound(SelectedItems);
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SAnimEditorBase::CreateDocumentAnchor()
|
||||
{
|
||||
return SNullWidget::NullWidget;
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SAnimEditorBase::ConstructAnimScrubPanel(bool bDisplayAnimScrubBarEditing)
|
||||
{
|
||||
return SNullWidget::NullWidget;
|
||||
}
|
||||
|
||||
float SAnimEditorBase::GetSequenceLength() const
|
||||
{
|
||||
if (const UAnimSequenceBase* AnimSequence = Cast<UAnimSequenceBase>(GetEditorObject()))
|
||||
{
|
||||
return AnimSequence->GetPlayLength();
|
||||
}
|
||||
|
||||
if (const UBlendSpace* BlendSpace = Cast<UBlendSpace>(GetEditorObject()))
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/LipSyncMappingAssetFactory.h"
|
||||
|
||||
#include "Morph/LipSyncMappingDataAsset.h"
|
||||
|
||||
ULipSyncMappingAssetFactory::ULipSyncMappingAssetFactory()
|
||||
{
|
||||
SupportedClass = ULipSyncMappingDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* ULipSyncMappingAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<ULipSyncMappingDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FString ULipSyncMappingAssetFactory::GetDefaultNewAssetName() const
|
||||
{
|
||||
return TEXT("LipSyncMapping");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/LipSyncMappingAssetTypeActions.h"
|
||||
|
||||
#include "Morph/LipSyncMappingDataAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "LipSyncMapping"
|
||||
|
||||
FText FLipSyncMappingAssetTypeActions::GetName() const
|
||||
{
|
||||
return LOCTEXT("AssetTypeActions_LipSyncMapping", "Lip Sync Mapping");
|
||||
}
|
||||
|
||||
FColor FLipSyncMappingAssetTypeActions::GetTypeColor() const
|
||||
{
|
||||
return FColor(120, 210, 255);
|
||||
}
|
||||
|
||||
UClass* FLipSyncMappingAssetTypeActions::GetSupportedClass() const
|
||||
{
|
||||
return ULipSyncMappingDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FLipSyncMappingAssetTypeActions::GetCategories()
|
||||
{
|
||||
return EAssetTypeCategories::Animation;
|
||||
}
|
||||
|
||||
FText FLipSyncMappingAssetTypeActions::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("LipSyncMappingDescription", "Maps lip sync viseme names to character morph targets.");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetAssetEditor.h"
|
||||
|
||||
#include "IDetailsView.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Morph/Editor/MorphPresetEditorController.h"
|
||||
#include "Morph/Editor/MorphPresetTimelinePanel.h"
|
||||
#include "Morph/Editor/MorphPresetViewportPanel.h"
|
||||
#include "PropertyEditorModule.h"
|
||||
#include "Widgets/Docking/SDockTab.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MorphPresetAssetEditor"
|
||||
|
||||
const FName FMorphPresetAssetEditor::EditorName(TEXT("MorphPresetAssetEditor"));
|
||||
const FName FMorphPresetAssetEditor::ViewportTabId(TEXT("MorphPresetEditor_Viewport"));
|
||||
const FName FMorphPresetAssetEditor::TimelineTabId(TEXT("MorphPresetEditor_Timeline"));
|
||||
const FName FMorphPresetAssetEditor::DetailsTabId(TEXT("MorphPresetEditor_Details"));
|
||||
|
||||
void FMorphPresetAssetEditor::InitMorphPresetAssetEditor(
|
||||
const EToolkitMode::Type Mode,
|
||||
const TSharedPtr<IToolkitHost>& InitToolkitHost,
|
||||
UMorphPresetDataAsset* InAsset)
|
||||
{
|
||||
Preset = InAsset;
|
||||
EditorController = MakeShared<FMorphPresetEditorController>(InAsset);
|
||||
EditorController->OnPresetChanged().AddSP(this, &FMorphPresetAssetEditor::HandlePresetChanged);
|
||||
EditorController->Initialize();
|
||||
|
||||
TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout("MorphPresetEditorLayout_V2")
|
||||
->AddArea(
|
||||
FTabManager::NewPrimaryArea()
|
||||
->SetOrientation(Orient_Horizontal)
|
||||
->Split(
|
||||
FTabManager::NewSplitter()
|
||||
->SetOrientation(Orient_Vertical)
|
||||
->Split(
|
||||
FTabManager::NewStack()
|
||||
->SetSizeCoefficient(0.68f)
|
||||
->AddTab(ViewportTabId, ETabState::OpenedTab)
|
||||
)
|
||||
->Split(
|
||||
FTabManager::NewStack()
|
||||
->SetSizeCoefficient(0.32f)
|
||||
->AddTab(TimelineTabId, ETabState::OpenedTab)
|
||||
)
|
||||
)
|
||||
->Split(
|
||||
FTabManager::NewStack()
|
||||
->SetSizeCoefficient(0.25f)
|
||||
->AddTab(DetailsTabId, ETabState::OpenedTab)
|
||||
)
|
||||
);
|
||||
|
||||
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, EditorName, Layout, true, true, InAsset);
|
||||
RegenerateMenusAndToolbars();
|
||||
}
|
||||
|
||||
void FMorphPresetAssetEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
||||
{
|
||||
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
||||
|
||||
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FMorphPresetAssetEditor::SpawnTab_Viewport))
|
||||
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"));
|
||||
|
||||
InTabManager->RegisterTabSpawner(TimelineTabId, FOnSpawnTab::CreateSP(this, &FMorphPresetAssetEditor::SpawnTab_Timeline))
|
||||
.SetDisplayName(LOCTEXT("TimelineTab", "Timeline"));
|
||||
|
||||
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FMorphPresetAssetEditor::SpawnTab_Details))
|
||||
.SetDisplayName(LOCTEXT("DetailsTab", "Details"));
|
||||
}
|
||||
|
||||
void FMorphPresetAssetEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
|
||||
{
|
||||
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
|
||||
InTabManager->UnregisterTabSpawner(ViewportTabId);
|
||||
InTabManager->UnregisterTabSpawner(TimelineTabId);
|
||||
InTabManager->UnregisterTabSpawner(DetailsTabId);
|
||||
}
|
||||
|
||||
FName FMorphPresetAssetEditor::GetToolkitFName() const
|
||||
{
|
||||
return EditorName;
|
||||
}
|
||||
|
||||
FText FMorphPresetAssetEditor::GetBaseToolkitName() const
|
||||
{
|
||||
return LOCTEXT("ToolkitName", "Morph Target Preset Editor");
|
||||
}
|
||||
|
||||
FString FMorphPresetAssetEditor::GetWorldCentricTabPrefix() const
|
||||
{
|
||||
return TEXT("Morph Preset ");
|
||||
}
|
||||
|
||||
FLinearColor FMorphPresetAssetEditor::GetWorldCentricTabColorScale() const
|
||||
{
|
||||
return FLinearColor(0.18f, 0.47f, 0.83f);
|
||||
}
|
||||
|
||||
TSharedRef<SDockTab> FMorphPresetAssetEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
|
||||
{
|
||||
ViewportWidget = SNew(SMorphPresetViewportPanel)
|
||||
.Controller(EditorController)
|
||||
.AssetEditorToolkit(SharedThis(this));
|
||||
|
||||
return SNew(SDockTab)
|
||||
.Label(LOCTEXT("ViewportTabLabel", "Viewport"))
|
||||
[
|
||||
ViewportWidget.ToSharedRef()
|
||||
];
|
||||
}
|
||||
|
||||
TSharedRef<SDockTab> FMorphPresetAssetEditor::SpawnTab_Timeline(const FSpawnTabArgs& Args)
|
||||
{
|
||||
TimelineWidget = SNew(SMorphPresetTimelinePanel)
|
||||
.Controller(EditorController);
|
||||
|
||||
return SNew(SDockTab)
|
||||
.Label(LOCTEXT("TimelineTabLabel", "Timeline"))
|
||||
[
|
||||
TimelineWidget.ToSharedRef()
|
||||
];
|
||||
}
|
||||
|
||||
TSharedRef<SDockTab> FMorphPresetAssetEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
|
||||
{
|
||||
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
FDetailsViewArgs DetailsArgs;
|
||||
DetailsArgs.bAllowSearch = true;
|
||||
DetailsArgs.bHideSelectionTip = true;
|
||||
DetailsView = PropertyModule.CreateDetailView(DetailsArgs);
|
||||
DetailsView->OnFinishedChangingProperties().AddSP(this, &FMorphPresetAssetEditor::HandleDetailsChanged);
|
||||
DetailsView->SetObjects(TArray<UObject*>{ Preset.Get() });
|
||||
|
||||
return SNew(SDockTab)
|
||||
.Label(LOCTEXT("DetailsTabLabel", "Details"))
|
||||
[
|
||||
DetailsView.ToSharedRef()
|
||||
];
|
||||
}
|
||||
|
||||
void FMorphPresetAssetEditor::HandleDetailsChanged(const FPropertyChangedEvent&)
|
||||
{
|
||||
if (EditorController.IsValid())
|
||||
{
|
||||
EditorController->NotifyPresetEdited();
|
||||
}
|
||||
|
||||
if (TimelineWidget.IsValid())
|
||||
{
|
||||
TimelineWidget->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void FMorphPresetAssetEditor::HandlePresetChanged()
|
||||
{
|
||||
if (DetailsView.IsValid())
|
||||
{
|
||||
DetailsView->ForceRefresh();
|
||||
}
|
||||
|
||||
if (TimelineWidget.IsValid())
|
||||
{
|
||||
TimelineWidget->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetAssetFactory.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
|
||||
UMorphPresetAssetFactory::UMorphPresetAssetFactory()
|
||||
{
|
||||
SupportedClass = UMorphPresetDataAsset::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UMorphPresetAssetFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UMorphPresetDataAsset>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FString UMorphPresetAssetFactory::GetDefaultNewAssetName() const
|
||||
{
|
||||
return TEXT("MorphPreset");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetAssetTypeActions.h"
|
||||
#include "Morph/Editor/MorphPresetAssetEditor.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Morph/Editor/MorphPresetAssetFactory.h"
|
||||
#include "Toolkits/IToolkitHost.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MorphPreset"
|
||||
|
||||
FText FMorphPresetAssetTypeActions::GetName() const
|
||||
{
|
||||
return LOCTEXT("AssetTypeActions_MorphPreset", "Morph Preset");
|
||||
}
|
||||
|
||||
FColor FMorphPresetAssetTypeActions::GetTypeColor() const
|
||||
{
|
||||
return FColor(200, 150, 255); // Светло-фиолетовый
|
||||
}
|
||||
|
||||
UClass* FMorphPresetAssetTypeActions::GetSupportedClass() const
|
||||
{
|
||||
return UMorphPresetDataAsset::StaticClass();
|
||||
}
|
||||
|
||||
uint32 FMorphPresetAssetTypeActions::GetCategories()
|
||||
{
|
||||
return EAssetTypeCategories::Animation;
|
||||
}
|
||||
|
||||
FText FMorphPresetAssetTypeActions::GetAssetDescription(const FAssetData& AssetData) const
|
||||
{
|
||||
return LOCTEXT("MorphPresetDescription", "Пресет морф таргетов для персонажа. Улыбка, грусть, удивление и т.д.");
|
||||
}
|
||||
|
||||
void FMorphPresetAssetTypeActions::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor)
|
||||
{
|
||||
if (InObjects.Num() == 0) return;
|
||||
UMorphPresetDataAsset* Preset = Cast<UMorphPresetDataAsset>(InObjects[0]);
|
||||
if (!Preset) return;
|
||||
|
||||
TSharedRef<FMorphPresetAssetEditor> Editor = MakeShared<FMorphPresetAssetEditor>();
|
||||
Editor->InitMorphPresetAssetEditor(EToolkitMode::Standalone, EditWithinLevelEditor, Preset);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetDetailsCustomization.h"
|
||||
|
||||
#include "DetailCategoryBuilder.h"
|
||||
#include "DetailLayoutBuilder.h"
|
||||
#include "DetailWidgetRow.h"
|
||||
#include "IPropertyUtilities.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Policies/PrettyJsonPrintPolicy.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Widgets/Input/SMultiLineEditableTextBox.h"
|
||||
#include "Widgets/Layout/SBox.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MorphPresetDetailsCustomization"
|
||||
|
||||
namespace
|
||||
{
|
||||
FString SerializePresetToJson(const UMorphPresetDataAsset* Preset)
|
||||
{
|
||||
if (!Preset)
|
||||
{
|
||||
return TEXT("{}");
|
||||
}
|
||||
|
||||
TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>();
|
||||
if (!FJsonObjectConverter::UStructToJsonObject(Preset->GetClass(), Preset, JsonObject, 0, 0))
|
||||
{
|
||||
return TEXT("{}");
|
||||
}
|
||||
|
||||
JsonObject->RemoveField(TEXT("PreviewMesh"));
|
||||
|
||||
FString JsonString;
|
||||
const TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer =
|
||||
TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&JsonString);
|
||||
FJsonSerializer::Serialize(JsonObject, Writer);
|
||||
return JsonString;
|
||||
}
|
||||
|
||||
bool DeserializePresetFromJson(const FString& JsonString, UMorphPresetDataAsset& OutPreset)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonObject->RemoveField(TEXT("PreviewMesh"));
|
||||
return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), &OutPreset, 0, 0);
|
||||
}
|
||||
|
||||
void CopyPresetValues(const UMorphPresetDataAsset& SourcePreset, UMorphPresetDataAsset& TargetPreset)
|
||||
{
|
||||
TargetPreset.DisplayName = SourcePreset.DisplayName;
|
||||
TargetPreset.Description = SourcePreset.Description;
|
||||
TargetPreset.MorphTargets = SourcePreset.MorphTargets;
|
||||
TargetPreset.Duration = SourcePreset.Duration;
|
||||
TargetPreset.bLoop = SourcePreset.bLoop;
|
||||
TargetPreset.LoopDelay = SourcePreset.LoopDelay;
|
||||
TargetPreset.BlendSpeedOverride = SourcePreset.BlendSpeedOverride;
|
||||
TargetPreset.AnimationMontage = SourcePreset.AnimationMontage;
|
||||
TargetPreset.AnimationPlayRate = SourcePreset.AnimationPlayRate;
|
||||
TargetPreset.AnimationStartSection = SourcePreset.AnimationStartSection;
|
||||
TargetPreset.bRestartAnimationOnReplay = SourcePreset.bRestartAnimationOnReplay;
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<IDetailCustomization> FMorphPresetDetailsCustomization::MakeInstance()
|
||||
{
|
||||
return MakeShared<FMorphPresetDetailsCustomization>();
|
||||
}
|
||||
|
||||
void FMorphPresetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
|
||||
{
|
||||
const TArray<TWeakObjectPtr<UObject>>& CustomizedObjects = DetailBuilder.GetSelectedObjects();
|
||||
for (const TWeakObjectPtr<UObject>& Object : CustomizedObjects)
|
||||
{
|
||||
if (UMorphPresetDataAsset* MorphPreset = Cast<UMorphPresetDataAsset>(Object.Get()))
|
||||
{
|
||||
Preset = MorphPreset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PropertyUtilities = DetailBuilder.GetPropertyUtilities();
|
||||
RefreshJsonFromPreset();
|
||||
|
||||
IDetailCategoryBuilder& JsonCategory = DetailBuilder.EditCategory("JSON", LOCTEXT("JsonCategory", "JSON"), ECategoryPriority::Uncommon);
|
||||
JsonCategory.AddCustomRow(LOCTEXT("PresetJsonFilter", "Preset JSON"))
|
||||
.WholeRowContent()
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 0.0f, 0.0f, 6.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("JsonHelpText", "JSON representation of the current preset. Pasted valid JSON is applied immediately."))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
[
|
||||
SNew(SBox)
|
||||
.MinDesiredHeight(260.0f)
|
||||
.MaxDesiredHeight(420.0f)
|
||||
[
|
||||
SAssignNew(JsonTextBox, SMultiLineEditableTextBox)
|
||||
.Text(FText::FromString(CachedJsonText))
|
||||
.OnTextChanged(this, &FMorphPresetDetailsCustomization::HandleJsonTextChanged)
|
||||
.AutoWrapText(false)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 6.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("ApplyJsonButton", "Apply JSON"))
|
||||
.OnClicked(this, &FMorphPresetDetailsCustomization::OnApplyJsonClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(0.0f, 0.0f, 12.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("ResetJsonButton", "Reset From Preset"))
|
||||
.OnClicked(this, &FMorphPresetDetailsCustomization::OnResetJsonClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &FMorphPresetDetailsCustomization::GetStatusText)
|
||||
.ColorAndOpacity(this, &FMorphPresetDetailsCustomization::GetStatusColor)
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void FMorphPresetDetailsCustomization::RefreshJsonFromPreset()
|
||||
{
|
||||
CachedJsonText = SerializePresetToJson(Preset.Get());
|
||||
bIsUpdatingJsonText = true;
|
||||
if (JsonTextBox.IsValid())
|
||||
{
|
||||
JsonTextBox->SetText(FText::FromString(CachedJsonText));
|
||||
}
|
||||
bIsUpdatingJsonText = false;
|
||||
}
|
||||
|
||||
void FMorphPresetDetailsCustomization::ApplyJsonToPreset(const FString& JsonString)
|
||||
{
|
||||
UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
if (!PresetAsset)
|
||||
{
|
||||
bLastApplySucceeded = false;
|
||||
StatusText = LOCTEXT("NoPresetStatus", "Preset is no longer available.");
|
||||
return;
|
||||
}
|
||||
|
||||
UMorphPresetDataAsset* TempPreset = NewObject<UMorphPresetDataAsset>(GetTransientPackage(), UMorphPresetDataAsset::StaticClass(), NAME_None, RF_Transient);
|
||||
if (!DeserializePresetFromJson(JsonString, *TempPreset))
|
||||
{
|
||||
bLastApplySucceeded = false;
|
||||
StatusText = LOCTEXT("InvalidJsonStatus", "JSON is invalid or does not match the preset structure.");
|
||||
return;
|
||||
}
|
||||
|
||||
PresetAsset->Modify();
|
||||
CopyPresetValues(*TempPreset, *PresetAsset);
|
||||
PresetAsset->MarkPackageDirty();
|
||||
PresetAsset->PostEditChange();
|
||||
|
||||
bLastApplySucceeded = true;
|
||||
StatusText = LOCTEXT("AppliedJsonStatus", "JSON applied to the preset.");
|
||||
RefreshJsonFromPreset();
|
||||
|
||||
if (PropertyUtilities.IsValid())
|
||||
{
|
||||
const FPropertyChangedEvent ChangeEvent(nullptr, EPropertyChangeType::ValueSet);
|
||||
PropertyUtilities->NotifyFinishedChangingProperties(ChangeEvent);
|
||||
PropertyUtilities->ForceRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
FText FMorphPresetDetailsCustomization::GetStatusText() const
|
||||
{
|
||||
return StatusText;
|
||||
}
|
||||
|
||||
FSlateColor FMorphPresetDetailsCustomization::GetStatusColor() const
|
||||
{
|
||||
return bLastApplySucceeded ? FSlateColor(FLinearColor(0.35f, 0.8f, 0.45f, 1.0f)) : FSlateColor(FLinearColor(0.95f, 0.4f, 0.35f, 1.0f));
|
||||
}
|
||||
|
||||
FReply FMorphPresetDetailsCustomization::OnApplyJsonClicked()
|
||||
{
|
||||
if (JsonTextBox.IsValid())
|
||||
{
|
||||
ApplyJsonToPreset(JsonTextBox->GetText().ToString());
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply FMorphPresetDetailsCustomization::OnResetJsonClicked()
|
||||
{
|
||||
RefreshJsonFromPreset();
|
||||
bLastApplySucceeded = true;
|
||||
StatusText = LOCTEXT("ResetJsonStatus", "JSON reset to the current preset state.");
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
void FMorphPresetDetailsCustomization::HandleJsonTextChanged(const FText& NewText)
|
||||
{
|
||||
if (bIsUpdatingJsonText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FString NewJsonText = NewText.ToString();
|
||||
if (NewJsonText == CachedJsonText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CachedJsonText = NewJsonText;
|
||||
ApplyJsonToPreset(NewJsonText);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,733 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetEditorController.h"
|
||||
|
||||
#include "Animation/AnimData/IAnimationDataController.h"
|
||||
#include "AnimPreviewInstance.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Animation/AnimSequence.h"
|
||||
#include "Animation/AnimSequenceBase.h"
|
||||
#include "Animation/AnimationAsset.h"
|
||||
#include "Animation/DebugSkelMeshComponent.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Editor.h"
|
||||
#include "Framework/Commands/UICommandList.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "IPersonaPreviewScene.h"
|
||||
#include "IPersonaToolkit.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
#include "Morph/LipSyncComponent.h"
|
||||
#include "Morph/LipSyncMappingDataAsset.h"
|
||||
#include "Morph/MorphControllerComponent.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "PersonaModule.h"
|
||||
#include "ReferenceSkeleton.h"
|
||||
#include "Sound/SoundBase.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogMorphPresetEditorPreview, Log, All);
|
||||
|
||||
namespace
|
||||
{
|
||||
void SetAnimSequencePlayLength(UAnimSequence& AnimSequence, float PlayLength, bool bShouldTransact)
|
||||
{
|
||||
IAnimationDataController& Controller = AnimSequence.GetController();
|
||||
Controller.SetNumberOfFrames(FMath::Max(Controller.ConvertSecondsToFrameNumber(PlayLength).Value, 1), bShouldTransact);
|
||||
}
|
||||
}
|
||||
|
||||
FMorphPresetEditorController::FMorphPresetEditorController(UMorphPresetDataAsset* InPreset)
|
||||
: Preset(InPreset)
|
||||
{
|
||||
CommandList = MakeShared<FUICommandList>();
|
||||
DefaultLipSyncPreviewMapping = TStrongObjectPtr<ULipSyncMappingDataAsset>(
|
||||
NewObject<ULipSyncMappingDataAsset>(GetTransientPackage(), NAME_None, RF_Transient));
|
||||
}
|
||||
|
||||
FMorphPresetEditorController::~FMorphPresetEditorController()
|
||||
{
|
||||
StopLipSyncPreviewAudio();
|
||||
DestroyLipSyncPreviewComponents();
|
||||
|
||||
if (PersonaToolkit.IsValid())
|
||||
{
|
||||
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = PersonaToolkit->GetPreviewScene())
|
||||
{
|
||||
PreviewScene->ClearSelectedBone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::Initialize()
|
||||
{
|
||||
RebuildPreviewScene();
|
||||
SetCurrentTime(0.0f);
|
||||
}
|
||||
|
||||
UMorphPresetDataAsset* FMorphPresetEditorController::GetPreset() const
|
||||
{
|
||||
return Preset.Get();
|
||||
}
|
||||
|
||||
USkeletalMesh* FMorphPresetEditorController::GetPreviewMesh() const
|
||||
{
|
||||
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
|
||||
{
|
||||
return PresetAsset->PreviewMesh.LoadSynchronous();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::SetPreviewMesh(USkeletalMesh* InPreviewMesh)
|
||||
{
|
||||
UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
if (!PresetAsset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PresetAsset->PreviewMesh.Get() == InPreviewMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PresetAsset->Modify();
|
||||
PresetAsset->PreviewMesh = InPreviewMesh;
|
||||
PresetAsset->MarkPackageDirty();
|
||||
|
||||
RebuildPreviewScene();
|
||||
return true;
|
||||
}
|
||||
|
||||
TSharedPtr<IPersonaPreviewScene> FMorphPresetEditorController::GetPreviewScene() const
|
||||
{
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene;
|
||||
if (PersonaToolkit.IsValid())
|
||||
{
|
||||
PreviewScene = PersonaToolkit->GetPreviewScene();
|
||||
}
|
||||
|
||||
return PreviewScene;
|
||||
}
|
||||
|
||||
float FMorphPresetEditorController::GetTimelineLength() const
|
||||
{
|
||||
if (const UMorphPresetDataAsset* PresetAsset = Preset.Get())
|
||||
{
|
||||
float TimelineLength = PresetAsset->GetTimelineLength();
|
||||
if (PresetAsset->AnimationMontage)
|
||||
{
|
||||
const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f);
|
||||
const UAnimSequenceBase* PreviewSequence = PresetAsset->AnimationMontage->GetFirstAnimReference();
|
||||
const float AnimationLength = PreviewSequence ? PreviewSequence->GetPlayLength() : PresetAsset->AnimationMontage->GetPlayLength();
|
||||
TimelineLength = FMath::Max(TimelineLength, AnimationLength / PlayRate);
|
||||
}
|
||||
return FMath::Max(TimelineLength, 0.1f);
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
float FMorphPresetEditorController::GetCurrentTime() const
|
||||
{
|
||||
return CurrentTime;
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::SetCurrentTime(float InTime)
|
||||
{
|
||||
CurrentTime = FMath::Clamp(InTime, 0.0f, GetTimelineLength());
|
||||
|
||||
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene())
|
||||
{
|
||||
if (UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent())
|
||||
{
|
||||
if (PreviewMeshComponent->PreviewInstance)
|
||||
{
|
||||
PreviewMeshComponent->PreviewInstance->SetPlaying(false);
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false);
|
||||
RefreshPreviewAnimationPose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyPresetToPreview();
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::TryGetHeadLocation(FVector& OutHeadLocation) const
|
||||
{
|
||||
USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
if (!PreviewMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FReferenceSkeleton& RefSkeleton = PreviewMesh->GetRefSkeleton();
|
||||
int32 HeadBoneIndex = RefSkeleton.FindBoneIndex(TEXT("head"));
|
||||
if (HeadBoneIndex == INDEX_NONE)
|
||||
{
|
||||
HeadBoneIndex = RefSkeleton.FindBoneIndex(TEXT("Head"));
|
||||
}
|
||||
|
||||
if (HeadBoneIndex == INDEX_NONE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const TArray<FTransform>& RefPose = RefSkeleton.GetRefBonePose();
|
||||
FTransform HeadTransform = FTransform::Identity;
|
||||
int32 BoneIndex = HeadBoneIndex;
|
||||
|
||||
while (BoneIndex != INDEX_NONE)
|
||||
{
|
||||
HeadTransform = RefPose[BoneIndex] * HeadTransform;
|
||||
BoneIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
||||
}
|
||||
|
||||
OutHeadLocation = HeadTransform.GetLocation();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::ResolvePreviewBoneName(FName InBoneName, FName& OutBoneName) const
|
||||
{
|
||||
USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
if (!PreviewMesh || InBoneName.IsNone())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FReferenceSkeleton& RefSkeleton = PreviewMesh->GetRefSkeleton();
|
||||
if (RefSkeleton.FindBoneIndex(InBoneName) != INDEX_NONE)
|
||||
{
|
||||
OutBoneName = InBoneName;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FString RequestedBoneName = InBoneName.ToString();
|
||||
for (int32 BoneIndex = 0; BoneIndex < RefSkeleton.GetNum(); ++BoneIndex)
|
||||
{
|
||||
const FName CandidateBoneName = RefSkeleton.GetBoneName(BoneIndex);
|
||||
if (CandidateBoneName.ToString().Equals(RequestedBoneName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
OutBoneName = CandidateBoneName;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::NotifyPresetEdited(bool bBroadcastPresetChanged)
|
||||
{
|
||||
if (UMorphPresetDataAsset* PresetAsset = Preset.Get())
|
||||
{
|
||||
PresetAsset->MarkPackageDirty();
|
||||
}
|
||||
|
||||
UAnimationAsset* DesiredPreviewAsset = ResolvePreviewAnimationAsset();
|
||||
UAnimationAsset* CurrentPreviewAsset = nullptr;
|
||||
if (TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene())
|
||||
{
|
||||
CurrentPreviewAsset = PreviewScene->GetPreviewAnimationAsset();
|
||||
}
|
||||
|
||||
if (DesiredPreviewAsset != CurrentPreviewAsset ||
|
||||
(DesiredPreviewAsset && PreviewAnimation.IsValid()) ||
|
||||
(!DesiredPreviewAsset && !PreviewAnimation.IsValid()))
|
||||
{
|
||||
RebuildPreviewAnimation();
|
||||
}
|
||||
else if (PreviewAnimation.IsValid())
|
||||
{
|
||||
SetAnimSequencePlayLength(*PreviewAnimation, GetTimelineLength(), false);
|
||||
}
|
||||
|
||||
CurrentTime = FMath::Clamp(CurrentTime, 0.0f, GetTimelineLength());
|
||||
|
||||
// Re-apply morph weights immediately so viewport reflects drag/resize edits without stalling.
|
||||
ApplyPresetToPreview();
|
||||
if (bBroadcastPresetChanged)
|
||||
{
|
||||
PresetChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::SetPlaying(bool bInPlaying)
|
||||
{
|
||||
if (bIsPlaying == bInPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bIsPlaying = bInPlaying;
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::TogglePlaying()
|
||||
{
|
||||
SetPlaying(!bIsPlaying);
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::Tick(float DeltaTime)
|
||||
{
|
||||
if (bIsPlaying)
|
||||
{
|
||||
float NewTime = CurrentTime + DeltaTime;
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
const float Length = GetTimelineLength();
|
||||
|
||||
if (PresetAsset && PresetAsset->bLoop && Length > KINDA_SMALL_NUMBER)
|
||||
{
|
||||
NewTime = FMath::Fmod(NewTime, Length);
|
||||
if (NewTime < 0.0f)
|
||||
{
|
||||
NewTime += Length;
|
||||
}
|
||||
}
|
||||
else if (NewTime >= Length)
|
||||
{
|
||||
NewTime = Length;
|
||||
bIsPlaying = false;
|
||||
}
|
||||
|
||||
SetCurrentTime(NewTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyPresetToPreview();
|
||||
}
|
||||
|
||||
TickLipSyncPreviewComponents(DeltaTime);
|
||||
if (bLipSyncPreviewAudioActive && !IsLipSyncPreviewAudioPlaying())
|
||||
{
|
||||
bLipSyncPreviewAudioActive = false;
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
USoundBase* FMorphPresetEditorController::GetLipSyncPreviewSound() const
|
||||
{
|
||||
return LipSyncPreviewSound.Get();
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::SetLipSyncPreviewSound(USoundBase* InSound)
|
||||
{
|
||||
if (LipSyncPreviewSound.Get() == InSound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StopLipSyncPreviewAudio();
|
||||
LipSyncPreviewSound = InSound;
|
||||
PlaybackStateChanged.Broadcast();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::PlayLipSyncPreviewAudio(float StartTime)
|
||||
{
|
||||
USoundBase* Sound = LipSyncPreviewSound.Get();
|
||||
if (!Sound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RebuildLipSyncPreviewComponents();
|
||||
ULipSyncComponent* PreviewLipSync = LipSyncPreviewComponent.Get();
|
||||
if (!PreviewLipSync || !GEditor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UAudioComponent* PreviewAudioComponent = GEditor->ResetPreviewAudioComponent(Sound);
|
||||
if (!PreviewAudioComponent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PreviewLipSync->Mapping = ResolveLipSyncPreviewMapping();
|
||||
PreviewLipSync->AudioDrivenSound = Sound;
|
||||
PreviewLipSync->AudioDrivenMode = LipSyncPreviewTranscript.IsEmpty()
|
||||
? ELipSyncAudioDrivenMode::EnvelopeMouthOpen
|
||||
: ELipSyncAudioDrivenMode::TranscriptVisemes;
|
||||
PreviewLipSync->AudioDrivenTranscript = LipSyncPreviewTranscript;
|
||||
PreviewLipSync->VisemeAttackSpeed = FMath::Lerp(30.0f, 10.0f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->VisemeReleaseSpeed = FMath::Lerp(16.0f, 4.0f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->SilenceHoldTime = FMath::Lerp(0.02f, 0.09f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->AudioDrivenVisemeCrossfade = FMath::Lerp(0.02f, 0.22f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->bUseFallbackPulseWithoutEnvelope = false;
|
||||
|
||||
bLipSyncPreviewAudioActive = PreviewLipSync->PlayAudioDrivenLipSyncWithComponent(PreviewAudioComponent, Sound, StartTime);
|
||||
PlaybackStateChanged.Broadcast();
|
||||
return bLipSyncPreviewAudioActive;
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::StopLipSyncPreviewAudio()
|
||||
{
|
||||
bLipSyncPreviewAudioActive = false;
|
||||
|
||||
if (ULipSyncComponent* PreviewLipSync = LipSyncPreviewComponent.Get())
|
||||
{
|
||||
PreviewLipSync->StopAudioDrivenLipSync();
|
||||
}
|
||||
|
||||
ApplyPresetToPreview();
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::IsLipSyncPreviewAudioPlaying() const
|
||||
{
|
||||
return bLipSyncPreviewAudioActive && LipSyncPreviewComponent.IsValid() && LipSyncPreviewComponent->IsAudioDrivenLipSyncPlaying();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::SetLipSyncPreviewSmoothness(float InSmoothness)
|
||||
{
|
||||
LipSyncPreviewSmoothness = FMath::Clamp(InSmoothness, 0.0f, 1.0f);
|
||||
if (ULipSyncComponent* PreviewLipSync = LipSyncPreviewComponent.Get())
|
||||
{
|
||||
PreviewLipSync->VisemeAttackSpeed = FMath::Lerp(30.0f, 10.0f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->VisemeReleaseSpeed = FMath::Lerp(16.0f, 4.0f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->SilenceHoldTime = FMath::Lerp(0.02f, 0.09f, LipSyncPreviewSmoothness);
|
||||
PreviewLipSync->AudioDrivenVisemeCrossfade = FMath::Lerp(0.02f, 0.22f, LipSyncPreviewSmoothness);
|
||||
}
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::SetLipSyncPreviewTranscript(const FText& InTranscript)
|
||||
{
|
||||
LipSyncPreviewTranscript = InTranscript;
|
||||
if (ULipSyncComponent* PreviewLipSync = LipSyncPreviewComponent.Get())
|
||||
{
|
||||
PreviewLipSync->AudioDrivenTranscript = LipSyncPreviewTranscript;
|
||||
PreviewLipSync->AudioDrivenMode = LipSyncPreviewTranscript.IsEmpty()
|
||||
? ELipSyncAudioDrivenMode::EnvelopeMouthOpen
|
||||
: ELipSyncAudioDrivenMode::TranscriptVisemes;
|
||||
}
|
||||
PlaybackStateChanged.Broadcast();
|
||||
}
|
||||
|
||||
ULipSyncMappingDataAsset* FMorphPresetEditorController::GetLipSyncPreviewMapping() const
|
||||
{
|
||||
return LipSyncPreviewMapping.Get();
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::SetLipSyncPreviewMapping(ULipSyncMappingDataAsset* InMapping)
|
||||
{
|
||||
if (LipSyncPreviewMapping.Get() == InMapping)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LipSyncPreviewMapping = InMapping;
|
||||
if (ULipSyncComponent* PreviewLipSync = LipSyncPreviewComponent.Get())
|
||||
{
|
||||
PreviewLipSync->Mapping = ResolveLipSyncPreviewMapping();
|
||||
}
|
||||
ApplyPresetToPreview();
|
||||
PlaybackStateChanged.Broadcast();
|
||||
return true;
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::RebuildPreviewScene()
|
||||
{
|
||||
StopLipSyncPreviewAudio();
|
||||
DestroyLipSyncPreviewComponents();
|
||||
PersonaToolkit.Reset();
|
||||
PreviewAnimation.Reset();
|
||||
|
||||
USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
if (PreviewMesh == nullptr)
|
||||
{
|
||||
PreviewSceneChanged.Broadcast();
|
||||
return;
|
||||
}
|
||||
|
||||
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
|
||||
FPersonaToolkitArgs ToolkitArgs;
|
||||
PersonaToolkit = PersonaModule.CreatePersonaToolkit(PreviewMesh, ToolkitArgs);
|
||||
|
||||
RebuildPreviewAnimation();
|
||||
RebuildLipSyncPreviewComponents();
|
||||
SetCurrentTime(CurrentTime);
|
||||
PreviewSceneChanged.Broadcast();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::RebuildPreviewAnimation()
|
||||
{
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
|
||||
if (!PreviewScene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UAnimSequence* AnimSequence = NewObject<UAnimSequence>(GetTransientPackage(), NAME_None, RF_Transient);
|
||||
PreviewAnimation.Reset();
|
||||
PreviewStatusText = FText::GetEmpty();
|
||||
|
||||
UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset();
|
||||
if (PreviewAsset && !IsPreviewAnimationCompatible(PreviewAsset))
|
||||
{
|
||||
const USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
const FString PreviewMeshSkeletonName = PreviewMesh && PreviewMesh->GetSkeleton()
|
||||
? PreviewMesh->GetSkeleton()->GetPathName()
|
||||
: TEXT("None");
|
||||
const FString AnimationSkeletonName = PreviewAsset->GetSkeleton()
|
||||
? PreviewAsset->GetSkeleton()->GetPathName()
|
||||
: TEXT("None");
|
||||
|
||||
PreviewStatusText = FText::FromString(FString::Printf(
|
||||
TEXT("Animation preview is disabled: montage sequence '%s' uses skeleton '%s', but Preview Mesh uses '%s'. Retarget the animation to the Preview Mesh skeleton."),
|
||||
*GetNameSafe(PreviewAsset),
|
||||
*AnimationSkeletonName,
|
||||
*PreviewMeshSkeletonName));
|
||||
UE_LOG(LogMorphPresetEditorPreview, Warning, TEXT("%s"), *PreviewStatusText.ToString());
|
||||
PreviewAsset = nullptr;
|
||||
}
|
||||
|
||||
if (!PreviewAsset)
|
||||
{
|
||||
PreviewAnimation = TStrongObjectPtr<UAnimSequence>(AnimSequence);
|
||||
PreviewAsset = AnimSequence;
|
||||
|
||||
SetAnimSequencePlayLength(*AnimSequence, GetTimelineLength(), false);
|
||||
if (USkeletalMesh* PreviewMesh = GetPreviewMesh())
|
||||
{
|
||||
if (USkeleton* Skeleton = PreviewMesh->GetSkeleton())
|
||||
{
|
||||
AnimSequence->SetSkeleton(Skeleton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PreviewScene->SetPreviewAnimationAsset(PreviewAsset, true);
|
||||
if (UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent())
|
||||
{
|
||||
PreviewMeshComponent->SetAnimationMode(EAnimationMode::AnimationSingleNode);
|
||||
if (PreviewMeshComponent->PreviewInstance)
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
const float PlayRate = PresetAsset ? FMath::Max(PresetAsset->AnimationPlayRate, 0.01f) : 1.0f;
|
||||
PreviewMeshComponent->PreviewInstance->SetAnimationAsset(PreviewAsset, false, PlayRate);
|
||||
PreviewMeshComponent->PreviewInstance->EnableControllers(true);
|
||||
PreviewMeshComponent->PreviewInstance->SetSkeletalControlAlpha(1.0f);
|
||||
PreviewMeshComponent->PreviewInstance->SetPlaying(false);
|
||||
PreviewMeshComponent->PreviewInstance->SetLooping(false);
|
||||
|
||||
PreviewMeshComponent->PreviewInstance->SetPosition(GetPreviewAnimationPosition(), false);
|
||||
RefreshPreviewAnimationPose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::RefreshPreviewAnimationPose()
|
||||
{
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
|
||||
if (!PreviewScene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent();
|
||||
if (!PreviewMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewMeshComponent->TickAnimation(0.0f, false);
|
||||
PreviewMeshComponent->RefreshBoneTransforms();
|
||||
PreviewMeshComponent->MarkRenderStateDirty();
|
||||
PreviewScene->InvalidateViews();
|
||||
}
|
||||
|
||||
UAnimationAsset* FMorphPresetEditorController::ResolvePreviewAnimationAsset() const
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
if (PresetAsset && PresetAsset->AnimationMontage)
|
||||
{
|
||||
if (UAnimSequenceBase* FirstAnimReference = PresetAsset->AnimationMontage->GetFirstAnimReference())
|
||||
{
|
||||
return FirstAnimReference;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
float FMorphPresetEditorController::GetPreviewAnimationPosition() const
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
UAnimationAsset* PreviewAsset = ResolvePreviewAnimationAsset();
|
||||
if (!PresetAsset || !PreviewAsset)
|
||||
{
|
||||
return CurrentTime;
|
||||
}
|
||||
|
||||
const float PlayRate = FMath::Max(PresetAsset->AnimationPlayRate, 0.01f);
|
||||
float PreviewPosition = CurrentTime * PlayRate;
|
||||
if (const UAnimSequenceBase* PreviewSequence = Cast<UAnimSequenceBase>(PreviewAsset))
|
||||
{
|
||||
return FMath::Clamp(PreviewPosition, 0.0f, PreviewSequence->GetPlayLength());
|
||||
}
|
||||
|
||||
return PreviewPosition;
|
||||
}
|
||||
|
||||
bool FMorphPresetEditorController::IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const
|
||||
{
|
||||
if (!PreviewAsset)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const USkeletalMesh* PreviewMesh = GetPreviewMesh();
|
||||
const USkeleton* PreviewSkeleton = PreviewMesh ? PreviewMesh->GetSkeleton() : nullptr;
|
||||
const USkeleton* AnimationSkeleton = PreviewAsset->GetSkeleton();
|
||||
return PreviewSkeleton && AnimationSkeleton && PreviewSkeleton->IsCompatibleForEditor(AnimationSkeleton);
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::ApplyPresetToPreview()
|
||||
{
|
||||
const UMorphPresetDataAsset* PresetAsset = Preset.Get();
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
|
||||
if (!PresetAsset || !PreviewScene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent();
|
||||
if (!PreviewMeshComponent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewMeshComponent->ClearMorphTargets();
|
||||
if (PreviewMeshComponent->PreviewInstance)
|
||||
{
|
||||
PreviewMeshComponent->PreviewInstance->EnableControllers(true);
|
||||
PreviewMeshComponent->PreviewInstance->SetSkeletalControlAlpha(1.0f);
|
||||
}
|
||||
|
||||
TMap<FName, float> FinalValues;
|
||||
MorphPresetRuntime::AccumulatePresetMorphValuesAtTime(*PresetAsset, CurrentTime, 1.0f, FinalValues);
|
||||
|
||||
for (const TPair<FName, float>& Pair : FinalValues)
|
||||
{
|
||||
if (Pair.Value <= 0.001f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PreviewMeshComponent->SetMorphTarget(Pair.Key, Pair.Value, false);
|
||||
}
|
||||
|
||||
RefreshPreviewAnimationPose();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::RebuildLipSyncPreviewComponents()
|
||||
{
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene = GetPreviewScene();
|
||||
if (!PreviewScene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent();
|
||||
UWorld* PreviewWorld = PreviewScene->GetWorld();
|
||||
if (!PreviewMeshComponent || !PreviewWorld)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LipSyncPreviewActor.IsValid() && LipSyncPreviewMorphController.IsValid() && LipSyncPreviewComponent.IsValid())
|
||||
{
|
||||
LipSyncPreviewMorphController->TargetMeshComponent = PreviewMeshComponent;
|
||||
LipSyncPreviewComponent->Mapping = ResolveLipSyncPreviewMapping();
|
||||
return;
|
||||
}
|
||||
|
||||
AActor* PreviewActor = PreviewWorld->SpawnActor<AActor>();
|
||||
if (!PreviewActor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewActor->SetFlags(RF_Transient);
|
||||
|
||||
UMorphControllerComponent* MorphController = NewObject<UMorphControllerComponent>(PreviewActor, TEXT("MorphEditorPreviewMorphController"), RF_Transient);
|
||||
ULipSyncComponent* LipSyncComponent = NewObject<ULipSyncComponent>(PreviewActor, TEXT("MorphEditorPreviewLipSyncComponent"), RF_Transient);
|
||||
if (!MorphController || !LipSyncComponent)
|
||||
{
|
||||
PreviewActor->Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
PreviewActor->AddInstanceComponent(MorphController);
|
||||
PreviewActor->AddInstanceComponent(LipSyncComponent);
|
||||
|
||||
MorphController->TargetMeshComponent = PreviewMeshComponent;
|
||||
MorphController->bAutoUpdate = false;
|
||||
MorphController->bLogWarnings = false;
|
||||
MorphController->RegisterComponent();
|
||||
|
||||
LipSyncComponent->MorphController = MorphController;
|
||||
LipSyncComponent->Mapping = ResolveLipSyncPreviewMapping();
|
||||
LipSyncComponent->AudioDrivenMode = LipSyncPreviewTranscript.IsEmpty()
|
||||
? ELipSyncAudioDrivenMode::EnvelopeMouthOpen
|
||||
: ELipSyncAudioDrivenMode::TranscriptVisemes;
|
||||
LipSyncComponent->AudioDrivenTranscript = LipSyncPreviewTranscript;
|
||||
LipSyncComponent->bAutoResolveMorphController = false;
|
||||
LipSyncComponent->VisemeAttackSpeed = FMath::Lerp(30.0f, 10.0f, LipSyncPreviewSmoothness);
|
||||
LipSyncComponent->VisemeReleaseSpeed = FMath::Lerp(16.0f, 4.0f, LipSyncPreviewSmoothness);
|
||||
LipSyncComponent->SilenceHoldTime = FMath::Lerp(0.02f, 0.09f, LipSyncPreviewSmoothness);
|
||||
LipSyncComponent->AudioDrivenVisemeCrossfade = FMath::Lerp(0.02f, 0.22f, LipSyncPreviewSmoothness);
|
||||
LipSyncComponent->bUseFallbackPulseWithoutEnvelope = false;
|
||||
LipSyncComponent->RegisterComponent();
|
||||
|
||||
LipSyncPreviewActor = PreviewActor;
|
||||
LipSyncPreviewMorphController = MorphController;
|
||||
LipSyncPreviewComponent = LipSyncComponent;
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::DestroyLipSyncPreviewComponents()
|
||||
{
|
||||
if (ULipSyncComponent* LipSyncComponent = LipSyncPreviewComponent.Get())
|
||||
{
|
||||
LipSyncComponent->StopAudioDrivenLipSync();
|
||||
LipSyncComponent->DestroyComponent();
|
||||
}
|
||||
|
||||
if (UMorphControllerComponent* MorphController = LipSyncPreviewMorphController.Get())
|
||||
{
|
||||
MorphController->DestroyComponent();
|
||||
}
|
||||
|
||||
if (AActor* PreviewActor = LipSyncPreviewActor.Get())
|
||||
{
|
||||
PreviewActor->Destroy();
|
||||
}
|
||||
|
||||
LipSyncPreviewComponent.Reset();
|
||||
LipSyncPreviewMorphController.Reset();
|
||||
LipSyncPreviewActor.Reset();
|
||||
}
|
||||
|
||||
void FMorphPresetEditorController::TickLipSyncPreviewComponents(float DeltaTime)
|
||||
{
|
||||
if (!LipSyncPreviewComponent.IsValid() || !LipSyncPreviewMorphController.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LipSyncPreviewComponent->TickComponent(DeltaTime, LEVELTICK_All, nullptr);
|
||||
LipSyncPreviewMorphController->UpdateMorphTargetsNow(DeltaTime);
|
||||
}
|
||||
|
||||
ULipSyncMappingDataAsset* FMorphPresetEditorController::ResolveLipSyncPreviewMapping() const
|
||||
{
|
||||
if (ULipSyncMappingDataAsset* SelectedMapping = LipSyncPreviewMapping.Get())
|
||||
{
|
||||
return SelectedMapping;
|
||||
}
|
||||
|
||||
return DefaultLipSyncPreviewMapping.Get();
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetEditorWindow.h"
|
||||
#include "Morph/MorphPresetDataAsset.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/SWindow.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Input/SMultiLineEditableTextBox.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "PropertyCustomizationHelpers.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Serialization/JsonReader.h"
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "Curves/RichCurve.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MorphPresetEditor"
|
||||
|
||||
namespace
|
||||
{
|
||||
FString CurveInterpToString(ERichCurveInterpMode InterpMode)
|
||||
{
|
||||
switch (InterpMode)
|
||||
{
|
||||
case RCIM_Constant:
|
||||
return TEXT("constant");
|
||||
case RCIM_Cubic:
|
||||
return TEXT("ease");
|
||||
case RCIM_Linear:
|
||||
default:
|
||||
return TEXT("linear");
|
||||
}
|
||||
}
|
||||
|
||||
FString CurveInterpToUnrealString(ERichCurveInterpMode InterpMode)
|
||||
{
|
||||
switch (InterpMode)
|
||||
{
|
||||
case RCIM_Constant:
|
||||
return TEXT("RCIM_Constant");
|
||||
case RCIM_Cubic:
|
||||
return TEXT("RCIM_Cubic");
|
||||
case RCIM_Linear:
|
||||
default:
|
||||
return TEXT("RCIM_Linear");
|
||||
}
|
||||
}
|
||||
|
||||
ERichCurveInterpMode ParseCurveInterp(const TSharedPtr<FJsonValue>& Value)
|
||||
{
|
||||
FString InterpName;
|
||||
if (!Value.IsValid() || !Value->TryGetString(InterpName))
|
||||
{
|
||||
return RCIM_Linear;
|
||||
}
|
||||
|
||||
InterpName = InterpName.ToLower();
|
||||
if (InterpName == TEXT("constant") || InterpName == TEXT("step"))
|
||||
{
|
||||
return RCIM_Constant;
|
||||
}
|
||||
if (InterpName == TEXT("ease") || InterpName == TEXT("cubic") || InterpName == TEXT("auto") || InterpName == TEXT("rcim_cubic"))
|
||||
{
|
||||
return RCIM_Cubic;
|
||||
}
|
||||
return RCIM_Linear;
|
||||
}
|
||||
|
||||
FKeyHandle FindAdjacentKeyHandle(const FRichCurve& Curve, FKeyHandle KeyHandle, bool bFindNext)
|
||||
{
|
||||
const float KeyTime = Curve.GetKeyTime(KeyHandle);
|
||||
FKeyHandle BestHandle;
|
||||
float BestDistance = TNumericLimits<float>::Max();
|
||||
for (auto It = Curve.GetKeyHandleIterator(); It; ++It)
|
||||
{
|
||||
const FKeyHandle CandidateHandle = *It;
|
||||
if (CandidateHandle == KeyHandle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float CandidateTime = Curve.GetKeyTime(CandidateHandle);
|
||||
const float Distance = bFindNext ? CandidateTime - KeyTime : KeyTime - CandidateTime;
|
||||
if (Distance > 0.0f && Distance < BestDistance)
|
||||
{
|
||||
BestDistance = Distance;
|
||||
BestHandle = CandidateHandle;
|
||||
}
|
||||
}
|
||||
|
||||
return BestHandle;
|
||||
}
|
||||
|
||||
void ApplyVisibleEaseTangents(FRichCurve& Curve, FKeyHandle KeyHandle)
|
||||
{
|
||||
FKeyHandle SegmentStartHandle = KeyHandle;
|
||||
FKeyHandle SegmentEndHandle = FindAdjacentKeyHandle(Curve, SegmentStartHandle, true);
|
||||
if (!SegmentEndHandle.IsValid())
|
||||
{
|
||||
SegmentEndHandle = SegmentStartHandle;
|
||||
SegmentStartHandle = FindAdjacentKeyHandle(Curve, SegmentEndHandle, false);
|
||||
}
|
||||
if (!SegmentStartHandle.IsValid() || !SegmentEndHandle.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Curve.SetKeyTangentMode(SegmentStartHandle, RCTM_Break, false);
|
||||
Curve.SetKeyTangentMode(SegmentEndHandle, RCTM_Break, false);
|
||||
Curve.GetKey(SegmentStartHandle).LeaveTangent = 0.0f;
|
||||
Curve.GetKey(SegmentEndHandle).ArriveTangent = 0.0f;
|
||||
}
|
||||
|
||||
void WriteCurveKeys(const FMorphTimelineClip& Clip, const TSharedPtr<FJsonObject>& Obj)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> KeyArray;
|
||||
if (const FRichCurve* Curve = Clip.WeightCurve.GetRichCurveConst())
|
||||
{
|
||||
for (auto It = Curve->GetKeyHandleIterator(); It; ++It)
|
||||
{
|
||||
const FKeyHandle KeyHandle = *It;
|
||||
TSharedPtr<FJsonObject> KeyObj = MakeShared<FJsonObject>();
|
||||
KeyObj->SetNumberField(TEXT("time"), Curve->GetKeyTime(KeyHandle));
|
||||
KeyObj->SetNumberField(TEXT("value"), Curve->GetKeyValue(KeyHandle));
|
||||
KeyObj->SetStringField(TEXT("interp"), CurveInterpToString(Curve->GetKeyInterpMode(KeyHandle)));
|
||||
KeyObj->SetStringField(TEXT("interpMode"), CurveInterpToUnrealString(Curve->GetKeyInterpMode(KeyHandle)));
|
||||
KeyObj->SetStringField(TEXT("tangentMode"), TEXT("RCTM_Auto"));
|
||||
KeyObj->SetStringField(TEXT("tangentWeightMode"), TEXT("RCTWM_WeightedNone"));
|
||||
KeyObj->SetNumberField(TEXT("arriveTangent"), 0.0);
|
||||
KeyObj->SetNumberField(TEXT("arriveTangentWeight"), 0.0);
|
||||
KeyObj->SetNumberField(TEXT("leaveTangent"), 0.0);
|
||||
KeyObj->SetNumberField(TEXT("leaveTangentWeight"), 0.0);
|
||||
KeyArray.Add(MakeShared<FJsonValueObject>(KeyObj));
|
||||
}
|
||||
}
|
||||
|
||||
Obj->SetArrayField(TEXT("curve_keys"), KeyArray);
|
||||
Obj->SetArrayField(TEXT("curveKeys"), KeyArray);
|
||||
|
||||
TSharedPtr<FJsonObject> EditorCurveDataObj = MakeShared<FJsonObject>();
|
||||
EditorCurveDataObj->SetArrayField(TEXT("keys"), KeyArray);
|
||||
EditorCurveDataObj->SetNumberField(TEXT("defaultValue"), TNumericLimits<float>::Max());
|
||||
EditorCurveDataObj->SetStringField(TEXT("preInfinityExtrap"), TEXT("RCCE_Constant"));
|
||||
EditorCurveDataObj->SetStringField(TEXT("postInfinityExtrap"), TEXT("RCCE_Constant"));
|
||||
|
||||
TSharedPtr<FJsonObject> WeightCurveObj = MakeShared<FJsonObject>();
|
||||
WeightCurveObj->SetObjectField(TEXT("editorCurveData"), EditorCurveDataObj);
|
||||
WeightCurveObj->SetStringField(TEXT("externalCurve"), TEXT("None"));
|
||||
Obj->SetObjectField(TEXT("weightCurve"), WeightCurveObj);
|
||||
}
|
||||
|
||||
float ParseCurveNumber(const TSharedPtr<FJsonValue>& Value, float DefaultValue)
|
||||
{
|
||||
double Number = 0.0;
|
||||
return Value.IsValid() && Value->TryGetNumber(Number) ? static_cast<float>(Number) : DefaultValue;
|
||||
}
|
||||
|
||||
void ReadCurveKeys(const TSharedPtr<FJsonObject>& Obj, FMorphTimelineClip& Clip)
|
||||
{
|
||||
if (!Obj.IsValid())
|
||||
{
|
||||
Clip.EnsureDefaultCurve();
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<TSharedPtr<FJsonValue>>* KeyArray = nullptr;
|
||||
if ((!Obj->TryGetArrayField(TEXT("curve_keys"), KeyArray) || !KeyArray) &&
|
||||
(!Obj->TryGetArrayField(TEXT("curveKeys"), KeyArray) || !KeyArray))
|
||||
{
|
||||
if (const TSharedPtr<FJsonObject>* WeightCurveObj = nullptr; Obj->TryGetObjectField(TEXT("weightCurve"), WeightCurveObj) && WeightCurveObj->IsValid())
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* EditorCurveDataObj = nullptr;
|
||||
if (!(*WeightCurveObj)->TryGetObjectField(TEXT("editorCurveData"), EditorCurveDataObj) || !EditorCurveDataObj || !EditorCurveDataObj->IsValid() ||
|
||||
!(*EditorCurveDataObj)->TryGetArrayField(TEXT("keys"), KeyArray) || !KeyArray)
|
||||
{
|
||||
Clip.EnsureDefaultCurve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Clip.EnsureDefaultCurve();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FRichCurve* Curve = Clip.WeightCurve.GetRichCurve();
|
||||
if (!Curve)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Curve->Reset();
|
||||
TArray<FKeyHandle> EaseKeyHandles;
|
||||
for (const TSharedPtr<FJsonValue>& KeyValue : *KeyArray)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* KeyObj = nullptr;
|
||||
if (!KeyValue.IsValid() || !KeyValue->TryGetObject(KeyObj) || !KeyObj || !KeyObj->IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float Time = FMath::Clamp(ParseCurveNumber((*KeyObj)->TryGetField(TEXT("time")), 0.0f), 0.0f, FMath::Max(Clip.Duration, 0.001f));
|
||||
const float Value = FMath::Clamp(ParseCurveNumber((*KeyObj)->TryGetField(TEXT("value")), 0.0f), 0.0f, 1.0f);
|
||||
const ERichCurveInterpMode InterpMode = ParseCurveInterp((*KeyObj)->TryGetField(TEXT("interp")).IsValid()
|
||||
? (*KeyObj)->TryGetField(TEXT("interp"))
|
||||
: (*KeyObj)->TryGetField(TEXT("interpMode")));
|
||||
const FKeyHandle KeyHandle = Curve->UpdateOrAddKey(Time, Value);
|
||||
Curve->SetKeyInterpMode(KeyHandle, InterpMode, false);
|
||||
if (InterpMode == RCIM_Cubic)
|
||||
{
|
||||
EaseKeyHandles.Add(KeyHandle);
|
||||
}
|
||||
}
|
||||
|
||||
for (const FKeyHandle KeyHandle : EaseKeyHandles)
|
||||
{
|
||||
ApplyVisibleEaseTangents(*Curve, KeyHandle);
|
||||
}
|
||||
|
||||
Clip.EnsureDefaultCurve();
|
||||
}
|
||||
}
|
||||
|
||||
void SMorphPresetEditorWindow::Construct(const FArguments& InArgs)
|
||||
{
|
||||
if (UMorphPresetDataAsset* PresetArg = InArgs._Preset)
|
||||
{
|
||||
SelectedPreset = PresetArg;
|
||||
bEmbeddedMode = true;
|
||||
}
|
||||
ChildSlot
|
||||
[
|
||||
BuildContent()
|
||||
];
|
||||
if (bEmbeddedMode && SelectedPreset.IsValid())
|
||||
{
|
||||
RefreshFromPreset();
|
||||
}
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> SMorphPresetEditorWindow::BuildContent()
|
||||
{
|
||||
TSharedRef<SVerticalBox> Root = SNew(SVerticalBox);
|
||||
|
||||
if (!bEmbeddedMode)
|
||||
{
|
||||
Root->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(4.0f)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
.Padding(0, 0, 8, 0)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("PresetLabel", "Morph Preset:"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
[
|
||||
SAssignNew(PresetPicker, SObjectPropertyEntryBox)
|
||||
.AllowedClass(UMorphPresetDataAsset::StaticClass())
|
||||
.ObjectPath(this, &SMorphPresetEditorWindow::GetSelectedPresetPath)
|
||||
.OnObjectChanged(this, &SMorphPresetEditorWindow::OnPresetChanged)
|
||||
.DisplayUseSelected(true)
|
||||
.DisplayBrowse(true)
|
||||
.AllowClear(true)
|
||||
.AllowCreate(false)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
Root->AddSlot()
|
||||
.FillHeight(1.0f)
|
||||
.Padding(4.0f)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("JsonLabel", "JSON (структурированный формат):"))
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
.Padding(0, 4)
|
||||
[
|
||||
SAssignNew(JsonTextBox, SMultiLineEditableTextBox)
|
||||
.HintText(LOCTEXT("JsonHint",
|
||||
"{\n"
|
||||
" \"metadata\": { \"display_name\": \"Моргание\", \"description\": \"Полное моргание\" },\n"
|
||||
" \"settings\": { \"duration\": 0.15, \"loop\": true, \"loop_delay\": 2.5, \"blend_speed\": 18 },\n"
|
||||
" \"morph_targets\": [\n"
|
||||
" { \"name\": \"eyeBlinkLeft\", \"start_time\": 0, \"duration\": 0.15, \"weight\": 1.5, \"curve_keys\": [{ \"time\": 0, \"value\": 0, \"interp\": \"ease\" }, { \"time\": 0.15, \"value\": 0, \"interp\": \"ease\" }] },\n"
|
||||
" { \"name\": \"eyeBlinkRight\", \"start_time\": 0, \"duration\": 0.15, \"weight\": 1.5, \"curve_keys\": [{ \"time\": 0, \"value\": 0, \"interp\": \"ease\" }, { \"time\": 0.15, \"value\": 0, \"interp\": \"ease\" }] }\n"
|
||||
" ]\n"
|
||||
"}"))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Mono", 10))
|
||||
]
|
||||
];
|
||||
|
||||
Root->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(4.0f)
|
||||
.HAlign(HAlign_Right)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(0, 0, 8, 0)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("ExportButton", "Export"))
|
||||
.OnClicked(this, &SMorphPresetEditorWindow::OnExportClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("ApplyButton", "Apply"))
|
||||
.OnClicked(this, &SMorphPresetEditorWindow::OnApplyClicked)
|
||||
]
|
||||
];
|
||||
|
||||
return Root;
|
||||
}
|
||||
|
||||
void SMorphPresetEditorWindow::RefreshFromPreset()
|
||||
{
|
||||
if (!SelectedPreset.IsValid() || !JsonTextBox.IsValid()) return;
|
||||
TSharedPtr<FJsonObject> RootObj = MakeShared<FJsonObject>();
|
||||
RootObj->SetStringField(TEXT("displayName"), SelectedPreset->DisplayName.ToString());
|
||||
RootObj->SetStringField(TEXT("description"), SelectedPreset->Description.ToString());
|
||||
TArray<TSharedPtr<FJsonValue>> MorphArr;
|
||||
for (const FMorphTimelineClip& Clip : SelectedPreset->MorphTargets)
|
||||
{
|
||||
if (!Clip.IsValid()) continue;
|
||||
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
||||
Obj->SetStringField(TEXT("name"), Clip.Name.ToString());
|
||||
Obj->SetNumberField(TEXT("startTime"), Clip.StartTime);
|
||||
Obj->SetNumberField(TEXT("duration"), Clip.Duration);
|
||||
Obj->SetNumberField(TEXT("weight"), Clip.Weight);
|
||||
WriteCurveKeys(Clip, Obj);
|
||||
MorphArr.Add(MakeShared<FJsonValueObject>(Obj));
|
||||
}
|
||||
RootObj->SetArrayField(TEXT("morphTargets"), MorphArr);
|
||||
RootObj->SetNumberField(TEXT("duration"), SelectedPreset->Duration);
|
||||
RootObj->SetBoolField(TEXT("bLoop"), SelectedPreset->bLoop);
|
||||
RootObj->SetNumberField(TEXT("loopDelay"), SelectedPreset->LoopDelay);
|
||||
RootObj->SetNumberField(TEXT("blendSpeedOverride"), SelectedPreset->BlendSpeedOverride);
|
||||
RootObj->SetStringField(TEXT("nativeClass"), TEXT("/Script/CoreUObject.Class'/Script/FunnyGirl.MorphPresetDataAsset'"));
|
||||
FString OutputString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString, 0);
|
||||
FJsonSerializer::Serialize(RootObj.ToSharedRef(), Writer);
|
||||
JsonTextBox->SetText(FText::FromString(OutputString));
|
||||
}
|
||||
|
||||
FString SMorphPresetEditorWindow::GetSelectedPresetPath() const
|
||||
{
|
||||
if (UMorphPresetDataAsset* Preset = SelectedPreset.Get())
|
||||
{
|
||||
return Preset->GetPathName();
|
||||
}
|
||||
return FString();
|
||||
}
|
||||
|
||||
void SMorphPresetEditorWindow::OnPresetChanged(const FAssetData& AssetData)
|
||||
{
|
||||
SelectedPreset = Cast<UMorphPresetDataAsset>(AssetData.GetAsset());
|
||||
}
|
||||
|
||||
FReply SMorphPresetEditorWindow::OnApplyClicked()
|
||||
{
|
||||
UMorphPresetDataAsset* Preset = SelectedPreset.Get();
|
||||
if (!Preset)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Morph Preset Editor: Select a Morph Preset first."));
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FString JsonString = JsonTextBox->GetText().ToString();
|
||||
if (JsonString.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Morph Preset Editor: JSON is empty."));
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Morph Preset Editor: Invalid JSON: %s"), *Reader->GetErrorMessage());
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
// Структурированный парсинг: metadata, settings, morph_targets
|
||||
// Поддержка обратной совместимости: плоский формат (все поля в корне)
|
||||
auto ParseFloat = [](const TSharedPtr<FJsonValue>& Val, float Default) -> float
|
||||
{
|
||||
double Num = 0.0;
|
||||
return Val.IsValid() && Val->TryGetNumber(Num) ? static_cast<float>(FMath::Max(0.0, Num)) : Default;
|
||||
};
|
||||
auto ParseBool = [](const TSharedPtr<FJsonValue>& Val, bool Default) -> bool
|
||||
{
|
||||
bool b = false;
|
||||
if (Val.IsValid() && Val->TryGetBool(b)) return b;
|
||||
double Num = 0.0;
|
||||
if (Val.IsValid() && Val->TryGetNumber(Num)) return Num != 0.0;
|
||||
return Default;
|
||||
};
|
||||
auto ParseInt = [](const TSharedPtr<FJsonValue>& Val, int32 Default) -> int32
|
||||
{
|
||||
double Num = 0.0;
|
||||
return Val.IsValid() && Val->TryGetNumber(Num) ? FMath::Max(0, static_cast<int32>(Num)) : Default;
|
||||
};
|
||||
auto ParseString = [](const TSharedPtr<FJsonValue>& Val) -> FString
|
||||
{
|
||||
FString S;
|
||||
return Val.IsValid() && Val->TryGetString(S) ? S : FString();
|
||||
};
|
||||
|
||||
TArray<FMorphTimelineClip> NewMorphTargets;
|
||||
|
||||
// metadata
|
||||
if (const TSharedPtr<FJsonObject>* MetaObj = nullptr; JsonObject->TryGetObjectField(TEXT("metadata"), MetaObj) && MetaObj->IsValid())
|
||||
{
|
||||
Preset->DisplayName = FText::FromString(ParseString((*MetaObj)->TryGetField(TEXT("display_name"))));
|
||||
Preset->Description = FText::FromString(ParseString((*MetaObj)->TryGetField(TEXT("description"))));
|
||||
}
|
||||
else
|
||||
{
|
||||
FString DisplayNameStr = ParseString(JsonObject->TryGetField(TEXT("displayName")));
|
||||
if (!DisplayNameStr.IsEmpty()) Preset->DisplayName = FText::FromString(DisplayNameStr);
|
||||
FString DescStr = ParseString(JsonObject->TryGetField(TEXT("description")));
|
||||
if (!DescStr.IsEmpty()) Preset->Description = FText::FromString(DescStr);
|
||||
}
|
||||
|
||||
// settings
|
||||
if (const TSharedPtr<FJsonObject>* SettingsObj = nullptr; JsonObject->TryGetObjectField(TEXT("settings"), SettingsObj) && SettingsObj->IsValid())
|
||||
{
|
||||
Preset->Duration = ParseFloat((*SettingsObj)->TryGetField(TEXT("duration")), Preset->Duration);
|
||||
Preset->bLoop = ParseBool((*SettingsObj)->TryGetField(TEXT("loop")), Preset->bLoop);
|
||||
Preset->LoopDelay = ParseFloat((*SettingsObj)->TryGetField(TEXT("loop_delay")), Preset->LoopDelay);
|
||||
Preset->BlendSpeedOverride = ParseFloat((*SettingsObj)->TryGetField(TEXT("blend_speed")), Preset->BlendSpeedOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
Preset->Duration = ParseFloat(JsonObject->TryGetField(TEXT("duration")), Preset->Duration);
|
||||
Preset->bLoop = ParseBool(JsonObject->TryGetField(TEXT("bLoop")), Preset->bLoop);
|
||||
Preset->LoopDelay = ParseFloat(JsonObject->TryGetField(TEXT("loopDelay")), Preset->LoopDelay);
|
||||
Preset->BlendSpeedOverride = ParseFloat(JsonObject->TryGetField(TEXT("blendSpeedOverride")), Preset->BlendSpeedOverride);
|
||||
}
|
||||
|
||||
// morph_targets: массив объектов { name, start_time, duration, weight, curve_keys? }
|
||||
const TArray<TSharedPtr<FJsonValue>>* MorphArr = nullptr;
|
||||
if ((JsonObject->TryGetArrayField(TEXT("morph_targets"), MorphArr) || JsonObject->TryGetArrayField(TEXT("morphTargets"), MorphArr)) && MorphArr)
|
||||
{
|
||||
for (const TSharedPtr<FJsonValue>& Val : *MorphArr)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* Obj = nullptr;
|
||||
if (Val->TryGetObject(Obj) && Obj->IsValid())
|
||||
{
|
||||
FString NameStr = ParseString((*Obj)->TryGetField(TEXT("name")));
|
||||
if (NameStr.IsEmpty()) continue;
|
||||
|
||||
FMorphTimelineClip Clip;
|
||||
Clip.Name = FName(*NameStr);
|
||||
Clip.StartTime = ParseFloat((*Obj)->TryGetField(TEXT("start_time")),
|
||||
ParseFloat((*Obj)->TryGetField(TEXT("startTime")), ParseFloat((*Obj)->TryGetField(TEXT("delay")), 0.0f)));
|
||||
Clip.Duration = ParseFloat((*Obj)->TryGetField(TEXT("duration")), 0.2f);
|
||||
if (Clip.Duration < 0.001f) Clip.Duration = 0.2f;
|
||||
Clip.Weight = ParseFloat((*Obj)->TryGetField(TEXT("weight")), 1.0f);
|
||||
Clip.Weight = FMath::Clamp(Clip.Weight, 0.0f, 5.0f);
|
||||
ReadCurveKeys(*Obj, Clip);
|
||||
NewMorphTargets.Add(Clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Обратная совместимость: старый формат morph_targets как объект { "name": weight }
|
||||
if (NewMorphTargets.Num() == 0)
|
||||
{
|
||||
if (const TSharedPtr<FJsonObject>* MorphObj = nullptr; JsonObject->TryGetObjectField(TEXT("morph_targets"), MorphObj) && MorphObj->IsValid())
|
||||
{
|
||||
for (const auto& Pair : (*MorphObj)->Values)
|
||||
{
|
||||
double NumValue = 0.0;
|
||||
if (Pair.Value->TryGetNumber(NumValue))
|
||||
{
|
||||
FMorphTimelineClip Clip;
|
||||
Clip.Name = FName(*Pair.Key);
|
||||
Clip.StartTime = 0.0f;
|
||||
Clip.Duration = 0.2f;
|
||||
Clip.Weight = static_cast<float>(FMath::Clamp(NumValue, 0.0, 5.0));
|
||||
Clip.EnsureDefaultCurve();
|
||||
NewMorphTargets.Add(Clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Старый morph_target_settings (если morph_targets был объектом)
|
||||
if (const TSharedPtr<FJsonObject>* SettingsMap = nullptr; JsonObject->TryGetObjectField(TEXT("morph_target_settings"), SettingsMap) && SettingsMap->IsValid())
|
||||
{
|
||||
for (const auto& Pair : (*SettingsMap)->Values)
|
||||
{
|
||||
const TSharedPtr<FJsonObject>* TimingObj = nullptr;
|
||||
if (Pair.Value->TryGetObject(TimingObj) && TimingObj->IsValid())
|
||||
{
|
||||
FName MorphName(*Pair.Key);
|
||||
for (FMorphTimelineClip& Clip : NewMorphTargets)
|
||||
{
|
||||
if (Clip.Name == MorphName)
|
||||
{
|
||||
Clip.StartTime = ParseFloat((*TimingObj)->TryGetField(TEXT("delay")), 0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Плоский формат (поля в корне) — если morph_targets не найден
|
||||
if (NewMorphTargets.Num() == 0)
|
||||
{
|
||||
for (const auto& Pair : JsonObject->Values)
|
||||
{
|
||||
const FString& Key = Pair.Key;
|
||||
const TSharedPtr<FJsonValue>& Value = Pair.Value;
|
||||
if (Key.Equals(TEXT("metadata"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("settings"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("morph_targets"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("morph_target_settings"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("duration"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("loop"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("loop_delay"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("blend_speed"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("display_name"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("description"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("morphTargets"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("bLoop"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("loopDelay"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("blendSpeedOverride"), ESearchCase::IgnoreCase) || Key.Equals(TEXT("displayName"), ESearchCase::IgnoreCase) ||
|
||||
Key.Equals(TEXT("nativeClass"), ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
double NumValue = 0.0;
|
||||
if (Value->TryGetNumber(NumValue))
|
||||
{
|
||||
FMorphTimelineClip Clip;
|
||||
Clip.Name = FName(*Key);
|
||||
Clip.StartTime = 0.0f;
|
||||
Clip.Duration = 0.2f;
|
||||
Clip.Weight = static_cast<float>(FMath::Clamp(NumValue, 0.0, 5.0));
|
||||
Clip.EnsureDefaultCurve();
|
||||
NewMorphTargets.Add(Clip);
|
||||
}
|
||||
}
|
||||
Preset->Duration = ParseFloat(JsonObject->TryGetField(TEXT("duration")), Preset->Duration);
|
||||
Preset->bLoop = ParseBool(JsonObject->TryGetField(TEXT("loop")), Preset->bLoop);
|
||||
Preset->LoopDelay = ParseFloat(JsonObject->TryGetField(TEXT("loop_delay")), Preset->LoopDelay);
|
||||
Preset->BlendSpeedOverride = ParseFloat(JsonObject->TryGetField(TEXT("blend_speed")), Preset->BlendSpeedOverride);
|
||||
FString DisplayNameStr = ParseString(JsonObject->TryGetField(TEXT("display_name")));
|
||||
if (!DisplayNameStr.IsEmpty()) Preset->DisplayName = FText::FromString(DisplayNameStr);
|
||||
FString DescStr = ParseString(JsonObject->TryGetField(TEXT("description")));
|
||||
if (!DescStr.IsEmpty()) Preset->Description = FText::FromString(DescStr);
|
||||
}
|
||||
|
||||
Preset->MorphTargets = MoveTemp(NewMorphTargets);
|
||||
Preset->MarkPackageDirty();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("Morph Preset Editor: Applied %d morph targets to %s"), Preset->GetMorphCount(), *Preset->GetName());
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SMorphPresetEditorWindow::OnExportClicked()
|
||||
{
|
||||
UMorphPresetDataAsset* Preset = SelectedPreset.Get();
|
||||
if (!Preset)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Morph Preset Editor: Select a Morph Preset first."));
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
TSharedPtr<FJsonObject> RootObj = MakeShared<FJsonObject>();
|
||||
RootObj->SetStringField(TEXT("displayName"), Preset->DisplayName.ToString());
|
||||
RootObj->SetStringField(TEXT("description"), Preset->Description.ToString());
|
||||
|
||||
// morph_targets — массив объектов (timeline clips)
|
||||
TArray<TSharedPtr<FJsonValue>> MorphArr;
|
||||
for (const FMorphTimelineClip& Clip : Preset->MorphTargets)
|
||||
{
|
||||
if (!Clip.IsValid()) continue;
|
||||
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
||||
Obj->SetStringField(TEXT("name"), Clip.Name.ToString());
|
||||
Obj->SetNumberField(TEXT("startTime"), Clip.StartTime);
|
||||
Obj->SetNumberField(TEXT("duration"), Clip.Duration);
|
||||
Obj->SetNumberField(TEXT("weight"), Clip.Weight);
|
||||
WriteCurveKeys(Clip, Obj);
|
||||
MorphArr.Add(MakeShared<FJsonValueObject>(Obj));
|
||||
}
|
||||
RootObj->SetArrayField(TEXT("morphTargets"), MorphArr);
|
||||
RootObj->SetNumberField(TEXT("duration"), Preset->Duration);
|
||||
RootObj->SetBoolField(TEXT("bLoop"), Preset->bLoop);
|
||||
RootObj->SetNumberField(TEXT("loopDelay"), Preset->LoopDelay);
|
||||
RootObj->SetNumberField(TEXT("blendSpeedOverride"), Preset->BlendSpeedOverride);
|
||||
RootObj->SetStringField(TEXT("nativeClass"), TEXT("/Script/CoreUObject.Class'/Script/FunnyGirl.MorphPresetDataAsset'"));
|
||||
|
||||
FString OutputString;
|
||||
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString, 0);
|
||||
FJsonSerializer::Serialize(RootObj.ToSharedRef(), Writer);
|
||||
|
||||
JsonTextBox->SetText(FText::FromString(OutputString));
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,479 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "Morph/Editor/MorphPresetViewportPanel.h"
|
||||
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "AdvancedPreviewScene.h"
|
||||
#include "Animation/DebugSkelMeshComponent.h"
|
||||
#include "EditorViewportClient.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "IPersonaPreviewScene.h"
|
||||
#include "Morph/LipSyncMappingDataAsset.h"
|
||||
#include "Morph/Editor/MorphPresetEditorController.h"
|
||||
#include "PropertyCustomizationHelpers.h"
|
||||
#include "SEditorViewport.h"
|
||||
#include "Sound/SoundBase.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
#include "Widgets/Input/SButton.h"
|
||||
#include "Widgets/Input/SEditableTextBox.h"
|
||||
#include "Widgets/Input/SSlider.h"
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MorphPresetViewportPanel"
|
||||
|
||||
namespace
|
||||
{
|
||||
static constexpr float MinFaceCameraDistance = 90.0f;
|
||||
static constexpr float MaxFaceCameraDistance = 220.0f;
|
||||
|
||||
FName FindHeadBoneName(const UDebugSkelMeshComponent* PreviewMeshComponent)
|
||||
{
|
||||
if (!PreviewMeshComponent)
|
||||
{
|
||||
return NAME_None;
|
||||
}
|
||||
|
||||
static const FName CandidateBoneNames[] =
|
||||
{
|
||||
TEXT("head"),
|
||||
TEXT("Head"),
|
||||
TEXT("head_jnt"),
|
||||
TEXT("Head_jnt")
|
||||
};
|
||||
|
||||
for (const FName CandidateBoneName : CandidateBoneNames)
|
||||
{
|
||||
if (PreviewMeshComponent->GetBoneIndex(CandidateBoneName) != INDEX_NONE)
|
||||
{
|
||||
return CandidateBoneName;
|
||||
}
|
||||
}
|
||||
|
||||
return NAME_None;
|
||||
}
|
||||
|
||||
FVector ResolveFaceForwardDirection(const FTransform& HeadTransform)
|
||||
{
|
||||
const FVector PreferredDirection = FVector::YAxisVector;
|
||||
const FVector CandidateDirections[] =
|
||||
{
|
||||
HeadTransform.GetUnitAxis(EAxis::X),
|
||||
-HeadTransform.GetUnitAxis(EAxis::X),
|
||||
HeadTransform.GetUnitAxis(EAxis::Y),
|
||||
-HeadTransform.GetUnitAxis(EAxis::Y)
|
||||
};
|
||||
|
||||
float BestScore = -FLT_MAX;
|
||||
FVector BestDirection = PreferredDirection;
|
||||
for (const FVector& CandidateDirection : CandidateDirections)
|
||||
{
|
||||
FVector FlattenedDirection = FVector(CandidateDirection.X, CandidateDirection.Y, 0.0f);
|
||||
if (!FlattenedDirection.Normalize())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const float Score = FVector::DotProduct(FlattenedDirection, PreferredDirection);
|
||||
if (Score > BestScore)
|
||||
{
|
||||
BestScore = Score;
|
||||
BestDirection = FlattenedDirection;
|
||||
}
|
||||
}
|
||||
|
||||
return BestDirection;
|
||||
}
|
||||
|
||||
void ResolveDefaultCamera(const TSharedPtr<IPersonaPreviewScene>& PreviewScene, FVector& OutCameraLocation, FVector& OutCameraTarget)
|
||||
{
|
||||
OutCameraTarget = FVector(0.0f, 0.0f, 80.0f);
|
||||
FVector FaceForwardDirection = FVector::YAxisVector;
|
||||
float CameraDistance = 165.0f;
|
||||
|
||||
if (PreviewScene.IsValid())
|
||||
{
|
||||
if (UDebugSkelMeshComponent* PreviewMeshComponent = PreviewScene->GetPreviewMeshComponent())
|
||||
{
|
||||
const FBoxSphereBounds Bounds = PreviewMeshComponent->Bounds;
|
||||
CameraDistance = FMath::Clamp(FMath::Max(Bounds.BoxExtent.X, Bounds.BoxExtent.Y) * 1.35f, MinFaceCameraDistance, MaxFaceCameraDistance);
|
||||
OutCameraTarget = Bounds.Origin + FVector(0.0f, 0.0f, Bounds.BoxExtent.Z * 0.62f);
|
||||
|
||||
const FName HeadBoneName = FindHeadBoneName(PreviewMeshComponent);
|
||||
if (HeadBoneName != NAME_None)
|
||||
{
|
||||
const FTransform HeadTransform = PreviewMeshComponent->GetSocketTransform(HeadBoneName, RTS_World);
|
||||
OutCameraTarget = HeadTransform.GetLocation() + FVector(0.0f, 0.0f, 4.0f);
|
||||
FaceForwardDirection = ResolveFaceForwardDirection(HeadTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutCameraLocation = OutCameraTarget + FaceForwardDirection * CameraDistance + FVector(0.0f, 0.0f, 8.0f);
|
||||
}
|
||||
|
||||
class SMorphPresetPreviewViewport : public SEditorViewport
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMorphPresetPreviewViewport) {}
|
||||
SLATE_ARGUMENT(TSharedPtr<FMorphPresetEditorController>, Controller)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs)
|
||||
{
|
||||
Controller = InArgs._Controller;
|
||||
SEditorViewport::Construct(SEditorViewport::FArguments());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<FEditorViewportClient> MakeEditorViewportClient() override
|
||||
{
|
||||
TSharedPtr<IPersonaPreviewScene> PreviewScene = Controller.IsValid() ? Controller->GetPreviewScene() : nullptr;
|
||||
check(PreviewScene.IsValid());
|
||||
|
||||
TSharedRef<FEditorViewportClient> ViewportClient = MakeShared<FEditorViewportClient>(nullptr, static_cast<FPreviewScene*>(PreviewScene.Get()), SharedThis(this));
|
||||
FVector CameraLocation = FVector::ZeroVector;
|
||||
FVector CameraTarget = FVector::ZeroVector;
|
||||
ResolveDefaultCamera(PreviewScene, CameraLocation, CameraTarget);
|
||||
ViewportClient->SetViewLocation(CameraLocation);
|
||||
ViewportClient->SetViewRotation((CameraTarget - CameraLocation).Rotation());
|
||||
ViewportClient->SetLookAtLocation(CameraTarget);
|
||||
ViewportClient->SetViewModes(VMI_Lit, VMI_Lit);
|
||||
ViewportClient->SetRealtime(true);
|
||||
return ViewportClient;
|
||||
}
|
||||
|
||||
virtual TSharedPtr<SWidget> BuildViewportToolbar() override
|
||||
{
|
||||
return SNullWidget::NullWidget;
|
||||
}
|
||||
|
||||
private:
|
||||
TSharedPtr<FMorphPresetEditorController> Controller;
|
||||
};
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::Construct(const FArguments& InArgs)
|
||||
{
|
||||
Controller = InArgs._Controller;
|
||||
AssetEditorToolkit = InArgs._AssetEditorToolkit;
|
||||
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->OnPreviewSceneChanged().AddSP(this, &SMorphPresetViewportPanel::RebuildContent);
|
||||
Controller->OnPresetChanged().AddSP(this, &SMorphPresetViewportPanel::RebuildContent);
|
||||
}
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(RootBorder, SBorder)
|
||||
.Padding(8.0f)
|
||||
];
|
||||
|
||||
RebuildContent();
|
||||
RegisterActiveTimer(0.0f, FWidgetActiveTimerDelegate::CreateSP(this, &SMorphPresetViewportPanel::HandleActiveTimer));
|
||||
}
|
||||
|
||||
SMorphPresetViewportPanel::~SMorphPresetViewportPanel()
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->OnPreviewSceneChanged().RemoveAll(this);
|
||||
Controller->OnPresetChanged().RemoveAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::RebuildContent()
|
||||
{
|
||||
if (!RootBorder.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Controller.IsValid() || !Controller->GetPreviewScene().IsValid())
|
||||
{
|
||||
RootBorder->SetContent(
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("PreviewMeshLabel", "Preview Mesh"))
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SObjectPropertyEntryBox)
|
||||
.AllowedClass(USkeletalMesh::StaticClass())
|
||||
.ObjectPath(this, &SMorphPresetViewportPanel::GetSelectedPreviewMeshPath)
|
||||
.OnObjectChanged(this, &SMorphPresetViewportPanel::OnPreviewMeshChanged)
|
||||
.DisplayUseSelected(true)
|
||||
.DisplayBrowse(true)
|
||||
.AllowClear(true)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Center)
|
||||
.Padding(0.0f, 16.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("NoPreviewMesh", "Assign a skeletal mesh to see the Morph Target preset in the viewport."))
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
RootBorder->SetContent(
|
||||
SNew(SVerticalBox)
|
||||
+ 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("LipSyncMappingLabel", "Lip Sync Mapping"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
.Padding(0.0f, 0.0f, 12.0f, 0.0f)
|
||||
[
|
||||
SNew(SObjectPropertyEntryBox)
|
||||
.AllowedClass(ULipSyncMappingDataAsset::StaticClass())
|
||||
.ObjectPath(this, &SMorphPresetViewportPanel::GetSelectedLipSyncMappingPath)
|
||||
.OnObjectChanged(this, &SMorphPresetViewportPanel::OnLipSyncMappingChanged)
|
||||
.DisplayUseSelected(true)
|
||||
.DisplayBrowse(true)
|
||||
.AllowClear(true)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("LipSyncAudioLabel", "Lip Sync Audio"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.FillWidth(1.0f)
|
||||
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
|
||||
[
|
||||
SNew(SObjectPropertyEntryBox)
|
||||
.AllowedClass(USoundBase::StaticClass())
|
||||
.ObjectPath(this, &SMorphPresetViewportPanel::GetSelectedLipSyncSoundPath)
|
||||
.OnObjectChanged(this, &SMorphPresetViewportPanel::OnLipSyncSoundChanged)
|
||||
.DisplayUseSelected(true)
|
||||
.DisplayBrowse(true)
|
||||
.AllowClear(true)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(0.0f, 0.0f, 4.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(this, &SMorphPresetViewportPanel::GetLipSyncPlaybackText)
|
||||
.OnClicked(this, &SMorphPresetViewportPanel::OnPlayLipSyncClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(LOCTEXT("StopLipSync", "Stop"))
|
||||
.OnClicked(this, &SMorphPresetViewportPanel::OnStopLipSyncClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
.Padding(12.0f, 0.0f, 6.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(LOCTEXT("LipSyncSmoothLabel", "Smooth"))
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(SSlider)
|
||||
.MinValue(0.0f)
|
||||
.MaxValue(1.0f)
|
||||
.Value(this, &SMorphPresetViewportPanel::GetLipSyncSmoothness)
|
||||
.OnValueChanged(this, &SMorphPresetViewportPanel::OnLipSyncSmoothnessChanged)
|
||||
]
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
|
||||
[
|
||||
SNew(SEditableTextBox)
|
||||
.HintText(LOCTEXT("LipSyncTranscriptHint", "Paste audio transcript here for text-assisted visemes"))
|
||||
.Text(this, &SMorphPresetViewportPanel::GetLipSyncTranscript)
|
||||
.OnTextChanged(this, &SMorphPresetViewportPanel::OnLipSyncTranscriptChanged)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(this, &SMorphPresetViewportPanel::GetPreviewStatusText)
|
||||
.Visibility(this, &SMorphPresetViewportPanel::GetPreviewStatusVisibility)
|
||||
.ColorAndOpacity(FSlateColor(FLinearColor(1.0f, 0.56f, 0.12f)))
|
||||
.AutoWrapText(true)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.FillHeight(1.0f)
|
||||
[
|
||||
SNew(SMorphPresetPreviewViewport)
|
||||
.Controller(Controller)
|
||||
]);
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::OnPreviewMeshChanged(const FAssetData& AssetData)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->SetPreviewMesh(Cast<USkeletalMesh>(AssetData.GetAsset()));
|
||||
}
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::OnLipSyncSoundChanged(const FAssetData& AssetData)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->SetLipSyncPreviewSound(Cast<USoundBase>(AssetData.GetAsset()));
|
||||
}
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::OnLipSyncMappingChanged(const FAssetData& AssetData)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->SetLipSyncPreviewMapping(Cast<ULipSyncMappingDataAsset>(AssetData.GetAsset()));
|
||||
}
|
||||
}
|
||||
|
||||
FString SMorphPresetViewportPanel::GetSelectedPreviewMeshPath() const
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
if (USkeletalMesh* Mesh = Controller->GetPreviewMesh())
|
||||
{
|
||||
return Mesh->GetPathName();
|
||||
}
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString SMorphPresetViewportPanel::GetSelectedLipSyncSoundPath() const
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
if (USoundBase* Sound = Controller->GetLipSyncPreviewSound())
|
||||
{
|
||||
return Sound->GetPathName();
|
||||
}
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString SMorphPresetViewportPanel::GetSelectedLipSyncMappingPath() const
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
if (ULipSyncMappingDataAsset* Mapping = Controller->GetLipSyncPreviewMapping())
|
||||
{
|
||||
return Mapping->GetPathName();
|
||||
}
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
FReply SMorphPresetViewportPanel::OnPlayLipSyncClicked()
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
if (Controller->IsLipSyncPreviewAudioPlaying())
|
||||
{
|
||||
Controller->StopLipSyncPreviewAudio();
|
||||
}
|
||||
else
|
||||
{
|
||||
Controller->PlayLipSyncPreviewAudio();
|
||||
}
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SMorphPresetViewportPanel::OnStopLipSyncClicked()
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->StopLipSyncPreviewAudio();
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FText SMorphPresetViewportPanel::GetLipSyncPlaybackText() const
|
||||
{
|
||||
return Controller.IsValid() && Controller->IsLipSyncPreviewAudioPlaying()
|
||||
? LOCTEXT("PauseLipSync", "Pause Lip Sync")
|
||||
: LOCTEXT("PlayLipSync", "Play Lip Sync");
|
||||
}
|
||||
|
||||
float SMorphPresetViewportPanel::GetLipSyncSmoothness() const
|
||||
{
|
||||
return Controller.IsValid() ? Controller->GetLipSyncPreviewSmoothness() : 0.55f;
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::OnLipSyncSmoothnessChanged(float NewValue)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->SetLipSyncPreviewSmoothness(NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
FText SMorphPresetViewportPanel::GetLipSyncTranscript() const
|
||||
{
|
||||
return Controller.IsValid() ? Controller->GetLipSyncPreviewTranscript() : FText::GetEmpty();
|
||||
}
|
||||
|
||||
void SMorphPresetViewportPanel::OnLipSyncTranscriptChanged(const FText& NewText)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->SetLipSyncPreviewTranscript(NewText);
|
||||
}
|
||||
}
|
||||
|
||||
FText SMorphPresetViewportPanel::GetPreviewStatusText() const
|
||||
{
|
||||
return Controller.IsValid() ? Controller->GetPreviewStatusText() : FText::GetEmpty();
|
||||
}
|
||||
|
||||
EVisibility SMorphPresetViewportPanel::GetPreviewStatusVisibility() const
|
||||
{
|
||||
return !GetPreviewStatusText().IsEmpty() ? EVisibility::Visible : EVisibility::Collapsed;
|
||||
}
|
||||
|
||||
EActiveTimerReturnType SMorphPresetViewportPanel::HandleActiveTimer(double, float InDeltaTime)
|
||||
{
|
||||
if (Controller.IsValid())
|
||||
{
|
||||
Controller->Tick(InDeltaTime);
|
||||
}
|
||||
|
||||
return EActiveTimerReturnType::Continue;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Vendored for FunnyGirlEditor (adapted from UE Persona private SAnimEditorBase).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/GCObject.h"
|
||||
#include "IPersonaPreviewScene.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
#include "PersonaDelegates.h"
|
||||
|
||||
class UAnimationAsset;
|
||||
class UAnimPreviewInstance;
|
||||
|
||||
class FUNNYGIRLEDITOR_API SAnimEditorBase : public SCompoundWidget, public FGCObject
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SAnimEditorBase)
|
||||
: _DisplayAnimTimeline(true)
|
||||
, _DisplayAnimScrubBar(false)
|
||||
, _DisplayAnimScrubBarEditing(false)
|
||||
{}
|
||||
|
||||
SLATE_EVENT(FOnObjectsSelected, OnObjectsSelected)
|
||||
SLATE_ARGUMENT(bool, DisplayAnimTimeline)
|
||||
SLATE_ARGUMENT(bool, DisplayAnimScrubBar)
|
||||
SLATE_ARGUMENT(bool, DisplayAnimScrubBarEditing)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs, const TSharedPtr<IPersonaPreviewScene>& InPreviewScene);
|
||||
|
||||
float GetViewMinInput() const { return ViewMinInput; }
|
||||
float GetViewMaxInput() const { return ViewMaxInput; }
|
||||
float GetMinInput() const { return 0.0f; }
|
||||
float GetMaxInput() const { return GetSequenceLength(); }
|
||||
|
||||
void SetInputViewRange(float InViewMinInput, float InViewMaxInput);
|
||||
float GetScrubValue() const;
|
||||
void OnSelectionChanged(const TArray<UObject*>& SelectedItems);
|
||||
|
||||
// FGCObject
|
||||
virtual void AddReferencedObjects(FReferenceCollector& Collector) override {}
|
||||
virtual FString GetReferencerName() const override { return TEXT("FunnyGirlVendoredSAnimEditorBase"); }
|
||||
|
||||
virtual TSharedRef<SWidget> CreateDocumentAnchor();
|
||||
|
||||
protected:
|
||||
virtual TSharedRef<SWidget> CreateAnimTimelineWidget() = 0;
|
||||
virtual TSharedRef<SWidget> ConstructAnimScrubPanel(bool bDisplayAnimScrubBarEditing);
|
||||
virtual UAnimationAsset* GetEditorObject() const = 0;
|
||||
float GetSequenceLength() const;
|
||||
|
||||
TSharedRef<IPersonaPreviewScene> GetPreviewScene() const { return PreviewScenePtr.Pin().ToSharedRef(); }
|
||||
|
||||
protected:
|
||||
TWeakPtr<IPersonaPreviewScene> PreviewScenePtr;
|
||||
TSharedPtr<SVerticalBox> EditorPanels;
|
||||
TSharedPtr<SVerticalBox> NonScrollEditorPanels;
|
||||
FOnObjectsSelected OnObjectsSelectedDelegate;
|
||||
float ViewMinInput = 0.0f;
|
||||
float ViewMaxInput = 0.0f;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "LipSyncMappingAssetFactory.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class FUNNYGIRLEDITOR_API ULipSyncMappingAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
ULipSyncMappingAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FString GetDefaultNewAssetName() const override;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FLipSyncMappingAssetTypeActions : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
|
||||
class IDetailsView;
|
||||
class SDockTab;
|
||||
class UMorphPresetDataAsset;
|
||||
|
||||
class FMorphPresetEditorController;
|
||||
class SMorphPresetTimelinePanel;
|
||||
class SMorphPresetViewportPanel;
|
||||
|
||||
class FUNNYGIRLEDITOR_API FMorphPresetAssetEditor : public FAssetEditorToolkit
|
||||
{
|
||||
public:
|
||||
static const FName EditorName;
|
||||
static const FName ViewportTabId;
|
||||
static const FName TimelineTabId;
|
||||
static const FName DetailsTabId;
|
||||
|
||||
void InitMorphPresetAssetEditor(const EToolkitMode::Type Mode, const TSharedPtr<class IToolkitHost>& InitToolkitHost, UMorphPresetDataAsset* InAsset);
|
||||
|
||||
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
|
||||
virtual FName GetToolkitFName() const override;
|
||||
virtual FText GetBaseToolkitName() const override;
|
||||
virtual FString GetWorldCentricTabPrefix() const override;
|
||||
virtual FLinearColor GetWorldCentricTabColorScale() const override;
|
||||
|
||||
UMorphPresetDataAsset* GetPreset() const { return Preset.Get(); }
|
||||
|
||||
private:
|
||||
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_Timeline(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
|
||||
void HandleDetailsChanged(const struct FPropertyChangedEvent& PropertyChangedEvent);
|
||||
void HandlePresetChanged();
|
||||
|
||||
private:
|
||||
TWeakObjectPtr<UMorphPresetDataAsset> Preset;
|
||||
TSharedPtr<FMorphPresetEditorController> EditorController;
|
||||
TSharedPtr<SMorphPresetViewportPanel> ViewportWidget;
|
||||
TSharedPtr<SMorphPresetTimelinePanel> TimelineWidget;
|
||||
TSharedPtr<IDetailsView> DetailsView;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "MorphPresetAssetFactory.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class FUNNYGIRLEDITOR_API UMorphPresetAssetFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UMorphPresetAssetFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual FString GetDefaultNewAssetName() const override;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
|
||||
class FMorphPresetAssetTypeActions : public FAssetTypeActions_Base
|
||||
{
|
||||
public:
|
||||
virtual FText GetName() const override;
|
||||
virtual FColor GetTypeColor() const override;
|
||||
virtual UClass* GetSupportedClass() const override;
|
||||
virtual uint32 GetCategories() override;
|
||||
virtual FText GetAssetDescription(const FAssetData& AssetData) const override;
|
||||
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IDetailCustomization.h"
|
||||
|
||||
class IDetailLayoutBuilder;
|
||||
class IPropertyUtilities;
|
||||
class SMultiLineEditableTextBox;
|
||||
class UMorphPresetDataAsset;
|
||||
|
||||
class FUNNYGIRLEDITOR_API FMorphPresetDetailsCustomization : public IDetailCustomization
|
||||
{
|
||||
public:
|
||||
static TSharedRef<IDetailCustomization> MakeInstance();
|
||||
|
||||
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
|
||||
|
||||
private:
|
||||
void RefreshJsonFromPreset();
|
||||
void ApplyJsonToPreset(const FString& JsonString);
|
||||
FText GetStatusText() const;
|
||||
FSlateColor GetStatusColor() const;
|
||||
FReply OnApplyJsonClicked();
|
||||
FReply OnResetJsonClicked();
|
||||
void HandleJsonTextChanged(const FText& NewText);
|
||||
|
||||
private:
|
||||
TWeakObjectPtr<UMorphPresetDataAsset> Preset;
|
||||
TSharedPtr<SMultiLineEditableTextBox> JsonTextBox;
|
||||
TSharedPtr<IPropertyUtilities> PropertyUtilities;
|
||||
FString CachedJsonText;
|
||||
FText StatusText;
|
||||
bool bIsUpdatingJsonText = false;
|
||||
bool bLastApplySucceeded = true;
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
class FUICommandList;
|
||||
class AActor;
|
||||
class IPersonaPreviewScene;
|
||||
class IPersonaToolkit;
|
||||
class UAnimationAsset;
|
||||
class UAnimSequence;
|
||||
class ULipSyncComponent;
|
||||
class ULipSyncMappingDataAsset;
|
||||
class UMorphControllerComponent;
|
||||
class UMorphPresetDataAsset;
|
||||
class USoundBase;
|
||||
class USkeletalMesh;
|
||||
|
||||
class FUNNYGIRLEDITOR_API FMorphPresetEditorController : public TSharedFromThis<FMorphPresetEditorController>
|
||||
{
|
||||
public:
|
||||
explicit FMorphPresetEditorController(UMorphPresetDataAsset* InPreset);
|
||||
~FMorphPresetEditorController();
|
||||
|
||||
void Initialize();
|
||||
|
||||
UMorphPresetDataAsset* GetPreset() const;
|
||||
USkeletalMesh* GetPreviewMesh() const;
|
||||
bool SetPreviewMesh(USkeletalMesh* InPreviewMesh);
|
||||
|
||||
TSharedPtr<IPersonaToolkit> GetPersonaToolkit() const { return PersonaToolkit; }
|
||||
TSharedPtr<IPersonaPreviewScene> GetPreviewScene() const;
|
||||
TSharedRef<FUICommandList> GetCommandList() const { return CommandList.ToSharedRef(); }
|
||||
|
||||
float GetTimelineLength() const;
|
||||
float GetCurrentTime() const;
|
||||
FText GetPreviewStatusText() const { return PreviewStatusText; }
|
||||
void SetCurrentTime(float InTime);
|
||||
bool TryGetHeadLocation(FVector& OutHeadLocation) const;
|
||||
bool ResolvePreviewBoneName(FName InBoneName, FName& OutBoneName) const;
|
||||
void NotifyPresetEdited(bool bBroadcastPresetChanged = true);
|
||||
|
||||
bool IsPlaying() const { return bIsPlaying; }
|
||||
void SetPlaying(bool bInPlaying);
|
||||
void TogglePlaying();
|
||||
void Tick(float DeltaTime);
|
||||
|
||||
USoundBase* GetLipSyncPreviewSound() const;
|
||||
bool SetLipSyncPreviewSound(USoundBase* InSound);
|
||||
ULipSyncMappingDataAsset* GetLipSyncPreviewMapping() const;
|
||||
bool SetLipSyncPreviewMapping(ULipSyncMappingDataAsset* InMapping);
|
||||
float GetLipSyncPreviewSmoothness() const { return LipSyncPreviewSmoothness; }
|
||||
void SetLipSyncPreviewSmoothness(float InSmoothness);
|
||||
FText GetLipSyncPreviewTranscript() const { return LipSyncPreviewTranscript; }
|
||||
void SetLipSyncPreviewTranscript(const FText& InTranscript);
|
||||
bool PlayLipSyncPreviewAudio(float StartTime = 0.0f);
|
||||
void StopLipSyncPreviewAudio();
|
||||
bool IsLipSyncPreviewAudioPlaying() const;
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE(FOnChanged);
|
||||
FOnChanged& OnPreviewSceneChanged() { return PreviewSceneChanged; }
|
||||
FOnChanged& OnPlaybackStateChanged() { return PlaybackStateChanged; }
|
||||
FOnChanged& OnPresetChanged() { return PresetChanged; }
|
||||
|
||||
private:
|
||||
void RebuildPreviewScene();
|
||||
void RebuildPreviewAnimation();
|
||||
void ApplyPresetToPreview();
|
||||
void RefreshPreviewAnimationPose();
|
||||
UAnimationAsset* ResolvePreviewAnimationAsset() const;
|
||||
float GetPreviewAnimationPosition() const;
|
||||
bool IsPreviewAnimationCompatible(UAnimationAsset* PreviewAsset) const;
|
||||
void RebuildLipSyncPreviewComponents();
|
||||
void DestroyLipSyncPreviewComponents();
|
||||
void TickLipSyncPreviewComponents(float DeltaTime);
|
||||
ULipSyncMappingDataAsset* ResolveLipSyncPreviewMapping() const;
|
||||
|
||||
private:
|
||||
TWeakObjectPtr<UMorphPresetDataAsset> Preset;
|
||||
TSharedPtr<IPersonaToolkit> PersonaToolkit;
|
||||
TSharedPtr<FUICommandList> CommandList;
|
||||
TStrongObjectPtr<UAnimSequence> PreviewAnimation;
|
||||
TStrongObjectPtr<ULipSyncMappingDataAsset> DefaultLipSyncPreviewMapping;
|
||||
TWeakObjectPtr<ULipSyncMappingDataAsset> LipSyncPreviewMapping;
|
||||
TWeakObjectPtr<USoundBase> LipSyncPreviewSound;
|
||||
TWeakObjectPtr<AActor> LipSyncPreviewActor;
|
||||
TWeakObjectPtr<UMorphControllerComponent> LipSyncPreviewMorphController;
|
||||
TWeakObjectPtr<ULipSyncComponent> LipSyncPreviewComponent;
|
||||
bool bIsPlaying = false;
|
||||
bool bLipSyncPreviewAudioActive = false;
|
||||
float CurrentTime = 0.0f;
|
||||
float LipSyncPreviewSmoothness = 0.55f;
|
||||
FText LipSyncPreviewTranscript;
|
||||
FText PreviewStatusText;
|
||||
FOnChanged PreviewSceneChanged;
|
||||
FOnChanged PlaybackStateChanged;
|
||||
FOnChanged PresetChanged;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class UMorphPresetDataAsset;
|
||||
|
||||
/**
|
||||
* Editor Window для импорта морф таргетов из JSON в Morph Preset.
|
||||
* Режим Embedded: передай Preset — пикер скрыт, редактор привязан к ассету.
|
||||
* Режим Standalone: Preset = nullptr — показывается пикер (Tools -> Morph Preset Editor).
|
||||
*/
|
||||
class FUNNYGIRLEDITOR_API SMorphPresetEditorWindow : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMorphPresetEditorWindow) {}
|
||||
SLATE_ARGUMENT(UMorphPresetDataAsset*, Preset)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
/** Только в embedded режиме (Preset задан): обновить JSON из ассета. */
|
||||
void RefreshFromPreset();
|
||||
|
||||
private:
|
||||
TSharedRef<SWidget> BuildContent();
|
||||
|
||||
/** true = внутри окна ассета (preset передан), false = отдельное окно с пикером */
|
||||
bool bEmbeddedMode = false;
|
||||
FReply OnApplyClicked();
|
||||
FReply OnExportClicked();
|
||||
void OnPresetChanged(const FAssetData& AssetData);
|
||||
FString GetSelectedPresetPath() const;
|
||||
|
||||
TSharedPtr<class SObjectPropertyEntryBox> PresetPicker;
|
||||
TSharedPtr<class SMultiLineEditableTextBox> JsonTextBox;
|
||||
TWeakObjectPtr<UMorphPresetDataAsset> SelectedPreset;
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ITransportControl.h"
|
||||
#include "Input/Events.h"
|
||||
#include "Input/Reply.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class FMorphPresetEditorController;
|
||||
template<typename ItemType> class SListView;
|
||||
|
||||
class FUNNYGIRLEDITOR_API SMorphPresetTimelinePanel : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMorphPresetTimelinePanel) {}
|
||||
SLATE_ARGUMENT(TSharedPtr<FMorphPresetEditorController>, Controller)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual ~SMorphPresetTimelinePanel() override;
|
||||
void Refresh();
|
||||
virtual bool SupportsKeyboardFocus() const override { return true; }
|
||||
virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
|
||||
|
||||
bool HasSelectedClips() const;
|
||||
int32 GetSelectedClipCount() const;
|
||||
bool IsClipSelected(int32 ClipIndex) const;
|
||||
void ToggleClipSelection(int32 ClipIndex);
|
||||
void SelectOnlyClip(int32 ClipIndex);
|
||||
void ClearClipSelection();
|
||||
void SelectAllClips();
|
||||
void BeginMoveSelectedClips(int32 PrimaryClipIndex);
|
||||
void ApplyMoveSelectedClips(float DeltaTime);
|
||||
void EndMoveSelectedClips();
|
||||
void DeleteSelectedClips();
|
||||
TSharedRef<SWidget> BuildClipInspectorWidget(int32 ClipIndex);
|
||||
void RegisterTimelineScrollBox(TSharedPtr<class SScrollBox> InTimelineScrollBox);
|
||||
|
||||
private:
|
||||
void RefreshAvailableMorphs();
|
||||
FReply OnPlayPauseClicked();
|
||||
FReply OnStopClicked();
|
||||
FReply OnAddMorphClicked();
|
||||
FReply OnDeleteSelectedClicked();
|
||||
EPlaybackMode::Type GetPlaybackMode() const;
|
||||
void OnLoopCheckStateChanged(ECheckBoxState NewState);
|
||||
ECheckBoxState GetLoopCheckState() const;
|
||||
void HandleTimeChanged(float InNewValue);
|
||||
TSharedRef<SWidget> BuildTimelineWidget();
|
||||
TSharedRef<SWidget> BuildMorphLibraryWidget();
|
||||
TSharedRef<class ITableRow> GenerateMorphRow(TSharedPtr<FString> Item, const TSharedRef<class STableViewBase>& OwnerTable) const;
|
||||
void HandleMorphSelectionChanged(TSharedPtr<FString> Item, ESelectInfo::Type SelectInfo);
|
||||
void HandleMorphDoubleClicked(TSharedPtr<FString> Item);
|
||||
bool CanAddSelectedMorph() const;
|
||||
struct FMorphTimelineClip* GetMutableClipByIndex(int32 ClipIndex) const;
|
||||
const struct FMorphTimelineClip* GetClipByIndex(int32 ClipIndex) const;
|
||||
int32 GetPrimarySelectedClipIndex() const;
|
||||
void NotifyClipChanged(bool bRefreshTimeline = false);
|
||||
void HandleClipWeightChanged(float NewValue);
|
||||
void HandleClipStartTimeChanged(float NewValue);
|
||||
void HandleClipDurationChanged(float NewValue);
|
||||
EActiveTimerReturnType HandleActiveTimer(double InCurrentTime, float InDeltaTime);
|
||||
FText GetPlayPauseText() const;
|
||||
float GetCurrentTime() const;
|
||||
TOptional<float> GetTimelineMaxTime() const;
|
||||
void HandleControllerChanged();
|
||||
|
||||
private:
|
||||
TSharedPtr<FMorphPresetEditorController> Controller;
|
||||
TSharedPtr<class SBox> TimelineContainer;
|
||||
TSharedPtr<class SScrollBox> TimelineScrollBox;
|
||||
TArray<TSharedPtr<FString>> AvailableMorphItems;
|
||||
TSharedPtr<FString> SelectedMorphItem;
|
||||
TSharedPtr<SListView<TSharedPtr<FString>>> MorphListView;
|
||||
TSet<int32> SelectedClipIndices;
|
||||
TMap<int32, float> DragInitialClipStartTimes;
|
||||
int32 DragPrimaryClipIndex = INDEX_NONE;
|
||||
float TimelineScrollOffset = 0.0f;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class FMorphPresetEditorController;
|
||||
class ULipSyncMappingDataAsset;
|
||||
class USoundBase;
|
||||
class USkeletalMesh;
|
||||
|
||||
class FUNNYGIRLEDITOR_API SMorphPresetViewportPanel : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SMorphPresetViewportPanel) {}
|
||||
SLATE_ARGUMENT(TSharedPtr<FMorphPresetEditorController>, Controller)
|
||||
SLATE_ARGUMENT(TSharedPtr<class FAssetEditorToolkit>, AssetEditorToolkit)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
virtual ~SMorphPresetViewportPanel() override;
|
||||
|
||||
private:
|
||||
void RebuildContent();
|
||||
void OnPreviewMeshChanged(const struct FAssetData& AssetData);
|
||||
void OnLipSyncSoundChanged(const struct FAssetData& AssetData);
|
||||
void OnLipSyncMappingChanged(const struct FAssetData& AssetData);
|
||||
FString GetSelectedPreviewMeshPath() const;
|
||||
FString GetSelectedLipSyncSoundPath() const;
|
||||
FString GetSelectedLipSyncMappingPath() const;
|
||||
FReply OnPlayLipSyncClicked();
|
||||
FReply OnStopLipSyncClicked();
|
||||
FText GetLipSyncPlaybackText() const;
|
||||
float GetLipSyncSmoothness() const;
|
||||
void OnLipSyncSmoothnessChanged(float NewValue);
|
||||
FText GetLipSyncTranscript() const;
|
||||
void OnLipSyncTranscriptChanged(const FText& NewText);
|
||||
FText GetPreviewStatusText() const;
|
||||
EVisibility GetPreviewStatusVisibility() const;
|
||||
EActiveTimerReturnType HandleActiveTimer(double InCurrentTime, float InDeltaTime);
|
||||
|
||||
private:
|
||||
TSharedPtr<FMorphPresetEditorController> Controller;
|
||||
TWeakPtr<class FAssetEditorToolkit> AssetEditorToolkit;
|
||||
TSharedPtr<class SBorder> RootBorder;
|
||||
};
|
||||
Reference in New Issue
Block a user