Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Pipelines/Private/InterchangeGenericSkeletalMeshPipeline.cpp
2025-05-18 13:04:45 +08:00

802 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InterchangeGenericMeshPipeline.h"
#include "Animation/Skeleton.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Async/Async.h"
#include "CoreMinimal.h"
#include "Engine/EngineTypes.h"
#include "InterchangeGenericAssetsPipeline.h"
#include "InterchangeMaterialFactoryNode.h"
#include "InterchangeMeshNode.h"
#include "InterchangePhysicsAssetFactoryNode.h"
#include "InterchangePipelineLog.h"
#include "InterchangePipelineMeshesUtilities.h"
#include "InterchangeSceneNode.h"
#include "InterchangeSkeletalMeshFactoryNode.h"
#include "InterchangeSkeletalMeshLodDataNode.h"
#include "InterchangeSkeletonFactoryNode.h"
#include "InterchangeSkeletonHelper.h"
#include "InterchangeSourceData.h"
#include "Misc/Paths.h"
#include "Nodes/InterchangeBaseNode.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "Nodes/InterchangeUserDefinedAttribute.h"
#if WITH_EDITOR
#include "PhysicsAssetUtils.h"
#endif //WITH_EDITOR
#include "PhysicsEngine/PhysicsAsset.h"
#include "ReferenceSkeleton.h"
#include "Tasks/Task.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "InterchangeManager.h"
namespace UE::Interchange::SkeletalMeshGenericPipeline
{
bool RecursiveFindChildUid(const UInterchangeBaseNodeContainer* BaseNodeContainer, const FString& ParentUid, const FString& SearchUid)
{
if (ParentUid == SearchUid)
{
return true;
}
const int32 ChildCount = BaseNodeContainer->GetNodeChildrenCount(ParentUid);
TArray<FString> Childrens = BaseNodeContainer->GetNodeChildrenUids(ParentUid);
for (int32 ChildIndex = 0; ChildIndex < ChildCount; ++ChildIndex)
{
if (RecursiveFindChildUid(BaseNodeContainer, Childrens[ChildIndex], SearchUid))
{
return true;
}
}
return false;
}
void RemoveNestedMeshNodes(const UInterchangeBaseNodeContainer* BaseNodeContainer, const UInterchangeSkeletonFactoryNode* SkeletonFactoryNode, TArray<FString>& NodeUids)
{
if (!SkeletonFactoryNode)
{
return;
}
FString SkeletonRootJointUid;
SkeletonFactoryNode->GetCustomRootJointUid(SkeletonRootJointUid);
for (int32 NodeIndex = NodeUids.Num()-1; NodeIndex >= 0; NodeIndex--)
{
const FString& NodeUid = NodeUids[NodeIndex];
if (RecursiveFindChildUid(BaseNodeContainer, SkeletonRootJointUid, NodeUid))
{
NodeUids.RemoveAt(NodeIndex);
}
}
}
void RenameSkeletalMeshLikeLegacyFbx(FString& DisplayLabel, const UInterchangeMeshNode* MeshNode, const TArray<const UInterchangeSourceData*>& SourceDatas, UInterchangePipelineBase* OuterMostPipeline)
{
if(!MeshNode || !SourceDatas.IsValidIndex(0))
{
return;
}
bool bRenameLikeLegacyFbx = false;
MeshNode->GetBooleanAttribute(TEXT("RenameLikeLegacyFbx"), bRenameLikeLegacyFbx);
if (!bRenameLikeLegacyFbx)
{
return;
}
FString BaseFilename = FPaths::GetBaseFilename(SourceDatas[0]->GetFilename());
FString MeshName;
if (UInterchangeGenericAssetsPipeline* GenericAssetPipeline = Cast<UInterchangeGenericAssetsPipeline>(OuterMostPipeline))
{
if (!GenericAssetPipeline->bUseSourceNameForAsset && !DisplayLabel.IsEmpty())
{
MeshName = TEXT("_") + DisplayLabel;
}
}
DisplayLabel = BaseFilename + MeshName;
}
}
void UInterchangeGenericMeshPipeline::ExecutePreImportPipelineSkeletalMesh()
{
LLM_SCOPE_BYNAME(TEXT("Interchange"));
check(CommonMeshesProperties.IsValid());
if (!bImportSkeletalMeshes)
{
//Nothing to import
return;
}
if (CommonMeshesProperties->ForceAllMeshAsType != EInterchangeForceMeshType::IFMT_None && CommonMeshesProperties->ForceAllMeshAsType != EInterchangeForceMeshType::IFMT_SkeletalMesh)
{
//Nothing to import
return;
}
#if WITH_EDITOR
//Make sure the generic pipeline we cover all skeletalmesh build settings by asserting when we import
Async(EAsyncExecution::TaskGraphMainThread, []()
{
static bool bVerifyBuildProperties = false;
if (!bVerifyBuildProperties)
{
bVerifyBuildProperties = true;
TArray<const UClass*> Classes;
Classes.Add(UInterchangeGenericCommonMeshesProperties::StaticClass());
Classes.Add(UInterchangeGenericMeshPipeline::StaticClass());
if (!DoClassesIncludeAllEditableStructProperties(Classes, FSkeletalMeshBuildSettings::StaticStruct()))
{
UE_LOG(LogInterchangePipeline, Log, TEXT("UInterchangeGenericMeshPipeline: The generic pipeline does not cover all skeletal mesh build options."));
}
}
});
#endif
TMap<FString, TArray<FString>> SkeletalMeshFactoryDependencyOrderPerSkeletonRootNodeUid;
auto SetSkeletalMeshDependencies = [&SkeletalMeshFactoryDependencyOrderPerSkeletonRootNodeUid](const FString& JointNodeUid, UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode)
{
TArray<FString>& SkeletalMeshFactoryDependencyOrder = SkeletalMeshFactoryDependencyOrderPerSkeletonRootNodeUid.FindOrAdd(JointNodeUid);
//Updating the skeleton is not multi thread safe, so we add dependency between skeletalmesh altering the same skeleton
//TODO make the skeletalMesh ReferenceSkeleton thread safe to allow multiple parallel skeletalmesh factory on the same skeleton asset.
int32 DependencyIndex = SkeletalMeshFactoryDependencyOrder.AddUnique(SkeletalMeshFactoryNode->GetUniqueID());
if (DependencyIndex > 0)
{
const FString SkeletalMeshFactoryNodeDependencyUid = SkeletalMeshFactoryDependencyOrder[DependencyIndex - 1];
SkeletalMeshFactoryNode->AddFactoryDependencyUid(SkeletalMeshFactoryNodeDependencyUid);
}
};
auto FinishSkeletalMeshSetup = [this, &SetSkeletalMeshDependencies](const bool bUseInstanceMesh
, bool& bFoundInstances
, const FString& SkeletonRootUid
, UInterchangeSkeletonFactoryNode* SkeletonFactoryNode
, TMap<int32, TArray<FString>>& MeshUidsPerLodIndex
, const TArray<FString>& MeshUids)
{
//We must add all nodes that are not part of any lod to all lods upper then 0
if (bUseInstanceMesh && MeshUidsPerLodIndex.Num() > 1)
{
constexpr int32 LodIndexZero = 0;
for (const FString& MeshUid : MeshUids)
{
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
if (MeshInstance.LodGroupNode == nullptr
&& MeshInstance.SceneNodePerLodIndex.Num() == 1
&& MeshInstance.SceneNodePerLodIndex.Contains(LodIndexZero)
&& MeshInstance.SceneNodePerLodIndex[LodIndexZero].SceneNodes.Num() == 1)
{
const UInterchangeSceneNode* SceneNode = MeshInstance.SceneNodePerLodIndex[LodIndexZero].SceneNodes[0];
//This mesh must be added to all existing LODs over 0
for (TPair<int32, TArray<FString>>& MeshUidsPerLodIndexPair : MeshUidsPerLodIndex)
{
if (MeshUidsPerLodIndexPair.Key > LodIndexZero)
{
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndexPair.Value;
TranslatedNodes.AddUnique(SceneNode->GetUniqueID());
}
}
}
}
}
if (MeshUidsPerLodIndex.Num() > 0)
{
UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = CreateSkeletalMeshFactoryNode(SkeletonRootUid, MeshUidsPerLodIndex);
if (SkeletalMeshFactoryNode != nullptr)
{
SetSkeletalMeshDependencies(SkeletonRootUid, SkeletalMeshFactoryNode);
SkeletonFactoryNodes.Add(SkeletonFactoryNode);
SkeletalMeshFactoryNodes.Add(SkeletalMeshFactoryNode);
bFoundInstances = true;
}
}
};
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const bool bCombineSkeletalMeshes = bCombineSkeletalMeshes_DEPRECATED;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (bCombineSkeletalMeshes)
{
//////////////////////////////////////////////////////////////////////////
//Combined everything we can
TMap<FString, TArray<FString>> MeshUidsPerSkeletonRootUid;
auto CreatePerSkeletonRootUidCombinedSkinnedMesh = [this, &MeshUidsPerSkeletonRootUid, &SetSkeletalMeshDependencies, &FinishSkeletalMeshSetup](const bool bUseInstanceMesh)
{
#if !WITH_EDITOR || !WITH_EDITORONLY_DATA
if (MeshUidsPerSkeletonRootUid.Num() > 0)
{
UE_LOG(LogInterchangePipeline, Warning, TEXT("Cannot import skeletalMesh asset in runtime, this is an editor only feature."));
}
return false;
#else
if (MeshUidsPerSkeletonRootUid.Num() > 0)
{
bool bFoundInstances = false;
for (const TPair<FString, TArray<FString>>& SkeletonRootUidAndMeshUids : MeshUidsPerSkeletonRootUid)
{
const FString& SkeletonRootUid = SkeletonRootUidAndMeshUids.Key;
//Every iteration is a skeletalmesh asset that combine all MeshInstances sharing the same skeleton root node
UInterchangeSkeletonFactoryNode* SkeletonFactoryNode = CommonSkeletalMeshesAndAnimationsProperties->CreateSkeletonFactoryNode(BaseNodeContainer, SkeletonRootUid);
//The MeshUids can represent a SceneNode pointing on a MeshNode or directly a MeshNode;
TMap<int32, TArray<FString>> MeshUidsPerLodIndex;
const TArray<FString>& MeshUids = SkeletonRootUidAndMeshUids.Value;
for (const FString& MeshUid : MeshUids)
{
if (bUseInstanceMesh)
{
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
for (const TPair<int32, FInterchangeLodSceneNodeContainer>& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex)
{
const int32 LodIndex = LodIndexAndSceneNodeContainer.Key;
const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
for (const UInterchangeSceneNode* SceneNode : SceneNodeContainer.SceneNodes)
{
TranslatedNodes.Add(SceneNode->GetUniqueID());
}
}
}
else
{
//MeshGeometry cannot have Lod since LODs are define in the scene node
const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(MeshUid);
const int32 LodIndex = 0;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
TranslatedNodes.Add(MeshGeometry.MeshUid);
}
}
FinishSkeletalMeshSetup(bUseInstanceMesh, bFoundInstances, SkeletonRootUid, SkeletonFactoryNode, MeshUidsPerLodIndex, MeshUids);
}
return bFoundInstances;
}
return false;
#endif
};
PipelineMeshesUtilities->GetCombinedSkinnedMeshInstances(MeshUidsPerSkeletonRootUid);
bool bUseMeshInstance = true;
CreatePerSkeletonRootUidCombinedSkinnedMesh(bUseMeshInstance);
}
else
{
//////////////////////////////////////////////////////////////////////////
//Do not combined meshes
TArray<FString> MeshUids;
auto CreatePerSkeletonRootUidSkinnedMesh = [this, &MeshUids, &SetSkeletalMeshDependencies, &FinishSkeletalMeshSetup](const bool bUseInstanceMesh)
{
#if !WITH_EDITOR || !WITH_EDITORONLY_DATA
if (MeshUids.Num() > 0)
{
UE_LOG(LogInterchangePipeline, Warning, TEXT("Cannot import skeletalMesh asset in runtime, this is an editor only feature."));
}
return false;
#else
if (MeshUids.Num() > 0)
{
bool bFoundInstances = false;
for (const FString& MeshUid : MeshUids)
{
//Every iteration is a skeletalmesh asset that combine all MeshInstances sharing the same skeleton root node
//The MeshUids can represent a SceneNode pointing on a MeshNode or directly a MeshNode;
TMap<int32, TArray<FString>> MeshUidsPerLodIndex;
FString SkeletonRootUid;
if (!(bUseInstanceMesh ? PipelineMeshesUtilities->IsValidMeshInstanceUid(MeshUid) : PipelineMeshesUtilities->IsValidMeshGeometryUid(MeshUid)))
{
continue;
}
SkeletonRootUid = (bUseInstanceMesh ? PipelineMeshesUtilities->GetMeshInstanceSkeletonRootUid(MeshUid) : PipelineMeshesUtilities->GetMeshGeometrySkeletonRootUid(MeshUid));
if (SkeletonRootUid.IsEmpty())
{
if (CommonSkeletalMeshesAndAnimationsProperties->bConvertStaticsWithMorphTargetsToSkeletals)
{
SkeletonRootUid = MeshUid;
}
else
{
//Log an error
continue;
}
}
UInterchangeSkeletonFactoryNode* SkeletonFactoryNode = CommonSkeletalMeshesAndAnimationsProperties->CreateSkeletonFactoryNode(BaseNodeContainer, SkeletonRootUid);
if (bUseInstanceMesh)
{
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
for (const TPair<int32, FInterchangeLodSceneNodeContainer>& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex)
{
const int32 LodIndex = LodIndexAndSceneNodeContainer.Key;
const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
for (const UInterchangeSceneNode* SceneNode : SceneNodeContainer.SceneNodes)
{
TranslatedNodes.Add(SceneNode->GetUniqueID());
}
}
}
else
{
const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(MeshUid);
const int32 LodIndex = 0;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
TranslatedNodes.Add(MeshGeometry.MeshUid);
}
FinishSkeletalMeshSetup(bUseInstanceMesh, bFoundInstances, SkeletonRootUid, SkeletonFactoryNode, MeshUidsPerLodIndex, MeshUids);
}
return bFoundInstances;
}
return false;
#endif
};
PipelineMeshesUtilities->GetAllSkinnedMeshInstance(MeshUids);
bool bUseMeshInstance = true;
CreatePerSkeletonRootUidSkinnedMesh(bUseMeshInstance);
}
}
UInterchangeSkeletalMeshFactoryNode* UInterchangeGenericMeshPipeline::CreateSkeletalMeshFactoryNode(const FString& RootJointUid, const TMap<int32, TArray<FString>>& MeshUidsPerLodIndex)
{
check(CommonMeshesProperties.IsValid());
check(CommonSkeletalMeshesAndAnimationsProperties.IsValid());
//Get the skeleton factory node
const UInterchangeBaseNode* RootJointNode = BaseNodeContainer->GetNode(RootJointUid);
if (!RootJointNode)
{
return nullptr;
}
const FString SkeletonUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(RootJointNode->GetUniqueID());
UInterchangeSkeletonFactoryNode* SkeletonFactoryNode = Cast<UInterchangeSkeletonFactoryNode>(BaseNodeContainer->GetFactoryNode(SkeletonUid));
if (!ensure(SkeletonFactoryNode))
{
//Log an error
return nullptr;
}
if (MeshUidsPerLodIndex.Num() == 0)
{
return nullptr;
}
auto GetFirstNodeInfo = [this, &MeshUidsPerLodIndex](const int32 Index, FString& OutFirstMeshNodeUid, int32& OutSceneNodeCount)->const UInterchangeBaseNode*
{
OutSceneNodeCount = 0;
if (!ensure(Index >= 0 && MeshUidsPerLodIndex.Num() > Index))
{
//Log an error
return nullptr;
}
for (const TPair<int32, TArray<FString>>& LodIndexAndMeshUids : MeshUidsPerLodIndex)
{
if (Index == LodIndexAndMeshUids.Key)
{
const TArray<FString>& MeshUids = LodIndexAndMeshUids.Value;
if (MeshUids.Num() > 0)
{
const FString& MeshUid = MeshUids[0];
const UInterchangeMeshNode* MeshNode = Cast<const UInterchangeMeshNode>(BaseNodeContainer->GetNode(MeshUid));
if (MeshNode)
{
OutFirstMeshNodeUid = MeshUid;
return MeshNode;
}
const UInterchangeSceneNode* SceneNode = Cast<const UInterchangeSceneNode>(BaseNodeContainer->GetNode(MeshUid));
if (SceneNode)
{
FString MeshNodeUid;
if (SceneNode->GetCustomAssetInstanceUid(MeshNodeUid))
{
OutSceneNodeCount = MeshUids.Num();
OutFirstMeshNodeUid = MeshNodeUid;
return SceneNode;
}
}
}
//We found the lod but there is no valid Mesh node to return the Uid
break;
}
}
return nullptr;
};
FString FirstMeshNodeUid;
const int32 BaseLodIndex = 0;
int32 SceneNodeCount = 0;
const UInterchangeBaseNode* InterchangeBaseNode = GetFirstNodeInfo(BaseLodIndex, FirstMeshNodeUid, SceneNodeCount);
if (!InterchangeBaseNode)
{
//Log an error
return nullptr;
}
const UInterchangeSceneNode* FirstSceneNode = Cast<UInterchangeSceneNode>(InterchangeBaseNode);
const UInterchangeMeshNode* FirstMeshNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(FirstMeshNodeUid));
//Create the skeletal mesh factory node, name it according to the first mesh node compositing the meshes
FString DisplayLabel = FirstMeshNode->GetDisplayLabel();
FString SkeletalMeshUid_MeshNamePart = FirstMeshNodeUid;
if(FirstSceneNode)
{
//use the scene node to name the skeletal mesh
DisplayLabel = FirstSceneNode->GetDisplayLabel();
//Use the first scene node uid this skeletalmesh reference, add backslash since this uid is not asset typed (\\Mesh\\) like FirstMeshNodeUid
SkeletalMeshUid_MeshNamePart = TEXT("\\") + FirstSceneNode->GetUniqueID();
}
UE::Interchange::SkeletalMeshGenericPipeline::RenameSkeletalMeshLikeLegacyFbx(DisplayLabel, FirstMeshNode, SourceDatas, GetMostPipelineOuter());
const FString SkeletalMeshUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(SkeletalMeshUid_MeshNamePart + SkeletonUid);
UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = NewObject<UInterchangeSkeletalMeshFactoryNode>(BaseNodeContainer, NAME_None);
if (!ensure(SkeletalMeshFactoryNode))
{
return nullptr;
}
SkeletalMeshFactoryNode->InitializeSkeletalMeshNode(SkeletalMeshUid, DisplayLabel, USkeletalMesh::StaticClass()->GetName(), BaseNodeContainer);
SkeletalMeshFactoryNode->AddFactoryDependencyUid(SkeletonUid);
if (CommonMeshesProperties->bKeepSectionsSeparate)
{
SkeletalMeshFactoryNode->SetCustomKeepSectionsSeparate(CommonMeshesProperties->bKeepSectionsSeparate);
}
SkeletonFactoryNode->SetCustomSkeletalMeshFactoryNodeUid(SkeletalMeshFactoryNode->GetUniqueID());
AddLodDataToSkeletalMesh(SkeletonFactoryNode, SkeletalMeshFactoryNode, MeshUidsPerLodIndex);
SkeletalMeshFactoryNode->SetCustomImportMorphTarget(bImportMorphTargets);
SkeletalMeshFactoryNode->SetCustomImportVertexAttributes(bImportVertexAttributes);
SkeletalMeshFactoryNode->SetCustomImportContentType(SkeletalMeshImportContentType);
SkeletalMeshFactoryNode->SetCustomImportSockets(CommonMeshesProperties->bImportSockets);
SkeletalMeshFactoryNode->SetCustomAddCurveMetadataToSkeleton(CommonSkeletalMeshesAndAnimationsProperties->bAddCurveMetadataToSkeleton);
//If we have a specified skeleton
if (CommonSkeletalMeshesAndAnimationsProperties->Skeleton.IsValid())
{
bool bSkeletonCompatible = false;
//TODO: support skeleton helper in runtime
#if WITH_EDITOR
bSkeletonCompatible = UE::Interchange::Private::FSkeletonHelper::IsCompatibleSkeleton(CommonSkeletalMeshesAndAnimationsProperties->Skeleton.Get()
, RootJointNode->GetUniqueID()
, BaseNodeContainer
, CommonSkeletalMeshesAndAnimationsProperties->bConvertStaticsWithMorphTargetsToSkeletals || CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_SkeletalMesh
, false /*bCheckForIdenticalSkeleton*/
, CommonMeshesProperties->bImportSockets);
#endif
if (bSkeletonCompatible)
{
FSoftObjectPath SkeletonSoftObjectPath(CommonSkeletalMeshesAndAnimationsProperties->Skeleton.Get());
SkeletalMeshFactoryNode->SetCustomSkeletonSoftObjectPath(SkeletonSoftObjectPath);
}
else
{
UInterchangeResultDisplay_Generic* Message = AddMessage<UInterchangeResultDisplay_Generic>();
Message->Text = FText::Format(NSLOCTEXT("UInterchangeGenericMeshPipeline", "IncompatibleSkeleton", "Incompatible skeleton {0} when importing skeletalmesh {1}."),
FText::FromString(CommonSkeletalMeshesAndAnimationsProperties->Skeleton->GetName()),
FText::FromString(DisplayLabel));
}
}
//Physic asset dependency, if we must create or use a specialize physic asset let create
//a PhysicsAsset factory node, so the asset will exist when we will setup the skeletalmesh
if (bCreatePhysicsAsset)
{
UInterchangePhysicsAssetFactoryNode* PhysicsAssetFactoryNode = NewObject<UInterchangePhysicsAssetFactoryNode>(BaseNodeContainer, NAME_None);
if (ensure(SkeletalMeshFactoryNode))
{
const FString PhysicsAssetUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(SkeletalMeshUid_MeshNamePart + SkeletonUid + TEXT("_PhysicsAsset"));
const FString PhysicsAssetDisplayLabel = DisplayLabel + TEXT("_PhysicsAsset");
PhysicsAssetFactoryNode->InitializePhysicsAssetNode(PhysicsAssetUid, PhysicsAssetDisplayLabel, UPhysicsAsset::StaticClass()->GetName(), BaseNodeContainer);
PhysicsAssetFactoryNode->SetCustomSkeletalMeshUid(SkeletalMeshUid);
}
}
SkeletalMeshFactoryNode->SetCustomCreatePhysicsAsset(bCreatePhysicsAsset);
if (!bCreatePhysicsAsset && PhysicsAsset.IsValid())
{
FSoftObjectPath PhysicSoftObjectPath(PhysicsAsset.Get());
SkeletalMeshFactoryNode->SetCustomPhysicAssetSoftObjectPath(PhysicSoftObjectPath);
}
const bool bTrueValue = true;
switch (CommonMeshesProperties->VertexColorImportOption)
{
case EInterchangeVertexColorImportOption::IVCIO_Replace:
{
SkeletalMeshFactoryNode->SetCustomVertexColorReplace(bTrueValue);
}
break;
case EInterchangeVertexColorImportOption::IVCIO_Ignore:
{
SkeletalMeshFactoryNode->SetCustomVertexColorIgnore(bTrueValue);
}
break;
case EInterchangeVertexColorImportOption::IVCIO_Override:
{
SkeletalMeshFactoryNode->SetCustomVertexColorOverride(CommonMeshesProperties->VertexOverrideColor);
}
break;
}
//Avoid importing skeletalmesh if we want to only import animation
if (CommonSkeletalMeshesAndAnimationsProperties->bImportOnlyAnimations)
{
SkeletonFactoryNode->SetEnabled(false);
SkeletalMeshFactoryNode->SetEnabled(false);
}
//Common meshes build options
SkeletalMeshFactoryNode->SetCustomRecomputeNormals(CommonMeshesProperties->bRecomputeNormals);
SkeletalMeshFactoryNode->SetCustomRecomputeTangents(CommonMeshesProperties->bRecomputeTangents);
SkeletalMeshFactoryNode->SetCustomUseMikkTSpace(CommonMeshesProperties->bUseMikkTSpace);
SkeletalMeshFactoryNode->SetCustomComputeWeightedNormals(CommonMeshesProperties->bComputeWeightedNormals);
SkeletalMeshFactoryNode->SetCustomUseHighPrecisionTangentBasis(CommonMeshesProperties->bUseHighPrecisionTangentBasis);
SkeletalMeshFactoryNode->SetCustomUseFullPrecisionUVs(CommonMeshesProperties->bUseFullPrecisionUVs);
SkeletalMeshFactoryNode->SetCustomUseBackwardsCompatibleF16TruncUVs(CommonMeshesProperties->bUseBackwardsCompatibleF16TruncUVs);
SkeletalMeshFactoryNode->SetCustomRemoveDegenerates(CommonMeshesProperties->bRemoveDegenerates);
//Skeletal meshes build options
SkeletalMeshFactoryNode->SetCustomUseHighPrecisionSkinWeights(bUseHighPrecisionSkinWeights);
SkeletalMeshFactoryNode->SetCustomThresholdPosition(ThresholdPosition);
SkeletalMeshFactoryNode->SetCustomThresholdTangentNormal(ThresholdTangentNormal);
SkeletalMeshFactoryNode->SetCustomThresholdUV(ThresholdUV);
SkeletalMeshFactoryNode->SetCustomMorphThresholdPosition(MorphThresholdPosition);
SkeletalMeshFactoryNode->SetCustomBoneInfluenceLimit(BoneInfluenceLimit);
SkeletalMeshFactoryNode->SetCustomMergeMorphTargetShapeWithSameName(bMergeMorphTargetsWithSameName);
return SkeletalMeshFactoryNode;
}
UInterchangeSkeletalMeshLodDataNode* UInterchangeGenericMeshPipeline::CreateSkeletalMeshLodDataNode(const FString& NodeName, const FString& NodeUniqueID, const FString& ParentNodeUniqueID)
{
FString DisplayLabel(NodeName);
FString NodeUID(NodeUniqueID);
UInterchangeSkeletalMeshLodDataNode* SkeletalMeshLodDataNode = NewObject<UInterchangeSkeletalMeshLodDataNode>(BaseNodeContainer, NAME_None);
if (!ensure(SkeletalMeshLodDataNode))
{
//TODO log error
return nullptr;
}
BaseNodeContainer->SetupNode(SkeletalMeshLodDataNode, NodeUniqueID, NodeName, EInterchangeNodeContainerType::FactoryData, ParentNodeUniqueID);
return SkeletalMeshLodDataNode;
}
void UInterchangeGenericMeshPipeline::AddLodDataToSkeletalMesh(const UInterchangeSkeletonFactoryNode* SkeletonFactoryNode, UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode, const TMap<int32, TArray<FString>>& NodeUidsPerLodIndex)
{
check(CommonMeshesProperties.IsValid());
check(CommonSkeletalMeshesAndAnimationsProperties.IsValid());
const FString SkeletalMeshUid = SkeletalMeshFactoryNode->GetUniqueID();
const FString SkeletonUid = SkeletonFactoryNode->GetUniqueID();
int32 MaxLodIndex = 0;
for (const TPair<int32, TArray<FString>>& LodIndexAndNodeUids : NodeUidsPerLodIndex)
{
MaxLodIndex = FMath::Max(MaxLodIndex, LodIndexAndNodeUids.Key);
}
TArray<FString> EmptyLodData;
const int32 LodCount = MaxLodIndex + 1;
for (int32 LodIndex = 0; LodIndex < LodCount; ++LodIndex)
{
if (!CommonMeshesProperties->bImportLods && LodIndex > 0)
{
//If the pipeline should not import lods, skip any lod over base lod
continue;
}
//Copy the nodes unique id because we need to remove nested mesh if the option is to not import them
TArray<FString> NodeUids = NodeUidsPerLodIndex.Contains(LodIndex) ? NodeUidsPerLodIndex.FindChecked(LodIndex) : EmptyLodData;
if (!CommonSkeletalMeshesAndAnimationsProperties->bImportMeshesInBoneHierarchy)
{
UE::Interchange::SkeletalMeshGenericPipeline::RemoveNestedMeshNodes(BaseNodeContainer, SkeletonFactoryNode, NodeUids);
}
//Create a lod data node with all the meshes for this LOD
const FString SkeletalMeshLodDataName = TEXT("LodData") + FString::FromInt(LodIndex);
const FString LODDataPrefix = TEXT("\\LodData") + (LodIndex > 0 ? FString::FromInt(LodIndex) : TEXT(""));
const FString SkeletalMeshLodDataUniqueID = LODDataPrefix + SkeletalMeshUid + SkeletonUid;
//The LodData already exist
UInterchangeSkeletalMeshLodDataNode* LodDataNode = Cast<UInterchangeSkeletalMeshLodDataNode>(BaseNodeContainer->GetFactoryNode(SkeletalMeshLodDataUniqueID));
if (!LodDataNode)
{
//Add the data for the LOD (skeleton Unique ID and all the mesh node fbx path, so we can find them when we will create the payload data)
LodDataNode = CreateSkeletalMeshLodDataNode(SkeletalMeshLodDataName, SkeletalMeshLodDataUniqueID, SkeletalMeshUid);
LodDataNode->SetCustomSkeletonUid(SkeletonUid);
SkeletalMeshFactoryNode->AddLodDataUniqueId(SkeletalMeshLodDataUniqueID);
}
TMap<FString, FString> ExistingLodSlotMaterialDependencies;
constexpr bool bAddSourceNodeName = true;
for (const FString& NodeUid : NodeUids)
{
TMap<FString, FString> SlotMaterialDependencies;
if (const UInterchangeSceneNode* SceneNode = Cast<UInterchangeSceneNode>(BaseNodeContainer->GetNode(NodeUid)))
{
FString MeshDependency;
SceneNode->GetCustomAssetInstanceUid(MeshDependency);
if (BaseNodeContainer->IsNodeUidValid(MeshDependency))
{
const UInterchangeMeshNode* MeshDependencyNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(MeshDependency));
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshDependencyNode, SkeletalMeshFactoryNode, bAddSourceNodeName);
SkeletalMeshFactoryNode->AddTargetNodeUid(MeshDependency);
MeshDependencyNode->AddTargetNodeUid(SkeletalMeshFactoryNode->GetUniqueID());
MeshDependencyNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
else
{
SceneNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(SceneNode, SkeletalMeshFactoryNode, bAddSourceNodeName);
}
else if (const UInterchangeMeshNode* MeshNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(NodeUid)))
{
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshNode, SkeletalMeshFactoryNode, bAddSourceNodeName);
SkeletalMeshFactoryNode->AddTargetNodeUid(NodeUid);
MeshNode->AddTargetNodeUid(SkeletalMeshFactoryNode->GetUniqueID());
MeshNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
UE::Interchange::MeshesUtilities::ApplySlotMaterialDependencies(*SkeletalMeshFactoryNode, SlotMaterialDependencies, *BaseNodeContainer, &ExistingLodSlotMaterialDependencies);
LodDataNode->AddMeshUid(NodeUid);
}
UE::Interchange::MeshesUtilities::ReorderSlotMaterialDependencies(*SkeletalMeshFactoryNode, *BaseNodeContainer);
}
}
void UInterchangeGenericMeshPipeline::PostImportSkeletalMesh(UObject* CreatedAsset, const UInterchangeFactoryBaseNode* FactoryNode)
{
check(CommonSkeletalMeshesAndAnimationsProperties.IsValid());
if (!BaseNodeContainer)
{
return;
}
USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(CreatedAsset);
if (!SkeletalMesh)
{
return;
}
//If we import only the geometry we do not want to update the skeleton reference pose.
const bool bImportGeometryOnlyContent = SkeletalMeshImportContentType == EInterchangeSkeletalMeshContentType::Geometry;
if (!bImportGeometryOnlyContent && bUpdateSkeletonReferencePose && CommonSkeletalMeshesAndAnimationsProperties->Skeleton.IsValid() && SkeletalMesh->GetSkeleton() == CommonSkeletalMeshesAndAnimationsProperties->Skeleton.Get())
{
SkeletalMesh->GetSkeleton()->UpdateReferencePoseFromMesh(SkeletalMesh);
//TODO: notify editor the skeleton has change
}
}
void UInterchangeGenericMeshPipeline::PostImportPhysicsAssetImport(UObject* CreatedAsset, const UInterchangeFactoryBaseNode* FactoryNode)
{
TRACE_CPUPROFILER_EVENT_SCOPE(UInterchangeGenericMeshPipeline::PostImportPhysicsAssetImport);
LLM_SCOPE_BYNAME(TEXT("Interchange"));
#if WITH_EDITOR
if (!bCreatePhysicsAsset || !BaseNodeContainer)
{
return;
}
UPhysicsAsset* CreatedPhysicsAsset = Cast<UPhysicsAsset>(CreatedAsset);
if (!CreatedPhysicsAsset)
{
return;
}
if (const UInterchangePhysicsAssetFactoryNode* PhysicsAssetFactoryNode = Cast<const UInterchangePhysicsAssetFactoryNode>(FactoryNode))
{
FString SkeletalMeshFactoryNodeUid;
if (PhysicsAssetFactoryNode->GetCustomSkeletalMeshUid(SkeletalMeshFactoryNodeUid))
{
if (const UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = Cast<const UInterchangeSkeletalMeshFactoryNode>(BaseNodeContainer->GetFactoryNode(SkeletalMeshFactoryNodeUid)))
{
FSoftObjectPath ReferenceObject;
SkeletalMeshFactoryNode->GetCustomReferenceObject(ReferenceObject);
if (ReferenceObject.IsValid())
{
if (USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(ReferenceObject.TryLoad()))
{
auto CreateFromSkeletalMeshLambda = [CreatedPhysicsAsset, SkeletalMesh]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(UInterchangeGenericMeshPipeline::PostImportPhysicsAssetImport::CreateFromSkeletalMeshLambda);
FPhysAssetCreateParams NewBodyData;
FText CreationErrorMessage;
constexpr bool bSetToMesh = true;
constexpr bool bShowProgress = false;
if (!FPhysicsAssetUtils::CreateFromSkeletalMesh(CreatedPhysicsAsset, SkeletalMesh, NewBodyData, CreationErrorMessage, bSetToMesh, bShowProgress))
{
//TODO: Log an error
}
};
if (!IsInGameThread() && SkeletalMesh->IsCompiling())
{
//If the skeletalmesh is compiling we have to stall on the main thread
Async(EAsyncExecution::TaskGraphMainThread, [CreateFromSkeletalMeshLambda]()
{
CreateFromSkeletalMeshLambda();
});
}
else
{
CreateFromSkeletalMeshLambda();
}
}
}
}
}
}
#endif //WITH_EDITOR
}
void UInterchangeGenericMeshPipeline::ImplementUseSourceNameForAssetOptionSkeletalMesh(const int32 MeshesImportedNodeCount, const bool bUseSourceNameForAsset, const FString& AssetName)
{
check(CommonSkeletalMeshesAndAnimationsProperties.IsValid());
const UClass* SkeletalMeshFactoryNodeClass = UInterchangeSkeletalMeshFactoryNode::StaticClass();
TArray<FString> SkeletalMeshNodeUids;
BaseNodeContainer->GetNodes(SkeletalMeshFactoryNodeClass, SkeletalMeshNodeUids);
if (SkeletalMeshNodeUids.Num() == 0)
{
return;
}
//If we import only one asset, and bUseSourceNameForAsset is true, we want to rename the asset using the file name.
const bool bShouldChangeAssetName = ((bUseSourceNameForAsset || !AssetName.IsEmpty()) && MeshesImportedNodeCount == 1);
const FString SkeletalMeshUid = SkeletalMeshNodeUids[0];
UInterchangeSkeletalMeshFactoryNode* SkeletalMeshNode = Cast<UInterchangeSkeletalMeshFactoryNode>(BaseNodeContainer->GetFactoryNode(SkeletalMeshUid));
if (!SkeletalMeshNode)
{
return;
}
FString DisplayLabelName = SkeletalMeshNode->GetDisplayLabel();
if (bShouldChangeAssetName)
{
DisplayLabelName = AssetName.IsEmpty() ? FPaths::GetBaseFilename(SourceDatas[0]->GetFilename()) : AssetName;
SkeletalMeshNode->SetDisplayLabel(DisplayLabelName);
}
//Also set the skeleton factory node name
TArray<FString> LodDataUids;
SkeletalMeshNode->GetLodDataUniqueIds(LodDataUids);
if (LodDataUids.Num() > 0)
{
//Get the skeleton from the base LOD, skeleton is shared with all LODs
if (const UInterchangeSkeletalMeshLodDataNode* SkeletalMeshLodDataNode = Cast<const UInterchangeSkeletalMeshLodDataNode>(BaseNodeContainer->GetFactoryNode(LodDataUids[0])))
{
//If the user did not specify any skeleton
if (!CommonSkeletalMeshesAndAnimationsProperties->Skeleton.IsValid())
{
FString SkeletalMeshSkeletonUid;
SkeletalMeshLodDataNode->GetCustomSkeletonUid(SkeletalMeshSkeletonUid);
UInterchangeSkeletonFactoryNode* SkeletonFactoryNode = Cast<UInterchangeSkeletonFactoryNode>(BaseNodeContainer->GetFactoryNode(SkeletalMeshSkeletonUid));
if (SkeletonFactoryNode)
{
const FString SkeletonName = DisplayLabelName + TEXT("_Skeleton");
SkeletonFactoryNode->SetDisplayLabel(SkeletonName);
}
}
}
}
const UClass* PhysicsAssetFactoryNodeClass = UInterchangePhysicsAssetFactoryNode::StaticClass();
TArray<FString> PhysicsAssetNodeUids;
BaseNodeContainer->GetNodes(PhysicsAssetFactoryNodeClass, PhysicsAssetNodeUids);
for (const FString& PhysicsAssetNodeUid : PhysicsAssetNodeUids)
{
UInterchangePhysicsAssetFactoryNode* PhysicsAssetFactoryNode = Cast<UInterchangePhysicsAssetFactoryNode>(BaseNodeContainer->GetFactoryNode(PhysicsAssetNodeUid));
if (!ensure(PhysicsAssetFactoryNode))
{
continue;
}
FString PhysicsAssetSkeletalMeshUid;
if (PhysicsAssetFactoryNode->GetCustomSkeletalMeshUid(PhysicsAssetSkeletalMeshUid) && PhysicsAssetSkeletalMeshUid.Equals(SkeletalMeshUid))
{
//Rename this asset
const FString PhysicsAssetName = DisplayLabelName + TEXT("_PhysicsAsset");
PhysicsAssetFactoryNode->SetDisplayLabel(PhysicsAssetName);
}
}
}