// 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 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 RenameSkeletalMeshLikeLegacyFbx(FString& DisplayLabel, const UInterchangeMeshNode* MeshNode, const TArray& 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(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 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].SceneNodes.Num() == 1) { const UInterchangeSceneNode* SceneNode = MeshInstance.SceneNodePerLodIndex[LodIndexZero].SceneNodes[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(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> 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 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& 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 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 UInterchangeSceneNode* SceneNode : SceneNodeContainer.SceneNodes) { TranslatedNodes.Add(SceneNode->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); } } 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 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>& LodIndexAndMeshUids : MeshUidsPerLodIndex) { if (Index == LodIndexAndMeshUids.Key) { const TArray& MeshUids = LodIndexAndMeshUids.Value; if (MeshUids.Num() > 0) { const FString& MeshUid = MeshUids[0]; const UInterchangeMeshNode* MeshNode = Cast(BaseNodeContainer->GetNode(MeshUid)); if (MeshNode) { OutFirstMeshNodeUid = MeshUid; return MeshNode; } const UInterchangeSceneNode* SceneNode = Cast(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(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(); } UE::Interchange::SkeletalMeshGenericPipeline::RenameSkeletalMeshLikeLegacyFbx(DisplayLabel, FirstMeshNode, SourceDatas, GetMostPipelineOuter()); 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); } //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(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) { 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) { TMap SlotMaterialDependencies; if (const UInterchangeSceneNode* SceneNode = Cast(BaseNodeContainer->GetNode(NodeUid))) { 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(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(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); } } }