// Copyright Epic Games, Inc. All Rights Reserved. #include "PersonaAssetFamily.h" #include "Modules/ModuleManager.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Animation/AnimBlueprint.h" #include "PhysicsEngine/PhysicsAsset.h" #include "Styling/AppStyle.h" #include "AssetToolsModule.h" #include "Preferences/PersonaOptions.h" #include "UObject/UObjectIterator.h" #define LOCTEXT_NAMESPACE "PersonaAssetFamily" FPersonaAssetFamily::FPersonaAssetFamily(const UObject* InFromObject) { for (TObjectIterator ExtensionIterator(RF_NoFlags); ExtensionIterator; ++ExtensionIterator) { if (ExtensionIterator->GetClass() == UAnimationEditorsAssetFamilyExtension::StaticClass()) { continue; } FExtenderObjects& Extender = Extenders.AddDefaulted_GetRef(); Extender.Extension = *ExtensionIterator; Extender.Asset = nullptr; } // Construct a directed graph to sort extenders according to position rules { TMap> Graph; TMap InDegree; TMap ExtenderMap; for (FExtenderObjects& Extender : Extenders) { const FName AssetClassName = Extender.Extension->GetAssetClass()->GetFName(); ExtenderMap.Add(AssetClassName, &Extender); InDegree.Add(AssetClassName, 0); Graph.Add(AssetClassName, TArray()); } for (FExtenderObjects& Extender : Extenders) { const FName AssetClassName = Extender.Extension->GetAssetClass()->GetFName(); FName BeforeName = NAME_None; FName AfterName = NAME_None; Extender.Extension->GetPosition(BeforeName, AfterName); if (BeforeName != NAME_None && Graph.Contains(AssetClassName) && InDegree.Contains(BeforeName)) { Graph[AssetClassName].Add(BeforeName); ++InDegree[BeforeName]; } if (AfterName != NAME_None && Graph.Contains(AfterName) && InDegree.Contains(AssetClassName)) { Graph[AfterName].Add(AssetClassName); ++InDegree[AssetClassName]; } } TQueue Queue; for (const auto& [ClassName, Degree] : InDegree) { if (Degree == 0) { Queue.Enqueue(ClassName); } } TArray SortedExtenders; while (!Queue.IsEmpty()) { FName Current = NAME_None; Queue.Dequeue(Current); SortedExtenders.Add(*ExtenderMap[Current]); for (const auto& Neighbour : Graph[Current]) { --InDegree[Neighbour]; if (InDegree[Neighbour] == 0) { Queue.Enqueue(Neighbour); } } } if (SortedExtenders.Num() != Extenders.Num()) { UE_LOG(LogAnimation, Warning, TEXT("Unable to sort animation editor extenders")); } else { Extenders = SortedExtenders; } } if (InFromObject) { for (FExtenderObjects& Extender : Extenders) { if (InFromObject->IsA(Extender.Extension->GetAssetClass())) { Extender.Asset = InFromObject; Extender.Extension->FindCounterpartAssets(InFromObject, *this); } } } } FPersonaAssetFamily::FPersonaAssetFamily(const UObject* InFromObject, const TSharedRef InFromFamily) : Extenders(InFromFamily->Extenders) { if (InFromObject) { for (FExtenderObjects& Extender : Extenders) { if (InFromObject->IsA(Extender.Extension->GetAssetClass())) { Extender.Asset = InFromObject; Extender.Extension->FindCounterpartAssets(InFromObject, *this); } } } } void FPersonaAssetFamily::Initialize() { GetMutableDefault()->RegisterOnUpdateSettings(UPersonaOptions::FOnUpdateSettingsMulticaster::FDelegate::CreateSP(this, &FPersonaAssetFamily::OnSettingsChange)); } void FPersonaAssetFamily::GetAssetTypes(TArray& OutAssetTypes) const { OutAssetTypes.Reset(); for (const FExtenderObjects& Extender : Extenders) { OutAssetTypes.Add(Extender.Extension->GetAssetClass()); } } template static void FindAssets(const USkeleton* InSkeleton, TArray& OutAssetData, FName SkeletonTag) { if (!InSkeleton) { return; } InSkeleton->GetCompatibleAssets(AssetType::StaticClass(), *SkeletonTag.ToString(), OutAssetData); } FAssetData FPersonaAssetFamily::FindAssetOfType(UClass* InAssetClass) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass)) { if (Extender->Asset.IsValid()) { return FAssetData(Extender->Asset.Get()); } else { TArray Assets; Extender->Extension->FindAssetsOfType(Assets, *this); if (Assets.Num() > 0) { return Assets[0]; } } } return FAssetData(); } void FPersonaAssetFamily::FindAssetsOfType(UClass* InAssetClass, TArray& OutAssets) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass)) { Extender->Extension->FindAssetsOfType(OutAssets, *this); } } FText FPersonaAssetFamily::GetAssetTypeDisplayName(UClass* InAssetClass) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass)) { return Extender->Extension->GetAssetTypeDisplayName(); } return FText(); } const FSlateBrush* FPersonaAssetFamily::GetAssetTypeDisplayIcon(UClass* InAssetClass) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass)) { return Extender->Extension->GetAssetTypeDisplayIcon(); } return nullptr; } FSlateColor FPersonaAssetFamily::GetAssetTypeDisplayTint(UClass* InAssetClass) const { static const FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); if (InAssetClass) { if (const FExtenderObjects* const Extender = GetExtensionForClass(InAssetClass)) { TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Extender->Extension->GetAssetClass()); if (AssetTypeActions.IsValid()) { return AssetTypeActions.Pin()->GetTypeColor(); } } } return FSlateColor::UseForeground(); } bool FPersonaAssetFamily::IsAssetCompatible(const FAssetData& InAssetData) const { UClass* Class = InAssetData.GetClass(); if (const FExtenderObjects* const Extender = GetExtensionForClass(Class)) { return Extender->Extension->IsAssetCompatible(InAssetData, *this); } return false; } UClass* FPersonaAssetFamily::GetAssetFamilyClass(UClass* InClass) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InClass)) { return Extender->Extension->GetAssetClass(); } return nullptr; } void FPersonaAssetFamily::RecordAssetOpened(const FAssetData& InAssetData) { if (IsAssetCompatible(InAssetData)) { UClass* Class = InAssetData.GetClass(); if (Class) { if (FExtenderObjects* Extender = GetExtensionForClass(Class)) { Extender->Asset = InAssetData.GetAsset(); } } OnAssetOpened.Broadcast(InAssetData.GetAsset()); } } bool FPersonaAssetFamily::IsAssetTypeInFamily(const TObjectPtr InClass) const { return GetExtensionForClass(InClass) != nullptr; } TWeakObjectPtr FPersonaAssetFamily::GetAssetOfType(const TObjectPtr InClass) const { if (const FExtenderObjects* const Extender = GetExtensionForClass(InClass)) { return Extender->Asset; } return nullptr; } bool FPersonaAssetFamily::SetAssetOfType(const TObjectPtr InClass, TWeakObjectPtr InObject) { if (FExtenderObjects* Extender = GetExtensionForClass(InClass)) { Extender->Asset = InObject; return true; } return false; } void FPersonaAssetFamily::AddReferencedObjects(FReferenceCollector& Collector) { for (FExtenderObjects& Extender : Extenders) { Collector.AddReferencedObject(Extender.Extension); } } FString FPersonaAssetFamily::GetReferencerName() const { return TEXT("FPersonaAssetFamily"); } void FPersonaAssetFamily::OnSettingsChange(const UPersonaOptions* InOptions, EPropertyChangeType::Type InChangeType) { OnAssetFamilyChanged.Broadcast(); } FPersonaAssetFamily::FExtenderObjects* FPersonaAssetFamily::GetExtensionForClass(const TObjectPtr InClass) { if (InClass) { for (FExtenderObjects& Extender : Extenders) { if (InClass->IsChildOf(Extender.Extension->GetAssetClass())) { return &Extender; } } } return nullptr; } const FPersonaAssetFamily::FExtenderObjects* const FPersonaAssetFamily::GetExtensionForClass(const TObjectPtr InClass) const { if (InClass) { for (const FExtenderObjects& Extender : Extenders) { if (InClass->IsChildOf(Extender.Extension->GetAssetClass())) { return &Extender; } } } return nullptr; } // UAnimationEditorsAssetFamilyExtension_SkeletonAsset TObjectPtr UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetClass() const { return USkeleton::StaticClass(); } FText UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetTypeDisplayName() const { return LOCTEXT("SkeletonAssetDisplayName", "Skeleton"); } const FSlateBrush* UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetAssetTypeDisplayIcon() const { return FAppStyle::Get().GetBrush("Persona.AssetClass.Skeleton"); } void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::FindAssetsOfType(TArray& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { SkeletonAsset->GetCompatibleSkeletonAssets(OutAssets); } } bool UAnimationEditorsAssetFamilyExtension_SkeletonAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { return SkeletonAsset.Get()->IsCompatibleForEditor(InAssetData); } return false; } void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) { const USkeleton* SkeletonAsset = CastChecked(InAsset); AssetFamilyInterface.SetAssetOfType(SkeletonAsset->GetPreviewMesh()); }; void UAnimationEditorsAssetFamilyExtension_SkeletonAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const { OutBeforeClass = NAME_None; OutAfterClass = NAME_None; } // UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset TObjectPtr UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetClass() const { return USkeletalMesh::StaticClass(); } FText UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetTypeDisplayName() const { return LOCTEXT("SkeletalMeshAssetDisplayName", "Skeletal Mesh"); } const FSlateBrush* UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetAssetTypeDisplayIcon() const { return FAppStyle::Get().GetBrush("Persona.AssetClass.SkeletalMesh"); } void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::FindAssetsOfType(TArray& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { FindAssets(SkeletonAsset.Get(), OutAssets, "Skeleton"); } } bool UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("Skeleton"); if (Result.IsSet()) { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { return SkeletonAsset->IsCompatibleForEditor(Result.GetValue()); } } return false; } void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) { const USkeletalMesh* SkeletalMesh = CastChecked(InAsset); AssetFamilyInterface.SetAssetOfType(SkeletalMesh->GetSkeleton()); }; void UAnimationEditorsAssetFamilyExtension_SkeletalMeshAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const { OutBeforeClass = NAME_None; OutAfterClass = USkeleton::StaticClass()->GetFName(); } // UAnimationEditorsAssetFamilyExtension_AnimationAsset TObjectPtr UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetClass() const { return UAnimationAsset::StaticClass(); } FText UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetTypeDisplayName() const { return LOCTEXT("AnimationAssetDisplayName", "Animation"); } const FSlateBrush* UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetAssetTypeDisplayIcon() const { return FAppStyle::Get().GetBrush("Persona.AssetClass.Animation"); } void UAnimationEditorsAssetFamilyExtension_AnimationAsset::FindAssetsOfType(TArray& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { FindAssets(SkeletonAsset, OutAssets, "Skeleton"); } } bool UAnimationEditorsAssetFamilyExtension_AnimationAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("Skeleton"); if (Result.IsSet()) { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { return SkeletonAsset->IsCompatibleForEditor(Result.GetValue()); } } return false; } void UAnimationEditorsAssetFamilyExtension_AnimationAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) { const UAnimationAsset* AnimationAsset = CastChecked(InAsset); AssetFamilyInterface.SetAssetOfType(AnimationAsset->GetSkeleton()); AssetFamilyInterface.SetAssetOfType(AnimationAsset->GetPreviewMesh()); if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned()) { AssetFamilyInterface.SetAssetOfType(AnimationAsset->GetSkeleton()->GetPreviewMesh()); } if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned()) { AssetFamilyInterface.SetAssetOfType(AnimationAsset->GetSkeleton()->FindCompatibleMesh()); } }; void UAnimationEditorsAssetFamilyExtension_AnimationAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const { OutBeforeClass = NAME_None; OutAfterClass = USkeletalMesh::StaticClass()->GetFName();; } // UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset TObjectPtr UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetClass() const { return UAnimBlueprint::StaticClass(); } FText UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetTypeDisplayName() const { return LOCTEXT("AnimBlueprintAssetDisplayName", "Animation Blueprint"); } const FSlateBrush* UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetAssetTypeDisplayIcon() const { return FAppStyle::Get().GetBrush("Persona.AssetClass.Blueprint"); } void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::FindAssetsOfType(TArray& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { FindAssets(SkeletonAsset, OutAssets, "TargetSkeleton"); } } bool UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag("TargetSkeleton"); if (Result.IsSet()) { if (TObjectPtr SkeletonAsset = AssetFamilyInterface.GetAssetOfType()) { return SkeletonAsset->IsCompatibleForEditor(Result.GetValue()); } } return false; } void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) { const UAnimBlueprint* AnimBlueprint = CastChecked(InAsset); AssetFamilyInterface.SetAssetOfType(AnimBlueprint->TargetSkeleton); AssetFamilyInterface.SetAssetOfType(AnimBlueprint->GetPreviewMesh()); check(AnimBlueprint->BlueprintType == BPTYPE_Interface || AnimBlueprint->bIsTemplate || AnimBlueprint->TargetSkeleton != nullptr); if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned() && AnimBlueprint->TargetSkeleton) { AssetFamilyInterface.SetAssetOfType(AnimBlueprint->TargetSkeleton->GetPreviewMesh()); } if (AssetFamilyInterface.IsAssetTypeInFamilyAndUnassigned() && AnimBlueprint->TargetSkeleton) { AssetFamilyInterface.SetAssetOfType(AnimBlueprint->TargetSkeleton->FindCompatibleMesh()); } }; void UAnimationEditorsAssetFamilyExtension_AnimBlueprintAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const { OutBeforeClass = NAME_None; OutAfterClass = UAnimationAsset::StaticClass()->GetFName();; } // UAnimationEditorsAssetFamilyExtension_PhysicsAsset TObjectPtr UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetClass() const { return UPhysicsAsset::StaticClass(); } FText UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetTypeDisplayName() const { return LOCTEXT("PhysicsAssetDisplayName", "Physics"); } const FSlateBrush* UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetAssetTypeDisplayIcon() const { return FAppStyle::Get().GetBrush("Persona.AssetClass.Physics"); } void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::FindAssetsOfType(TArray& OutAssets, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); FARFilter Filter; Filter.bRecursiveClasses = true; Filter.ClassPaths.Add(UPhysicsAsset::StaticClass()->GetClassPathName()); // If we have a mesh, look for a physics asset that has that mesh as its preview mesh if (TObjectPtr SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType()) { Filter.TagsAndValues.Add(GET_MEMBER_NAME_CHECKED(UPhysicsAsset, PreviewSkeletalMesh), FSoftObjectPath(SkeletalMeshAsset).ToString()); } AssetRegistryModule.Get().GetAssets(Filter, OutAssets); // If we have a mesh and it has a physics asset, use it but only if its different from the one on the preview mesh if (TObjectPtr SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType()) { if (UPhysicsAsset* MeshPhysicsAsset = SkeletalMeshAsset->GetPhysicsAsset()) { OutAssets.AddUnique(FAssetData(MeshPhysicsAsset)); } } } bool UAnimationEditorsAssetFamilyExtension_PhysicsAsset::IsAssetCompatible(const FAssetData& InAssetData, const IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) const { // If our mesh is valid and this is the physics asset used on it, we are compatible TObjectPtr SkeletalMeshAsset = AssetFamilyInterface.GetAssetOfType(); if (SkeletalMeshAsset && InAssetData.GetSoftObjectPath() == FSoftObjectPath(SkeletalMeshAsset->GetPhysicsAsset())) { return true; } // Otherwise check if our mesh is the preview mesh of the physics asset FAssetDataTagMapSharedView::FFindTagResult Result = InAssetData.TagsAndValues.FindTag(GET_MEMBER_NAME_CHECKED(UPhysicsAsset, PreviewSkeletalMesh)); if (Result.IsSet() && SkeletalMeshAsset) { return Result.GetValue() == FSoftObjectPath(SkeletalMeshAsset).ToString(); } return false; } void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::FindCounterpartAssets(const UObject* InAsset, IAnimationEditorsAssetFamilyInterface& AssetFamilyInterface) { const UPhysicsAsset* PhysicsAsset = CastChecked(InAsset); TObjectPtr SkeletalMesh = PhysicsAsset->PreviewSkeletalMesh.LoadSynchronous(); if (SkeletalMesh) { AssetFamilyInterface.SetAssetOfType(SkeletalMesh); AssetFamilyInterface.SetAssetOfType(SkeletalMesh->GetSkeleton()); } }; void UAnimationEditorsAssetFamilyExtension_PhysicsAsset::GetPosition(FName& OutBeforeClass, FName& OutAfterClass) const { OutBeforeClass = NAME_None; OutAfterClass = UAnimBlueprint::StaticClass()->GetFName();; } #undef LOCTEXT_NAMESPACE