Files
UnrealEngine/Engine/Plugins/MetaHuman/MetaHumanCharacter/Source/MetaHumanCharacterPalette/Private/MetaHumanCharacterPipelineSpecification.cpp
2025-05-18 13:04:45 +08:00

179 lines
4.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetaHumanCharacterPipelineSpecification.h"
#include "AssetRegistry/AssetData.h"
const FName UE::MetaHuman::CharacterPipelineSlots::Character = "Character";
namespace
{
bool SlotSupportsAssetType(const FMetaHumanCharacterPipelineSlot& Slot, TNotNull<const UClass*> AssetType)
{
for (const TSoftClassPtr<UObject>& SoftSupportedType : Slot.SupportedPrincipalAssetTypes)
{
const UClass* SupportedType = SoftSupportedType.LoadSynchronous();
if (SupportedType
&& AssetType->IsChildOf(SupportedType))
{
return true;
}
}
return false;
}
}
bool FMetaHumanCharacterPipelineSlot::IsVirtual() const
{
return TargetSlot != NAME_None;
}
bool FMetaHumanCharacterPipelineSlot::SupportsAsset(const FAssetData& Asset) const
{
const TSoftClassPtr<UObject> SoftAssetClass(FSoftObjectPath(Asset.AssetClassPath));
return SlotSupportsAssetType(*this, SoftAssetClass.LoadSynchronous());
}
bool FMetaHumanCharacterPipelineSlot::SupportsAssetType(TNotNull<const UClass*> AssetType) const
{
return SlotSupportsAssetType(*this, AssetType);
}
bool UMetaHumanCharacterPipelineSpecification::IsValid() const
{
// The set of slots that are known not to be part of any cycles in the virtual slot graph
TSet<FName> AcyclicSlots;
AcyclicSlots.Reserve(Slots.Num());
for (const TPair<FName, FMetaHumanCharacterPipelineSlot>& Slot : Slots)
{
if (Slot.Key == NAME_None)
{
// All slots must have non-empty names
return false;
}
if (!Slot.Value.bAllowsMultipleSelection)
{
// Ensure there isn't more than one virtual slot targeting this slot
bool bFoundVirtualSlot = false;
for (const TPair<FName, FMetaHumanCharacterPipelineSlot>& OtherSlot : Slots)
{
if (OtherSlot.Key == Slot.Key)
{
// Only process other slots
continue;
}
if (OtherSlot.Value.TargetSlot == Slot.Key)
{
if (bFoundVirtualSlot)
{
// Multiple slots target this slot, which doesn't allow multiple selections.
//
// This isn't currently supported.
return false;
}
// Found one virtual slot targeting this slot. Not a problem unless we find a second one.
bFoundVirtualSlot = true;
}
}
}
if (Slot.Value.TargetSlot != NAME_None)
{
const FMetaHumanCharacterPipelineSlot* FoundTargetSlot = Slots.Find(Slot.Value.TargetSlot);
if (!FoundTargetSlot)
{
return false;
}
// The target slot must be able to accept all valid selections made on this slot, otherwise
// this slot is invalid.
if (Slot.Value.bAllowsMultipleSelection && !FoundTargetSlot->bAllowsMultipleSelection)
{
// This slot allows multiple selection, but the target slot doesn't
return false;
}
for (const TSoftClassPtr<UObject>& SupportedType : Slot.Value.SupportedPrincipalAssetTypes)
{
if (!SlotSupportsAssetType(*FoundTargetSlot, SupportedType.LoadSynchronous()))
{
// This virtual slot claims to support an asset type that the target slot doesn't support
return false;
}
}
// Check for cycles in the virtual slot graph
{
TSet<FName, DefaultKeyFuncs<FName>, TInlineSetAllocator<8>> VisitedSlots;
FName CurrentSlotName = Slot.Key;
const FMetaHumanCharacterPipelineSlot* CurrentSlot = &Slot.Value;
while (CurrentSlot->IsVirtual())
{
if (AcyclicSlots.Contains(CurrentSlotName))
{
// Reached a known acyclic slot, so there are no cycles reachable from here
break;
}
if (VisitedSlots.Contains(CurrentSlotName))
{
// There is a cycle in the graph of virtual slots
return false;
}
VisitedSlots.Add(CurrentSlotName);
CurrentSlotName = CurrentSlot->TargetSlot;
CurrentSlot = Slots.Find(CurrentSlot->TargetSlot);
if (!CurrentSlot)
{
// Target doesn't exist
return false;
}
}
AcyclicSlots.Append(VisitedSlots);
}
}
}
return true;
}
TOptional<FName> UMetaHumanCharacterPipelineSpecification::ResolveRealSlotName(FName SlotName) const
{
FName CurrentSlotName = SlotName;
const FMetaHumanCharacterPipelineSlot* CurrentSlot;
// This loop should always terminate, because any cycles in the virtual slot graph will be
// detected by UMetaHumanCharacterPipelineSpecification::IsValid, which should be called
// before this function.
while (true)
{
CurrentSlot = Slots.Find(CurrentSlotName);
if (!CurrentSlot)
{
// Slot couldn't be found by name
return TOptional<FName>();
}
if (!CurrentSlot->IsVirtual())
{
// We've reached the end of the chain and CurrentSlotName is now set to a real slot
return CurrentSlotName;
}
CurrentSlotName = CurrentSlot->TargetSlot;
}
}