// 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 "InterchangeSceneComponentNodes.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 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& 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 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 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> SkeletalMeshFactoryDependencyOrderPerSkeletonRootNodeUid; auto SetSkeletalMeshDependencies = [&SkeletalMeshFactoryDependencyOrderPerSkeletonRootNodeUid](const FString& JointNodeUid, UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode) { TArray& 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>& MeshUidsPerLodIndex , const TArray& 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].BaseNodes.Num() == 1) { const UInterchangeBaseNode* BaseNode = MeshInstance.SceneNodePerLodIndex[LodIndexZero].BaseNodes[0]; //This mesh must be added to all existing LODs over 0 for (TPair>& MeshUidsPerLodIndexPair : MeshUidsPerLodIndex) { if (MeshUidsPerLodIndexPair.Key > LodIndexZero) { TArray& TranslatedNodes = MeshUidsPerLodIndexPair.Value; TranslatedNodes.AddUnique(BaseNode->GetUniqueID()); } } } } } if (MeshUidsPerLodIndex.Num() > 0) { UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = CreateSkeletalMeshFactoryNode(SkeletonRootUid, MeshUidsPerLodIndex); if (SkeletalMeshFactoryNode != nullptr) { SetSkeletalMeshDependencies(SkeletonRootUid, SkeletalMeshFactoryNode); SkeletonFactoryNodes.Add(SkeletonFactoryNode); SkeletalMeshFactoryNodes.Add(SkeletalMeshFactoryNode); UpdateAssemblyPartDependencyTable(SkeletalMeshFactoryNode, MeshUidsPerLodIndex); bFoundInstances = true; } } }; PRAGMA_DISABLE_DEPRECATION_WARNINGS const bool bCombineSkeletalMeshes = bCombineSkeletalMeshes_DEPRECATED; PRAGMA_ENABLE_DEPRECATION_WARNINGS if (bCombineSkeletalMeshes) { ////////////////////////////////////////////////////////////////////////// //Combined everything we can TMap> 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>& 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> MeshUidsPerLodIndex; const TArray& MeshUids = SkeletonRootUidAndMeshUids.Value; for (const FString& MeshUid : MeshUids) { if (bUseInstanceMesh) { const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid); for (const TPair& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex) { const int32 LodIndex = LodIndexAndSceneNodeContainer.Key; const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value; TArray& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex); for (const UInterchangeBaseNode* BaseNode : SceneNodeContainer.BaseNodes) { TranslatedNodes.Add(BaseNode->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& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex); TranslatedNodes.Add(MeshGeometry.MeshUid); } } FinishSkeletalMeshSetup(bUseInstanceMesh, bFoundInstances, SkeletonRootUid, SkeletonFactoryNode, MeshUidsPerLodIndex, MeshUids); } return bFoundInstances; } return false; #endif }; const bool bUseSingleBoneForConvertedMeshes = CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_SkeletalMesh && CommonMeshesProperties->bSingleBoneSkeleton; PipelineMeshesUtilities->GetCombinedSkinnedMeshInstances(MeshUidsPerSkeletonRootUid, bUseSingleBoneForConvertedMeshes); constexpr bool bUseMeshInstance = true; CreatePerSkeletonRootUidCombinedSkinnedMesh(bUseMeshInstance); } else { ////////////////////////////////////////////////////////////////////////// //Do not combined meshes TArray 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> 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& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex) { const int32 LodIndex = LodIndexAndSceneNodeContainer.Key; const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value; TArray& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex); for (const UInterchangeBaseNode* BaseNode : SceneNodeContainer.BaseNodes) { TranslatedNodes.Add(BaseNode->GetUniqueID()); } } } else { const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(MeshUid); const int32 LodIndex = 0; TArray& 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); } CreateAssemblyPartDependencies(); } UInterchangeSkeletalMeshFactoryNode* UInterchangeGenericMeshPipeline::CreateSkeletalMeshFactoryNode(const FString& RootJointUid, const TMap>& 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(BaseNodeContainer->GetFactoryNode(SkeletonUid)); if (!ensure(SkeletonFactoryNode)) { //Log an error return nullptr; } if (MeshUidsPerLodIndex.Num() == 0) { return nullptr; } auto HasNaniteAssemblyDependencies = [this, &MeshUidsPerLodIndex](const int32 LodIndex) { if (!PipelineMeshesUtilities || !PipelineMeshesUtilities->HasAssemblyMeshDependencies()) { return false; } const TArray* MeshUids = MeshUidsPerLodIndex.Find(LodIndex); if (ensure(MeshUids)) { for (const FString& MeshUid : *MeshUids) { if (const UInterchangeMeshNode* MeshNode = Cast(BaseNodeContainer->GetNode(MeshUid))) { if (MeshNode->GetAssemblyPartDependenciesCount() > 0) { return true; } } if (const UInterchangeSceneNode* SceneNode = Cast(BaseNodeContainer->GetNode(MeshUid))) { FString MeshNodeUid; if (SceneNode->GetCustomAssetInstanceUid(MeshNodeUid)) { if (const UInterchangeMeshNode* MeshNode = Cast(BaseNodeContainer->GetNode(MeshNodeUid))) { if (MeshNode->GetAssemblyPartDependenciesCount() > 0) { return true; } } } } } } return false; }; auto GetFirstNodeInfo = [this, &MeshUidsPerLodIndex](const int32 LodIndex, FString& OutFirstMeshNodeUid, int32& OutSceneNodeCount)->const UInterchangeBaseNode* { OutSceneNodeCount = 0; const TArray* MeshUids = MeshUidsPerLodIndex.Find(LodIndex); if (ensure(MeshUids)) { FString FirstNodeUid = MeshUids->Num() > 0 ? (*MeshUids)[0] : FString(); for (int32 Index = 1; Index < MeshUids->Num(); ++Index) { if ((*MeshUids)[Index] < FirstNodeUid) { FirstNodeUid = (*MeshUids)[Index]; } } if (!FirstNodeUid.IsEmpty()) { if (const UInterchangeMeshNode* MeshNode = Cast(BaseNodeContainer->GetNode(FirstNodeUid))) { OutFirstMeshNodeUid = FirstNodeUid; return MeshNode; } if (const UInterchangeSceneNode* SceneNode = Cast(BaseNodeContainer->GetNode(FirstNodeUid))) { FString MeshNodeUid; if (SceneNode->GetCustomAssetInstanceUid(MeshNodeUid)) { OutSceneNodeCount = MeshUids->Num(); OutFirstMeshNodeUid = MeshNodeUid; return SceneNode; } } if (const UInterchangeInstancedStaticMeshComponentNode* ISMComponentNode = Cast(BaseNodeContainer->GetNode(FirstNodeUid))) { FString AssetInstanceUid; if (ISMComponentNode->GetCustomInstancedAssetUid(AssetInstanceUid)) { OutSceneNodeCount = MeshUids->Num(); OutFirstMeshNodeUid = AssetInstanceUid; return ISMComponentNode; } } } } //Log an error 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(InterchangeBaseNode); const UInterchangeMeshNode* FirstMeshNode = Cast(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(); } const FString SkeletalMeshUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(SkeletalMeshUid_MeshNamePart + SkeletonUid); UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = NewObject(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(); 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(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); } // When Nanite is enabled, recomputed tangents are unused. Nanite generates its own tangents // unless explicit tangent data is provided, so we can safely skip recomputation here. bool bRecomputeTangents = CommonMeshesProperties->bRecomputeTangents; if (bRecomputeTangents && HasNaniteAssemblyDependencies(BaseLodIndex)) { UE_LOG(LogInterchangePipeline, Log, TEXT("UInterchangeGenericMeshPipeline: Disabling unnecessary tangent recomputation on Nanite skeletalmesh %s"), *DisplayLabel); bRecomputeTangents = false; } //Common meshes build options SkeletalMeshFactoryNode->SetCustomRecomputeNormals(CommonMeshesProperties->bRecomputeNormals); SkeletalMeshFactoryNode->SetCustomRecomputeTangents(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(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>& NodeUidsPerLodIndex) { check(CommonMeshesProperties.IsValid()); check(CommonSkeletalMeshesAndAnimationsProperties.IsValid()); const FString SkeletalMeshUid = SkeletalMeshFactoryNode->GetUniqueID(); const FString SkeletonUid = SkeletonFactoryNode->GetUniqueID(); int32 MaxLodIndex = 0; for (const TPair>& LodIndexAndNodeUids : NodeUidsPerLodIndex) { MaxLodIndex = FMath::Max(MaxLodIndex, LodIndexAndNodeUids.Key); } TArray 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 NodeUids = NodeUidsPerLodIndex.Contains(LodIndex) ? NodeUidsPerLodIndex.FindChecked(LodIndex) : EmptyLodData; if (!CommonSkeletalMeshesAndAnimationsProperties->bImportMeshesInBoneHierarchy) { //Unless ForceAllMeshAsType is set to SkeletalMesh (UE-317102) //In this case we need to keep nested static meshes since they might be the only meshes used to generate this skeletal mesh. if (CommonMeshesProperties->ForceAllMeshAsType != EInterchangeForceMeshType::IFMT_SkeletalMesh) { 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(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 ExistingLodSlotMaterialDependencies; constexpr bool bAddSourceNodeName = true; for (const FString& NodeUid : NodeUids) { const UInterchangeBaseNode* BaseNode = BaseNodeContainer->GetNode(NodeUid); TMap SlotMaterialDependencies; if (const UInterchangeSceneNode* SceneNode = Cast(BaseNode)) { FString MeshDependency; SceneNode->GetCustomAssetInstanceUid(MeshDependency); if (BaseNodeContainer->IsNodeUidValid(MeshDependency)) { const UInterchangeMeshNode* MeshDependencyNode = Cast(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(BaseNode)) { UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshNode, SkeletalMeshFactoryNode, bAddSourceNodeName); SkeletalMeshFactoryNode->AddTargetNodeUid(NodeUid); MeshNode->AddTargetNodeUid(SkeletalMeshFactoryNode->GetUniqueID()); MeshNode->GetSlotMaterialDependencies(SlotMaterialDependencies); } else if (const UInterchangeInstancedStaticMeshComponentNode* ISMComponentNode = Cast(BaseNode)) { FString MeshDependency; ISMComponentNode->GetCustomInstancedAssetUid(MeshDependency); if (const UInterchangeMeshNode* MeshDependencyNode = Cast(BaseNodeContainer->GetNode(MeshDependency))) { UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshDependencyNode, SkeletalMeshFactoryNode, bAddSourceNodeName); SkeletalMeshFactoryNode->AddTargetNodeUid(MeshDependency); MeshDependencyNode->AddTargetNodeUid(SkeletalMeshFactoryNode->GetUniqueID()); MeshDependencyNode->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(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(CreatedAsset); if (!CreatedPhysicsAsset) { return; } if (const UInterchangePhysicsAssetFactoryNode* PhysicsAssetFactoryNode = Cast(FactoryNode)) { FString SkeletalMeshFactoryNodeUid; if (PhysicsAssetFactoryNode->GetCustomSkeletalMeshUid(SkeletalMeshFactoryNodeUid)) { if (const UInterchangeSkeletalMeshFactoryNode* SkeletalMeshFactoryNode = Cast(BaseNodeContainer->GetFactoryNode(SkeletalMeshFactoryNodeUid))) { FSoftObjectPath ReferenceObject; SkeletalMeshFactoryNode->GetCustomReferenceObject(ReferenceObject); if (ReferenceObject.IsValid()) { if (USkeletalMesh* SkeletalMesh = Cast(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 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(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 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(BaseNodeContainer->GetFactoryNode(LodDataUids[0]))) { //If the user did not specify any skeleton if (!CommonSkeletalMeshesAndAnimationsProperties->Skeleton.IsValid()) { FString SkeletalMeshSkeletonUid; SkeletalMeshLodDataNode->GetCustomSkeletonUid(SkeletalMeshSkeletonUid); UInterchangeSkeletonFactoryNode* SkeletonFactoryNode = Cast(BaseNodeContainer->GetFactoryNode(SkeletalMeshSkeletonUid)); if (SkeletonFactoryNode) { const FString SkeletonName = DisplayLabelName + TEXT("_Skeleton"); SkeletonFactoryNode->SetDisplayLabel(SkeletonName); } } } } const UClass* PhysicsAssetFactoryNodeClass = UInterchangePhysicsAssetFactoryNode::StaticClass(); TArray PhysicsAssetNodeUids; BaseNodeContainer->GetNodes(PhysicsAssetFactoryNodeClass, PhysicsAssetNodeUids); for (const FString& PhysicsAssetNodeUid : PhysicsAssetNodeUids) { UInterchangePhysicsAssetFactoryNode* PhysicsAssetFactoryNode = Cast(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); } } }