Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Pipelines/Public/InterchangePipelineMeshesUtilities.h
2025-05-18 13:04:45 +08:00

514 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "InterchangeMaterialFactoryNode.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "Nodes/InterchangeFactoryBaseNode.h"
#include "InterchangePipelineMeshesUtilities.generated.h"
#define UE_API INTERCHANGEPIPELINES_API
class UInterchangeBaseNodeContainer;
class UInterchangeMeshNode;
class UInterchangeSceneNode;
/*
* This container exists only because UPROPERTY cannot support nested container. See FInterchangeMeshInstance.
*/
USTRUCT(BlueprintType)
struct FInterchangeLodSceneNodeContainer
{
GENERATED_BODY()
/**
* Each scene node here represents a mesh scene node. If it represents a LOD group, there may be more then one mesh scene node for a specific LOD index.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
TArray<TObjectPtr<const UInterchangeSceneNode>> SceneNodes;
};
/*
* A mesh instance is a description of a translated scene node that points to a translated mesh asset.
* A mesh instance that points to an LOD group can have many LODs and many scene mesh nodes per LOD index.
* A mesh instance that points to a mesh node will have only LOD 0 and will point to one scene mesh node.
*/
USTRUCT(BlueprintType)
struct FInterchangeMeshInstance
{
GENERATED_BODY()
FInterchangeMeshInstance()
{
LodGroupNode = nullptr;
bReferenceSkinnedMesh = false;
bReferenceMorphTarget = false;
bHasMorphTargets = false;
bIsAnimated = false;
}
/**
* This ID represents either a LOD group scene node UID or a mesh scene node UID.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
FString MeshInstanceUid;
/**
* This member is null unless the mesh instance represents a LOD group.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
TObjectPtr<const UInterchangeSceneNode> LodGroupNode;
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
bool bReferenceSkinnedMesh;
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
bool bReferenceMorphTarget;
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
bool bHasMorphTargets;
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
bool bIsAnimated;
/**
* Each scene node here represents a mesh scene node. If it represents a LOD group, there may be more then one mesh scene node for a specific LOD index.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesInstance")
TMap<int32, FInterchangeLodSceneNodeContainer> SceneNodePerLodIndex;
/**
* All mesh geometry referenced by this MeshInstance.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesGeometry")
TArray<FString> ReferencingMeshGeometryUids;
};
/*
* A mesh geometry is a description of a translated mesh asset node that defines a geometry.
*/
USTRUCT(BlueprintType)
struct FInterchangeMeshGeometry
{
GENERATED_BODY()
FInterchangeMeshGeometry()
{
MeshNode = nullptr;
}
/**
* The unique ID of the UInterchangeMeshNode represented by this structure.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesGeometry")
FString MeshUid;
/**
* The UInterchangeMeshNode pointer represented by this structure.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesGeometry")
TObjectPtr<const UInterchangeMeshNode> MeshNode = nullptr;
/**
* All mesh instances that refer to this UInterchangeMeshNode pointer.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesGeometry")
TArray<FString> ReferencingMeshInstanceUids;
/**
* A list of all scene nodes that represent sockets attached to this mesh.
*/
UPROPERTY(EditAnywhere, Category = "Interchange | Pipeline | MeshesGeometry")
TArray<FString> AttachedSocketUids;
};
/*
* Represents the context UInterchangePipelineMeshesUtilities will use when the client queries data.
*/
USTRUCT(BlueprintType)
struct FInterchangePipelineMeshesUtilitiesContext
{
GENERATED_BODY()
/**
* If enabled, all static meshes are converted to skeletal meshes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bConvertStaticMeshToSkeletalMesh = false;
/**
* If enabled, all skeletal meshes are converted to static meshes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bConvertSkeletalMeshToStaticMesh = false;
/**
* If enabled, all static meshes that have morph targets will be imported as skeletal meshes instead.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext", meta = (DisplayName = "Convert Static Meshes with Morph Targets to Skeletal Meshes"))
bool bConvertStaticsWithMorphTargetsToSkeletals = false;
/**
* If enabled, meshes nested in bone hierarchies are imported as meshes instead of being converted to bones. If the meshes are not skinned, they are
* added to the skeletal mesh and removed from the list of static meshes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bImportMeshesInBoneHierarchy = true;
/**
* When querying geometry, this flag will not add MeshGeometry if there is a scene node that points to a geometry.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bQueryGeometryOnlyIfNoInstance = true;
/**
* If enabled, all static meshes will be ignored. The mesh utility will not return any static meshes.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bIgnoreStaticMeshes = false;
/**
* If enabled, all geometry caches will be ignored. The mesh utility will not return any geometry caches.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interchange | Pipeline | MeshesContext")
bool bIgnoreGeometryCaches = false;
bool IsStaticMeshInstance(const FInterchangeMeshInstance& MeshInstance, UInterchangeBaseNodeContainer* BaseNodeContainer);
bool IsSkeletalMeshInstance(const FInterchangeMeshInstance& MeshInstance, UInterchangeBaseNodeContainer* BaseNodeContainer);
bool IsSkeletalMeshInstance(const FInterchangeMeshInstance& MeshInstance, UInterchangeBaseNodeContainer* BaseNodeContainer, bool& bOutIsStaticMeshNestedInSkeleton);
bool IsGeometryCacheInstance(const FInterchangeMeshInstance& MeshInstance);
bool IsStaticMeshGeometry(const FInterchangeMeshGeometry& MeshGeometry);
bool IsSkeletalMeshGeometry(const FInterchangeMeshGeometry& MeshGeometry);
bool IsGeometryCacheGeometry(const FInterchangeMeshGeometry& MeshGeometry);
};
/**/
UCLASS(MinimalAPI, BlueprintType)
class UInterchangePipelineMeshesUtilities : public UObject
{
GENERATED_BODY()
public:
/**
* Create an instance of UInterchangePipelineMeshesUtilities.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
static UE_API UInterchangePipelineMeshesUtilities* CreateInterchangePipelineMeshesUtilities(UInterchangeBaseNodeContainer* BaseNodeContainer);
/**
* Get the unique IDs of all mesh instances.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllMeshInstanceUids(TArray<FString>& MeshInstanceUids) const;
/**
* Iterate over all mesh instances.
*/
UE_API void IterateAllMeshInstance(TFunctionRef<void(const FInterchangeMeshInstance&)> IterationLambda) const;
/**
* Get the unique IDs of all skinned mesh instances.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllSkinnedMeshInstance(TArray<FString>& MeshInstanceUids) const;
/**
* Iterate over all skinned mesh instances.
*/
UE_API void IterateAllSkinnedMeshInstance(TFunctionRef<void(const FInterchangeMeshInstance&)> IterationLambda) const;
/**
* Get the unique IDs of all static mesh instances.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllStaticMeshInstance(TArray<FString>& MeshInstanceUids) const;
/**
* Iterate over all static mesh instances.
*/
UE_API void IterateAllStaticMeshInstance(TFunctionRef<void(const FInterchangeMeshInstance&)> IterationLambda) const;
/**
* Get the unique IDs of all geometry cache instances.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllGeometryCacheInstance(TArray<FString>& MeshInstanceUids) const;
/**
* Iterate over all geometry cache instances.
*/
UE_API void IterateAllGeometryCacheInstance(TFunctionRef<void(const FInterchangeMeshInstance&)> IterationLambda) const;
/**
* Get the unique IDs of all mesh geometry.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllMeshGeometry(TArray<FString>& MeshGeometryUids) const;
/**
* Iterate over all mesh geometry.
*/
UE_API void IterateAllMeshGeometry(TFunctionRef<void(const FInterchangeMeshGeometry&)> IterationLambda) const;
/**
* Get the unique IDs of all skinned mesh geometry.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllSkinnedMeshGeometry(TArray<FString>& MeshGeometryUids) const;
/**
* Iterate over all skinned mesh geometry.
*/
UE_API void IterateAllSkinnedMeshGeometry(TFunctionRef<void(const FInterchangeMeshGeometry&)> IterationLambda) const;
/**
* Get the unique IDs of all static mesh geometry.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllStaticMeshGeometry(TArray<FString>& MeshGeometryUids) const;
/**
* Iterate over all static mesh geometry.
*/
UE_API void IterateAllStaticMeshGeometry(TFunctionRef<void(const FInterchangeMeshGeometry&)> IterationLambda) const;
/**
* Get the unique IDs of all geometry cache geometry.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllGeometryCacheGeometry(TArray<FString>& MeshGeometryUids) const;
/**
* Iterate over all geometry cache geometry.
*/
UE_API void IterateAllGeometryCacheGeometry(TFunctionRef<void(const FInterchangeMeshGeometry&)> IterationLambda) const;
/**
* Get the unique IDs of all non-instanced mesh geometry.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllMeshGeometryNotInstanced(TArray<FString>& MeshGeometryUids) const;
/**
* Iterate over all non-instanced mesh geometry.
*/
UE_API void IterateAllMeshGeometryNotIntanced(TFunctionRef<void(const FInterchangeMeshGeometry&)> IterationLambda) const;
/**
* Return true if there is an existing FInterchangeMeshInstance that matches the MeshInstanceUid key.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API bool IsValidMeshInstanceUid(const FString& MeshInstanceUid) const;
/**
* Get the instanced mesh from the unique ID.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API const FInterchangeMeshInstance& GetMeshInstanceByUid(const FString& MeshInstanceUid) const;
/**
* Return true if there is an existing FInterchangeMeshGeometry that matches the MeshInstanceUid key.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API bool IsValidMeshGeometryUid(const FString& MeshGeometryUid) const;
/**
* Get the geometry mesh from the unique ID.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API const FInterchangeMeshGeometry& GetMeshGeometryByUid(const FString& MeshGeometryUid) const;
/**
* Get all instanced mesh UIDs that use the mesh geometry unique ID.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API void GetAllMeshInstanceUidsUsingMeshGeometryUid(const FString& MeshGeometryUid, TArray<FString>& MeshInstanceUids) const;
/**
* Iterate over all instanced mesh UIDs that use the mesh geometry unique ID.
*/
UE_API void IterateAllMeshInstanceUsingMeshGeometry(const FString& MeshGeometryUid, TFunctionRef<void(const FInterchangeMeshInstance&)> IterationLambda) const;
/**
* Return a list of skinned FInterchangeMeshInstance UIDs that can be combined together.
* We cannot create a skinned mesh that has multiple skeleton root nodes. This function returns a combined MeshInstance per skeleton roots.
*/
UE_API void GetCombinedSkinnedMeshInstances(TMap<FString, TArray<FString>>& OutMeshInstanceUidsPerSkeletonRootUid) const;
/**
* Return the skeleton root node UID. This is the UID for a UInterchangeSceneNode that has a "Joint" specialized type.
* Return an empty string if the MeshInstanceUid parameter points to nothing.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API FString GetMeshInstanceSkeletonRootUid(const FString& MeshInstanceUid) const;
UE_API FString GetMeshInstanceSkeletonRootUid(const FInterchangeMeshInstance& MeshInstance) const;
/**
* Return the skeleton root node UID. This is the UID for a UInterchangeSceneNode that has a "Joint" specialized type.
* Return an empty string if the MeshGeometryUid parameter points to nothing.
*/
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
UE_API FString GetMeshGeometrySkeletonRootUid(const FString& MeshGeometryUid) const;
UE_API FString GetMeshGeometrySkeletonRootUid(const FInterchangeMeshGeometry& MeshGeometry) const;
UFUNCTION(BlueprintCallable, Category = "Interchange | Pipeline | Meshes")
void SetContext(const FInterchangePipelineMeshesUtilitiesContext& Context) const
{
CurrentDataContext = Context;
}
protected:
TMap<FString, FInterchangeMeshGeometry> MeshGeometriesPerMeshUid;
TMap<FString, FInterchangeMeshInstance> MeshInstancesPerMeshInstanceUid;
TMap<FString, FString> SkeletonRootUidPerMeshUid;
UInterchangeBaseNodeContainer* BaseNodeContainer;
mutable FInterchangePipelineMeshesUtilitiesContext CurrentDataContext;
};
namespace UE::Interchange::MeshesUtilities
{
/**
* Applies material slot dependencies stored in SlotMaterialDependencies to FactoryNode.
* If the caller want to support bKeepSectionSeparate feature it must provide a valid ExistingSlotMaterialDependenciesPtr.
*/
template<class T>
void ApplySlotMaterialDependencies(T& FactoryNode
, const TMap<FString, FString>& SlotMaterialDependencies
, const UInterchangeBaseNodeContainer& NodeContainer
, TMap<FString, FString> *ExistingSlotMaterialDependenciesPtr)
{
bool bKeepSectionsSeparate = false;
int32 IndexCounter = 0; //Only use when bKeepSectionsSeparate is true
if (ExistingSlotMaterialDependenciesPtr)
{
FactoryNode.GetCustomKeepSectionsSeparate(bKeepSectionsSeparate);
IndexCounter = ExistingSlotMaterialDependenciesPtr->Num();
}
for (const TPair<FString, FString>& SlotMaterialDependency : SlotMaterialDependencies)
{
FString NewSlotName = SlotMaterialDependency.Key;
if (bKeepSectionsSeparate && ExistingSlotMaterialDependenciesPtr)
{
if (ExistingSlotMaterialDependenciesPtr->Contains(NewSlotName))
{
NewSlotName += TEXT("_Section") + FString::FromInt(IndexCounter);
}
ExistingSlotMaterialDependenciesPtr->Add(NewSlotName, SlotMaterialDependency.Value);
IndexCounter++;
}
const FString MaterialFactoryNodeUid = UInterchangeBaseMaterialFactoryNode::GetMaterialFactoryNodeUidFromMaterialNodeUid(SlotMaterialDependency.Value);
FactoryNode.SetSlotMaterialDependencyUid(NewSlotName, MaterialFactoryNodeUid);
if (UInterchangeBaseMaterialFactoryNode* MaterialFactoryNode = Cast<UInterchangeBaseMaterialFactoryNode>(NodeContainer.GetFactoryNode(MaterialFactoryNodeUid)))
{
bool IsMaterialImportEnabled = true;
MaterialFactoryNode->GetCustomIsMaterialImportEnabled(IsMaterialImportEnabled);
MaterialFactoryNode->SetEnabled(IsMaterialImportEnabled);
// Create a factory dependency so Material asset are imported before the static mesh asset
TArray<FString> FactoryDependencies;
FactoryNode.GetFactoryDependencies(FactoryDependencies);
if (!FactoryDependencies.Contains(MaterialFactoryNodeUid))
{
FactoryNode.AddFactoryDependencyUid(MaterialFactoryNodeUid);
}
}
}
if (ExistingSlotMaterialDependenciesPtr)
{
ExistingSlotMaterialDependenciesPtr->Append(SlotMaterialDependencies);
}
}
template<class T>
void ReorderSlotMaterialDependencies(T& FactoryNode, const UInterchangeBaseNodeContainer& NodeContainer)
{
TMap<FString, FString> SlotMaterialDependencies;
FactoryNode.GetSlotMaterialDependencies(SlotMaterialDependencies);
//Empty all slot dependencies, they will be added backin the correct order
FactoryNode.ResetSlotMaterialDependencies();
TArray<FString> KeyReorder;
KeyReorder.Reserve(SlotMaterialDependencies.Num());
TArray<FString> MissingSuffixMaterialNames;
MissingSuffixMaterialNames.Reserve(SlotMaterialDependencies.Num());
//Reorder material using the skinXX workflow
for (const TPair<FString, FString>& SlotMaterialDependency : SlotMaterialDependencies)
{
bool bHasSuffix = false;
const FString& MaterialName = SlotMaterialDependency.Key;
if (MaterialName.Len() > 6)
{
int32 Offset = MaterialName.Find(TEXT("_SKIN"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
if (Offset != INDEX_NONE)
{
// Chop off the material name so we are left with the number in _SKINXX
FString SkinXXNumber = MaterialName.Right(MaterialName.Len() - (Offset + 1)).RightChop(4);
if (SkinXXNumber.IsNumeric())
{
int32 TmpIndex = FPlatformString::Atoi(*SkinXXNumber);
if (TmpIndex >= 0)
{
while (KeyReorder.Num() <= TmpIndex)
{
KeyReorder.AddDefaulted();
}
check(KeyReorder.IsValidIndex(TmpIndex));
if (KeyReorder[TmpIndex].IsEmpty())
{
bHasSuffix = true;
KeyReorder[TmpIndex] = MaterialName;
continue;
}
}
}
}
}
if (!bHasSuffix)
{
MissingSuffixMaterialNames.Add(MaterialName);
}
}
//The map is MaterialName, MaterialUid
TMap<FString, FString> ReorderedSlotMaterialDependencies;
ReorderedSlotMaterialDependencies.Reserve(SlotMaterialDependencies.Num());
//Start by adding the KeyReorder material
for (const FString& MaterialName : KeyReorder)
{
if (MaterialName.IsEmpty())
{
continue;
}
const FString& SlotMaterialUid = SlotMaterialDependencies.FindChecked(MaterialName);
ReorderedSlotMaterialDependencies.Add(MaterialName, SlotMaterialUid);
}
//Add the missing suffix material
for (const FString& MaterialName : MissingSuffixMaterialNames)
{
const FString& SlotMaterialUid = SlotMaterialDependencies.FindChecked(MaterialName);
ReorderedSlotMaterialDependencies.Add(MaterialName, SlotMaterialUid);
}
check(ReorderedSlotMaterialDependencies.Num() == SlotMaterialDependencies.Num());
for (const TPair<FString, FString>& SlotMaterialDependency : ReorderedSlotMaterialDependencies)
{
FactoryNode.SetSlotMaterialDependencyUid(SlotMaterialDependency.Key, SlotMaterialDependency.Value);
}
}
}
#undef UE_API