// Copyright Epic Games, Inc. All Rights Reserved. #include "MetaHumanInterchangeDnaTranslator.h" #include "MetaHumanDNAImportColorMap.h" #include "DNACommon.h" #include "DNAUtils.h" #include "StaticMeshOperations.h" #include "SkeletalMeshAttributes.h" #include "BoneWeights.h" #include "Materials/Material.h" #include "MaterialDomain.h" #include "Async/Async.h" #include "Misc/FileHelper.h" #include "InterchangeManager.h" #include "InterchangeMeshDefinitions.h" #include "InterchangeMeshNode.h" #include "InterchangeSceneNode.h" #include "Logging/StructuredLog.h" DEFINE_LOG_CATEGORY(InterchangeDNATranslator); #define LOCTEXT_NAMESPACE "InterchangeDNATranslator" namespace UE::MetaHuman { static const TArray DNAMissingJoints = { "root", "pelvis", "spine_01", "spine_02", "spine_03" }; static const TMap MaterialSlotsMapping = { /* LOD0 meshes */ {"head_lod0_mesh", "head_shader_shader"}, {"teeth_lod0_mesh", "teeth_shader_shader"}, {"saliva_lod0_mesh", "saliva_shader_shader"}, {"eyeLeft_lod0_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod0_mesh", "eyeRight_shader_shader"}, {"eyeshell_lod0_mesh", "eyeshell_shader_shader"}, {"eyelashes_lod0_mesh", "eyelashes_shader_shader"}, {"eyeEdge_lod0_mesh", "eyeEdge_shader_shader"}, {"cartilage_lod0_mesh", "cartilage_shader_shader"}, /* LOD1 meshes */ {"head_lod1_mesh", "head_LOD1_shader_shader"}, {"teeth_lod1_mesh", "teeth_shader_shader"}, {"saliva_lod1_mesh", "saliva_shader_shader"}, {"eyeLeft_lod1_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod1_mesh", "eyeRight_shader_shader"}, {"eyeshell_lod1_mesh", "eyeshell_shader_shader"}, {"eyelashes_lod1_mesh", "eyelashes_HiLOD_shader_shader"}, {"eyeEdge_lod1_mesh", "eyeEdge_shader_shader"}, {"cartilage_lod1_mesh", "cartilage_shader_shader"}, /* LOD2 meshes */ {"head_lod2_mesh", "head_LOD2_shader_shader"}, {"teeth_lod2_mesh", "teeth_shader_shader"}, {"saliva_lod2_mesh", "saliva_shader_shader"}, {"eyeLeft_lod2_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod2_mesh", "eyeRight_shader_shader"}, {"eyeshell_lod2_mesh", "eyeshell_shader_shader"}, {"eyelashes_lod2_mesh", "eyelashes_HiLOD_shader_shader"}, {"eyeEdge_lod2_mesh", "eyeEdge_shader_shader"}, /* LOD3 meshes */ {"head_lod3_mesh", "head_LOD3_shader_shader"}, {"teeth_lod3_mesh", "teeth_shader_shader"}, {"eyeLeft_lod3_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod3_mesh", "eyeRight_shader_shader"}, {"eyeshell_lod3_mesh", "eyeshell_shader_shader"}, {"eyelashes_lod3_mesh", "eyelashes_HiLOD_shader_shader"}, {"eyeEdge_lod3_mesh", "eyeEdge_shader_shader"}, /* LOD4 meshes */ {"head_lod4_mesh", "head_LOD4_shader_shader"}, {"teeth_lod4_mesh", "teeth_shader_shader"}, {"eyeLeft_lod4_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod4_mesh", "eyeRight_shader_shader"}, {"eyeshell_lod4_mesh", "eyeshell_shader_shader"}, /* LOD5 meshes */ {"head_lod5_mesh", "head_LOD57_shader_shader"}, {"teeth_lod5_mesh", "teeth_shader_shader"}, {"eyeLeft_lod5_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod5_mesh", "eyeRight_shader_shader"}, /* LOD6 meshes */ {"head_lod6_mesh", "head_LOD57_shader_shader"}, {"teeth_lod6_mesh", "teeth_shader_shader"}, {"eyeLeft_lod6_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod6_mesh", "eyeRight_shader_shader"}, /* LOD7 meshes */ {"head_lod7_mesh", "head_LOD57_shader_shader"}, {"teeth_lod7_mesh", "teeth_shader_shader"}, {"eyeLeft_lod7_mesh", "eyeLeft_shader_shader"}, {"eyeRight_lod7_mesh", "eyeRight_shader_shader"}, /* body meshes */ {"body_lod0_mesh", "body_shader_shader"}, {"body_lod1_mesh", "body_shader_shader"}, {"body_lod2_mesh", "body_shader_shader"}, {"body_lod3_mesh", "body_shader_shader"}, {"combined_lod0_mesh", "body_shader_shader"}, {"combined_lod1_mesh", "body_shader_shader"}, {"combined_lod2_mesh", "body_shader_shader"}, {"combined_lod3_mesh", "body_shader_shader"} }; } static constexpr const TCHAR* FaceColorMaskAssetPath = TEXT("/" UE_PLUGIN_NAME "/CommonAssets/Face/MeshColorAsset.MeshColorAsset"); UMetaHumanInterchangeDnaTranslator::UMetaHumanInterchangeDnaTranslator() { } void UMetaHumanInterchangeDnaTranslator::ReleaseSource() { } void UMetaHumanInterchangeDnaTranslator::ImportFinish() { } bool UMetaHumanInterchangeDnaTranslator::IsThreadSafe() const { // This translator is not using dispatcher to translate and return payloads return false; } EInterchangeTranslatorType UMetaHumanInterchangeDnaTranslator::GetTranslatorType() const { return EInterchangeTranslatorType::Assets; } EInterchangeTranslatorAssetType UMetaHumanInterchangeDnaTranslator::GetSupportedAssetTypes() const { //DNA translator supports only Meshes return EInterchangeTranslatorAssetType::Meshes; } TArray UMetaHumanInterchangeDnaTranslator::GetSupportedFormats() const { TArray DnaExtensions; //TODO: Remove the dna import convenience function when actual dna importer is implemented DnaExtensions.Add(TEXT("dna;MetaHuman DNA format")); return DnaExtensions; } bool UMetaHumanInterchangeDnaTranslator::Translate(UInterchangeBaseNodeContainer& NodeContainer) const { // Interchange handles the source file upload from the temporary DNA file. FString FilePath = GetSourceData()->GetFilename(); if (!FPaths::FileExists(FilePath)) { UE_LOGFMT(InterchangeDNATranslator, Error, "Temporary DNA file {FilePath} does not exist.", *FilePath); return false; } TArray DNADataAsBuffer; if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *FilePath)) { const_cast(this)->DNAReader = ReadDNAFromBuffer(&DNADataAsBuffer, EDNADataLayer::All); } if (DNAReader == nullptr) { UE_LOGFMT(InterchangeDNATranslator, Error, "Failed to load temporary DNA file at {FilePath}.", *FilePath); return false; } uint16 MeshCount = DNAReader->GetMeshCount(); //Create one material node per mesh. TArray> MaterialSlots; MaterialSlots.Reserve(MeshCount); FString MaterialName; for (int16 MaterialIndex = 0; MaterialIndex < MeshCount; MaterialIndex++) { // Remap material slots to face archetype configuration. const FString MeshName = DNAReader->GetMeshName(MaterialIndex); if (UE::MetaHuman::MaterialSlotsMapping.Contains(MeshName)) { MaterialName = UE::MetaHuman::MaterialSlotsMapping[MeshName]; } else { MaterialName = DNAReader->GetMeshName(MaterialIndex) + TEXT("_shader"); } FString NodeUid = TEXT("\\Material\\") + MaterialName; MaterialSlots.Add({ MaterialName, NodeUid }); } int32 LODCount = DNAReader->GetLODCount(); for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++) { TArrayView LODMeshIndices = DNAReader->GetMeshIndicesForLOD(LODIndex); for (int16 LODMeshIndex = 0; LODMeshIndex < LODMeshIndices.Num(); LODMeshIndex++) { int32 MeshIndex = LODMeshIndices[LODMeshIndex]; // Create Mesh node per LOD0 mesh in DNA. const FString MeshName = DNAReader->GetMeshName(MeshIndex); const FString MeshUniqueId = TEXT("\\Mesh\\") + MeshName; const UInterchangeMeshNode* ExistingMeshNode = Cast(NodeContainer.GetNode(MeshUniqueId)); UInterchangeMeshNode* MeshNode = nullptr; if (ExistingMeshNode) { //This mesh node was already created. continue; } MeshNode = NewObject(&NodeContainer, NAME_None); if (!ensure(MeshNode)) { UE_LOGFMT(InterchangeDNATranslator, Error, "Mesh node allocation failed when importing DNA."); return false; } // Creating a SkinnedMessNode. NodeContainer.SetupNode(MeshNode, MeshUniqueId, MeshName, EInterchangeNodeContainerType::TranslatedAsset); MeshNode->SetSkinnedMesh(true); // Designate mesh as a skeletal mesh. // Add joint dependencies for every mesh by looking at the skin weights. const int32 MeshVertexCount = DNAReader->GetVertexPositionCount(MeshIndex); if (MeshVertexCount > 0) { TArray JointNodeUniqueIDs; for (int32 VertexIndex = 0; VertexIndex < MeshVertexCount; VertexIndex++) { TArrayView SkinJointIndices = DNAReader->GetSkinWeightsJointIndices(MeshIndex, VertexIndex); JointNodeUniqueIDs.Reserve(JointNodeUniqueIDs.Num() + SkinJointIndices.Num()); for (int32 JointIndex : SkinJointIndices) { FString JointUid = GetJointHierarchyName(DNAReader, JointIndex); if (!JointNodeUniqueIDs.Contains(JointUid)) { JointNodeUniqueIDs.Add(JointUid); MeshNode->SetSkeletonDependencyUid(JointUid); } } } } // Set material slots dependencies. if (MaterialSlots.IsValidIndex(MeshIndex)) // Material slot names are corresponding to mesh indices in the same order. { MeshNode->SetSlotMaterialDependencyUid(MaterialSlots[MeshIndex].Key, MaterialSlots[MeshIndex].Value); } FString PayLoadKey = MeshUniqueId; if (ensure(!PayloadContexts.Contains(PayLoadKey))) { TSharedPtr DnaMeshPayload = MakeShared(); DnaMeshPayload->bIsSkinnedMesh = MeshNode->IsSkinnedMesh(); DnaMeshPayload->DnaLodIndex = LODIndex; DnaMeshPayload->DnaMeshIndex = MeshIndex; PayloadContexts.Add(PayLoadKey, DnaMeshPayload); } MeshNode->SetPayLoadKey(PayLoadKey, EInterchangeMeshPayLoadType::SKELETAL); // This payload key is important, it is used to fetch the Mesh container in async mode when requested. // Wrap up morph targets. const int32 MorphTargetCount = DNAReader->GetBlendShapeTargetCount(MeshIndex); // Add up all meshes (sections). for (int32 MorphTargetIndex = 0; MorphTargetIndex < MorphTargetCount; ++MorphTargetIndex) { // Construct MorphTarget name by combining Blend Shape Channel name and Mesh name from DNA. const uint16 ChannelIndex = DNAReader->GetBlendShapeChannelIndex(MeshIndex, MorphTargetIndex); const FString BlendShapeStr = DNAReader->GetBlendShapeChannelName(ChannelIndex); const FString ShapeName = MeshName + TEXT("__") + BlendShapeStr; FString MorphTargetAttributeName = ShapeName; // MorphTarget shape mesh name. FString MorphTargetUniqueID = TEXT("\\Shape\\") + ShapeName; // MorphTarget shape mesh unique ID. const UInterchangeMeshNode* ExistingMorphTargetNode = Cast(NodeContainer.GetNode(MorphTargetUniqueID)); if (!ExistingMorphTargetNode) { UInterchangeMeshNode* MorphTargetNode = NewObject(&NodeContainer, NAME_None); NodeContainer.SetupNode(MorphTargetNode, MorphTargetUniqueID, MorphTargetAttributeName, EInterchangeNodeContainerType::TranslatedAsset); const bool bIsMorphTarget = true; MorphTargetNode->SetMorphTarget(bIsMorphTarget); MorphTargetNode->SetMorphTargetName(ShapeName); FString MorphTargetPayLoadKey = MorphTargetUniqueID; if (ensure(!PayloadContexts.Contains(MorphTargetPayLoadKey))) { TSharedPtr DnaMorphTargetPayload = MakeShared(); DnaMorphTargetPayload->DnaMeshIndex = MeshIndex; DnaMorphTargetPayload->DnaMorphTargetIndex = MorphTargetIndex; DnaMorphTargetPayload->DnaChannelIndex = ChannelIndex; PayloadContexts.Add(MorphTargetPayLoadKey, DnaMorphTargetPayload); } MorphTargetNode->SetPayLoadKey(MorphTargetPayLoadKey, EInterchangeMeshPayLoadType::MORPHTARGET); } //Create a Mesh node dependency, so the mesh node can retrieve is associate morph target MeshNode->SetMorphTargetDependencyUid(MorphTargetUniqueID); } } } constexpr bool bResetCache = false; // Add scene hierarchy. // This will include SceneNodes starting from an empty RootNode which is added manually (does not exist in DNA). UInterchangeSceneNode* RootNode = NewObject(&NodeContainer, NAME_None); const FString RootNodeUid = TEXT("RootNode"); const FString RootNodeName = RootNodeUid; NodeContainer.SetupNode(RootNode, RootNodeUid, RootNodeName, EInterchangeNodeContainerType::TranslatedScene); UInterchangeSceneNode* CurrentMeshNode = NewObject(&NodeContainer, NAME_None); const FString MeshNodeName = DNAReader->GetName(); const FString MeshNodeUid = RootNodeUid + "." + MeshNodeName; NodeContainer.SetupNode(CurrentMeshNode, MeshNodeUid, MeshNodeName, EInterchangeNodeContainerType::TranslatedScene, RootNode->GetUniqueID()); UInterchangeSceneNode* LODGroupNode = NewObject(&NodeContainer, NAME_None); const FString LODGroupUid = MeshNodeUid + TEXT("_LODGroup"); const FString LODGroupName = MeshNodeName + TEXT("_LODGroup"); NodeContainer.SetupNode(LODGroupNode, LODGroupUid, LODGroupName, EInterchangeNodeContainerType::TranslatedScene, CurrentMeshNode->GetUniqueID()); // Set LOD group attribute; LODGroupNode->AddSpecializedType(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString()); // Inside of LODGroup node we have to specify one child SceneNode per LOD. // Each LOD node should contain one SceneNode per Mesh in that LOD group in hierarchical order. for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++) { UInterchangeSceneNode* LODNode = NewObject(&NodeContainer, NAME_None); const FString LODNodeName = TEXT("LOD") + FString::FromInt(LODIndex); const FString LODNodeUid = LODGroupUid + "." + LODNodeName; NodeContainer.SetupNode(LODNode, LODNodeUid, LODNodeName, EInterchangeNodeContainerType::TranslatedScene, LODGroupNode->GetUniqueID()); // Add a SceneNode for each mesh in the LOD level. TArrayView LODMeshIndices = DNAReader->GetMeshIndicesForLOD(LODIndex); for (int16 LODMeshIndex = 0; LODMeshIndex < LODMeshIndices.Num(); LODMeshIndex++) { int32 MeshIndex = LODMeshIndices[LODMeshIndex]; const FString NodeName = DNAReader->GetMeshName(MeshIndex); const FString NodeUniqueId = LODGroupUid + "." + NodeName; const UInterchangeSceneNode* ExistingSceneNode = Cast(NodeContainer.GetNode(NodeUniqueId)); if (ExistingSceneNode) { //This scene node was already created. continue; } UInterchangeSceneNode* SceneNode = NewObject(&NodeContainer, NAME_None); NodeContainer.SetupNode(SceneNode, NodeUniqueId, NodeName, EInterchangeNodeContainerType::TranslatedScene, LODNode->GetUniqueID()); FTransform LocalTransform = FTransform::Identity; SceneNode->SetCustomLocalTransform(&NodeContainer, LocalTransform, bResetCache); // Assign mesh node dependency. const FString MeshUniqueID = TEXT("\\Mesh\\") + NodeName; const UInterchangeBaseNode* MeshNode = NodeContainer.GetNode(MeshUniqueID); if (MeshNode) { SceneNode->SetCustomAssetInstanceUid(MeshNode->GetUniqueID()); } // Assign material dependency. if (MaterialSlots.IsValidIndex(MeshIndex)) // Material slot names are corresponding to mesh indices in the same order. { SceneNode->SetSlotMaterialDependencyUid(MaterialSlots[MeshIndex].Key, MaterialSlots[MeshIndex].Value); } } } // Next, Joint hierarchy needs to be attached to a "RootNode". // NOTE: DNA hierarchy starts at spine04 joint, while Archetype skeleton is expected to have root->pelvis->spine01->spine02->spine03->... // Total of 5 joints missing at the beginning of the hierarchy. These joints are added here. FTransform CombinedMissingJointTransform; int32 JointCount = DNAReader->GetJointCount(); FString JointRoot = MeshNodeUid; if (JointCount > 0 && UE::MetaHuman::DNAMissingJoints.Num()> 0 && DNAReader->GetJointName(0) != UE::MetaHuman::DNAMissingJoints[0]) { JointRoot = AddDNAMissingJoints(NodeContainer, MeshNodeUid, CombinedMissingJointTransform); } for (int32 JointIndex = 0; JointIndex < JointCount; JointIndex++) { FString NodeName = DNAReader->GetJointName(JointIndex); FString NodeUniqueID = GetJointHierarchyName(DNAReader, JointIndex); int32 ParentIndex = DNAReader->GetJointParentIndex(JointIndex); const bool bIsRootNode = JointIndex == ParentIndex; UInterchangeSceneNode* JointNode = NewObject(&NodeContainer, NAME_None); if (!ensure(JointNode)) { UE_LOGFMT(InterchangeDNATranslator, Error, "Scene (joint) node allocation failed when importing DNA."); return false; } // Initialize joint node and set the parent association. NodeContainer.SetupNode(JointNode, NodeUniqueID, NodeName, EInterchangeNodeContainerType::TranslatedScene, !bIsRootNode ? GetJointHierarchyName(DNAReader, ParentIndex) : JointRoot); // Set the node default transform { FTransform DNATransform = FTransform::Identity; FVector JointRotationVector = DNAReader->GetNeutralJointRotation(JointIndex); FVector JointTranslation = DNAReader->GetNeutralJointTranslation(JointIndex); FVector JointScale = FVector(1.0, 1.0, 1.0); FRotator Rotation(JointRotationVector.X, JointRotationVector.Y, JointRotationVector.Z); FTransform GlobalTransform = FTransform(); // Create transform from translation and rotation of current joint. if (!bIsRootNode) { DNATransform.SetRotation(Rotation.Quaternion()); DNATransform.SetTranslation(JointTranslation); FTransform LocalTransform = DNATransform; JointNode->SetCustomLocalTransform(&NodeContainer, LocalTransform, bResetCache); JointNode->SetCustomTimeZeroLocalTransform(&NodeContainer, LocalTransform, bResetCache); JointNode->SetCustomBindPoseLocalTransform(&NodeContainer, LocalTransform, bResetCache); } else { // Root node here means Spine_04 as that's the first node in the DNA // Transform for this node in the DNA contains absolute values. But bones are constructed // relative to previous joint positions.So a relative Spine_04 position can be calculated by // combining the hard coded values of Spine_03 to Pelvis x Inverse of Absolute position of Spine_04 // However rotation/translation values have to be mapped from DNA space to UE space for Spine_04 // Taking into account the 90 degree rotation, in addition to DNAReader mapping FVector FlippedTranslation = FVector(JointTranslation.X, JointTranslation.Z, -JointTranslation.Y); FRotator RotationDNA(JointRotationVector.X, JointRotationVector.Y, JointRotationVector.Z); FQuat YUpToZUpRotation = FQuat(FRotator(0, 0, 90)); FQuat TransformRotation = YUpToZUpRotation * FQuat(RotationDNA); DNATransform.SetRotation(TransformRotation); DNATransform.SetTranslation(FlippedTranslation); FTransform AbsoluteSpine3Inverse = CombinedMissingJointTransform.Inverse(); FTransform LocalTransform = DNATransform * AbsoluteSpine3Inverse; JointNode->SetCustomLocalTransform(&NodeContainer, LocalTransform, bResetCache); JointNode->SetCustomTimeZeroLocalTransform(&NodeContainer, LocalTransform, bResetCache); JointNode->SetCustomBindPoseLocalTransform(&NodeContainer, LocalTransform, bResetCache); } } //Add the joint specialized type JointNode->AddSpecializedType(UE::Interchange::FSceneNodeStaticData::GetJointSpecializeTypeString()); JointNode->SetDisplayLabel(NodeName); } return true; } FString UMetaHumanInterchangeDnaTranslator::GetJointHierarchyName(TSharedPtr InDNAReader, int32 JointIndex) const { TArray UniqueIDTokens; int32 ParentIndex = JointIndex; do { UniqueIDTokens.Add(InDNAReader->GetJointName(ParentIndex)); JointIndex = ParentIndex; ParentIndex = InDNAReader->GetJointParentIndex(ParentIndex); } while (ParentIndex != JointIndex); // Add missing joints (in reverse order, root being the last token added) for (auto it = UE::MetaHuman::DNAMissingJoints.rbegin(); it != UE::MetaHuman::DNAMissingJoints.rend(); ++it) { UniqueIDTokens.Add(*it); } FString UniqueID; for (int32 TokenIndex = UniqueIDTokens.Num() - 1; TokenIndex >= 0; TokenIndex--) { UniqueID += UniqueIDTokens[TokenIndex]; if (TokenIndex > 0) { UniqueID += TEXT("."); } } return UniqueID; } FString UMetaHumanInterchangeDnaTranslator::AddDNAMissingJoints(UInterchangeBaseNodeContainer& NodeContainer, const FString& InLastNodeId, FTransform& OutCombinedTransform) const { FString Heirarchy = ""; FString LastNodeId = InLastNodeId; TMap MissingTransforms; // It is assumed that the Transform values for pelvis, spine_01, spine_02 and spine_03 are set // and will not change. And that for imported head these values are the same for all MetaHumans. // The values below were obtained inspecting the archetype skelmesh editor. // BEWARE! The pitch/roll/yaw in skelmesh editor and in C++ DO NOT MATCH! The mapping is: // X = Y, Y = Z, Z = X FTransform Pelvis; FRotator Rotation(87.947094, 90.0, 90.0); Pelvis.SetRotation(Rotation.Quaternion()); Pelvis.SetTranslation(FVector(0.0, 2.094849, 87.070755)); FTransform Spine01; Rotation = FRotator(-0.000213, 10.950073, 0.0); Spine01.SetRotation(Rotation.Quaternion()); Spine01.SetTranslation(FVector(2.031172, -0.104403, 0.0)); FTransform Spine02; Rotation = FRotator(0.0, -7.320824, 0.0); Spine02.SetRotation(Rotation.Quaternion()); Spine02.SetTranslation(FVector(4.267596, 0.0, 0.0)); FTransform Spine03; Rotation = FRotator(-0.000361, -9.506168, 0.0); Spine03.SetRotation(Rotation.Quaternion()); Spine03.SetTranslation(FVector(6.75445, 0.0, 0.0)); MissingTransforms.Add("pelvis", Pelvis); MissingTransforms.Add("spine_01", Spine01); MissingTransforms.Add("spine_02", Spine02); MissingTransforms.Add("spine_03", Spine03); OutCombinedTransform = Spine03 * Spine02 * Spine01 * Pelvis; for (const FString& MissingJoint : UE::MetaHuman::DNAMissingJoints) { Heirarchy = Heirarchy.IsEmpty() ? MissingJoint : Heirarchy + "." + MissingJoint; UInterchangeSceneNode* JointNode = NewObject(&NodeContainer, NAME_None); NodeContainer.SetupNode(JointNode, Heirarchy, MissingJoint, EInterchangeNodeContainerType::TranslatedScene, LastNodeId); JointNode->SetDisplayLabel(MissingJoint); JointNode->SetCustomLocalTransform(&NodeContainer, FTransform::Identity); //Add the joint specialized type JointNode->AddSpecializedType(UE::Interchange::FSceneNodeStaticData::GetJointSpecializeTypeString()); LastNodeId = Heirarchy; FTransform DNATransform = FTransform::Identity; if (MissingTransforms.Contains(MissingJoint)) { DNATransform = *MissingTransforms.Find(MissingJoint); } JointNode->SetCustomLocalTransform(&NodeContainer, DNATransform, false /*bResetCache*/); JointNode->SetCustomTimeZeroLocalTransform(&NodeContainer, DNATransform, false /*bResetCache*/); JointNode->SetCustomBindPoseLocalTransform(&NodeContainer, DNATransform, false /*bResetCache*/); } return LastNodeId; } TOptional UMetaHumanInterchangeDnaTranslator::GetMeshPayloadData(const FInterchangeMeshPayLoadKey& PayLoadKey, const UE::Interchange::FAttributeStorage& PayloadAttributes) const { using namespace UE::Interchange; FTransform MeshGlobalTransform; PayloadAttributes.GetAttribute(UE::Interchange::FAttributeKey{ MeshPayload::Attributes::MeshGlobalTransform }, MeshGlobalTransform); TOptional Result; UE::Interchange::FMeshPayloadData MeshPayLoadData; if (FetchMeshPayloadData(PayLoadKey.UniqueId, MeshGlobalTransform, MeshPayLoadData)) { if (!FStaticMeshOperations::ValidateAndFixData(MeshPayLoadData.MeshDescription, PayLoadKey.UniqueId)) { UInterchangeResultError_Generic* ErrorResult = AddMessage(); ErrorResult->SourceAssetName = SourceData ? SourceData->GetFilename() : FString(); ErrorResult->Text = LOCTEXT("GetMeshPayloadData_ValidateMeshDescriptionFail", "Invalid mesh data (NAN) was found and fix to zero. Mesh render can be bad."); } Result.Emplace(MeshPayLoadData); } return Result; } bool UMetaHumanInterchangeDnaTranslator::FetchMeshPayloadData(const FString& PayloadKey, const FTransform& MeshGlobalTransform, UE::Interchange::FMeshPayloadData& OutMeshPayloadData) const { if (!DNAReader) { UInterchangeResultError_Generic* Message = AddMessage(); Message->Text = LOCTEXT("FetchMeshPayloadInternal_DNAReader_isNULL", "Cannot fetch mesh payload because the DNA reader is null."); return false; } if (!PayloadContexts.Contains(PayloadKey)) { UInterchangeResultError_Generic* Message = AddMessage(); Message->Text = LOCTEXT("CannotRetrievePayloadContext", "Cannot retrieve payload; payload key doesn't have any context."); return false; } TSharedPtr& PayloadContext = PayloadContexts.FindChecked(PayloadKey); return PayloadContext->FetchMeshPayload(DNAReader, MeshGlobalTransform, OutMeshPayloadData); } bool FDnaMeshPayloadContext::FetchMeshPayload(TSharedPtr InDNAReader, const FTransform& MeshGlobalTransform, UE::Interchange::FMeshPayloadData& OutMeshPayloadData) { if (FetchMeshPayloadInternal(InDNAReader, MeshGlobalTransform, OutMeshPayloadData.MeshDescription, OutMeshPayloadData.JointNames)) { return true; } return false; } bool FDnaMeshPayloadContext::FetchMeshPayloadInternal(TSharedPtr InDNAReader, const FTransform& MeshGlobalTransform, FMeshDescription& OutMeshDescription, TArray& OutJointNames) const { if (DnaMeshIndex != INDEX_NONE) { PopulateStaticMeshDescription(OutMeshDescription, *InDNAReader.Get(), DnaMeshIndex); // Apply the skin weights FSkeletalMeshAttributes SkeletalMeshAttributes(OutMeshDescription); SkeletalMeshAttributes.Register(true); //Add the influence data in the skeletal mesh description FSkinWeightsVertexAttributesRef VertexSkinWeights = SkeletalMeshAttributes.GetVertexSkinWeights(); using namespace UE::AnimationCore; TMap> RawBoneWeights; int32 NumSkinVerts = InDNAReader->GetVertexPositionCount(DnaMeshIndex); for (int32 SkinVert = 0; SkinVert < NumSkinVerts; ++SkinVert) { TArrayView JointIndices = InDNAReader->GetSkinWeightsJointIndices(DnaMeshIndex, SkinVert); TArrayView VertexWeights = InDNAReader->GetSkinWeightsValues(DnaMeshIndex, SkinVert); TArray VertexBoneWeights; for (int32 JointIndexCtr = 0; JointIndexCtr < JointIndices.Num(); ++JointIndexCtr) { float SkinWeightValue = VertexWeights[JointIndexCtr]; int32 CurrentBoneIndex = JointIndices[JointIndexCtr]; VertexBoneWeights.Add(FBoneWeight(CurrentBoneIndex, SkinWeightValue)); } RawBoneWeights.Add(SkinVert, VertexBoneWeights); } // Add all the raw bone weights. This will cause the weights to be sorted and re-normalized after culling to max influences. for (const TTuple>& Item : RawBoneWeights) { VertexSkinWeights.Set(Item.Key, Item.Value); } int32 JointCount = InDNAReader->GetJointCount(); for (int32 JointIndex = 0; JointIndex < JointCount; JointIndex++) { FString JointName = InDNAReader->GetJointName(JointIndex); OutJointNames.Add(JointName); } return true; } return false; } void FDnaMeshPayloadContext::PopulateStaticMeshDescription(FMeshDescription& OutMeshDescription, const IDNAReader& InDNAReader, const int32 InMeshIndex) { FStaticMeshAttributes Attributes(OutMeshDescription); Attributes.Register(); OutMeshDescription.SuspendVertexInstanceIndexing(); OutMeshDescription.SuspendEdgeIndexing(); OutMeshDescription.SuspendPolygonIndexing(); OutMeshDescription.SuspendPolygonGroupIndexing(); OutMeshDescription.SuspendUVIndexing(); // The code to populate static mesh description was adapted from InterchangeOBJTranslator. // Similarly, the MeshDescription vertex and UV element buffers are in the same order as the .DNA TArray VertexIndexMapping; const int32 VertexIndexMappingNum = InDNAReader.GetVertexPositionCount(InMeshIndex); VertexIndexMapping.Init(0, VertexIndexMappingNum); for (int32 i = 0; i < VertexIndexMappingNum; i++) { VertexIndexMapping[i] = i; } // Create vertices and initialize positions TVertexAttributesRef MeshPositions = Attributes.GetVertexPositions(); OutMeshDescription.ReserveNewVertices(VertexIndexMapping.Num()); for (int32 ObjVertexIndex : VertexIndexMapping) { FVertexID VertexIndex = OutMeshDescription.CreateVertex(); if (MeshPositions.GetRawArray().IsValidIndex(VertexIndex)) { FVector3f& Position = Attributes.GetVertexPositions()[VertexIndex]; Position = FVector3f(InDNAReader.GetVertexPosition(InMeshIndex, ObjVertexIndex)); } } OutMeshDescription.SetNumUVChannels(1); TArray UVIndexMapping; const int32 UVIndexMappingNum = InDNAReader.GetVertexTextureCoordinateCount(InMeshIndex); UVIndexMapping.Init(0, UVIndexMappingNum); for (int32 i = 0; i < UVIndexMappingNum; i++) { UVIndexMapping[i] = i; } // Create UVs and initialize values const int32 UVChannel = 0; OutMeshDescription.ReserveNewUVs(UVIndexMapping.Num()); for (int32 ObjUVIndex : UVIndexMapping) { FUVID UVIndex = OutMeshDescription.CreateUV(UVChannel); FTextureCoordinate ObjUV = InDNAReader.GetVertexTextureCoordinate(InMeshIndex, ObjUVIndex); Attributes.GetUVCoordinates(UVChannel)[UVIndex] = FVector2f(ObjUV.U, ObjUV.V); } FPolygonGroupID PolygonGroupIndex = OutMeshDescription.CreatePolygonGroup(); const FString MeshName = InDNAReader.GetMeshName(InMeshIndex); const FString MaterialName = UE::MetaHuman::MaterialSlotsMapping.Contains(MeshName) ? UE::MetaHuman::MaterialSlotsMapping[MeshName] : MeshName + TEXT("_shader"); Attributes.GetPolygonGroupMaterialSlotNames()[PolygonGroupIndex] = FName(MaterialName); const int32 NumOfFaces = InDNAReader.GetFaceCount(InMeshIndex); OutMeshDescription.ReserveNewTriangles(NumOfFaces); OutMeshDescription.ReserveNewPolygons(NumOfFaces); TArray> VertexInstanceIDs; auto UVToUEBasis = [](const FVector2f& InVector) { return FVector2f(InVector.X, 1.0f - InVector.Y); }; TVertexInstanceAttributesRef VertexColor = Attributes.GetVertexInstanceColors(); UDNAMeshVertexColorDataAsset* ColorAsset = LoadObject(nullptr, FaceColorMaskAssetPath); for (int32 FaceIndex = 0; FaceIndex < NumOfFaces; ++FaceIndex) { VertexInstanceIDs.Reset(); TArray VertexInstanceIds; TArrayView FaceLayout = InDNAReader.GetFaceVertexLayoutIndices(InMeshIndex, FaceIndex); OutMeshDescription.ReserveNewVertexInstances(FaceLayout.Num()); for (uint32 FaceLayoutIndex : FaceLayout) { FVertexLayout VertexData = InDNAReader.GetVertexLayout(InMeshIndex, FaceLayoutIndex); FVertexID VertexID = Algo::BinarySearch(VertexIndexMapping, VertexData.Position); FVertexInstanceID VertexInstanceID = OutMeshDescription.CreateVertexInstance(VertexID); VertexInstanceIDs.Add(VertexInstanceID); if (VertexData.Normal != INDEX_NONE) { FVector3f& Normal = Attributes.GetVertexInstanceNormals()[VertexInstanceID]; Normal = FVector3f(InDNAReader.GetVertexNormal(InMeshIndex, VertexData.Normal)); } if (VertexData.TextureCoordinate != INDEX_NONE) { auto ObjUVVal = InDNAReader.GetVertexTextureCoordinate(InMeshIndex, VertexData.TextureCoordinate); Attributes.GetVertexInstanceUVs()[VertexInstanceID] = UVToUEBasis(FVector2f(ObjUVVal.U, ObjUVVal.V)); } VertexColor[VertexInstanceID] = ColorAsset->GetColorByMeshAndIndex(MeshName, VertexID); } OutMeshDescription.CreatePolygon(PolygonGroupIndex, VertexInstanceIDs); } OutMeshDescription.ResumeVertexInstanceIndexing(); OutMeshDescription.ResumeEdgeIndexing(); OutMeshDescription.ResumePolygonIndexing(); OutMeshDescription.ResumePolygonGroupIndexing(); OutMeshDescription.ResumeUVIndexing(); } bool FDnaMorphTargetPayloadContext::FetchMeshPayload(TSharedPtr InDNAReader, const FTransform& MeshGlobalTransform, UE::Interchange::FMeshPayloadData& OutMeshPayloadData) { return FetchMeshPayloadInternal(InDNAReader, MeshGlobalTransform, OutMeshPayloadData.MeshDescription); } bool FDnaMorphTargetPayloadContext::FetchMeshPayloadInternal(TSharedPtr InDNAReader, const FTransform& MeshGlobalTransform, FMeshDescription& OutMorphTargetMeshDescription) const { if (DnaMeshIndex == INDEX_NONE) { UE_LOGFMT(InterchangeDNATranslator, Error, "Unknown mesh index for morph target import."); return false; } const FString MorphTargetName = InDNAReader->GetBlendShapeChannelName(DnaChannelIndex); //Import the MorphTarget FSkeletalMeshAttributes MeshAttributes(OutMorphTargetMeshDescription); MeshAttributes.Register(); //Extract the points into a simplified MeshDescription { OutMorphTargetMeshDescription.SuspendVertexIndexing(); // Create vertices TVertexAttributesRef VertexPositions = MeshAttributes.GetVertexPositions(); int32 NumOfVerts = InDNAReader->GetVertexPositionCount(DnaMeshIndex); const int32 VertexOffset = OutMorphTargetMeshDescription.Vertices().Num(); // The below code expects Num() to be equivalent to GetArraySize(), i.e. that all added elements are appended, not inserted into existing gaps check(VertexOffset == OutMorphTargetMeshDescription.Vertices().GetArraySize()); //Fill the vertex array OutMorphTargetMeshDescription.ReserveNewVertices(NumOfVerts); TArray VertexIDs; VertexIDs.Reserve(NumOfVerts); for (int32 VertexIndex = 0; VertexIndex < NumOfVerts; ++VertexIndex) { const int32 RealVertexIndex = VertexOffset + VertexIndex; FVertexID AddedVertexId = OutMorphTargetMeshDescription.CreateVertex(); if (AddedVertexId.GetValue() != RealVertexIndex) { UE_LOGFMT(InterchangeDNATranslator, Error, "Cannot create valid vertex for the morph target '{MorphTargetName}'."); return false; } const FVector VertPosition = InDNAReader->GetVertexPosition(DnaMeshIndex, VertexIndex); const FVector3f VertexPosition = FVector3f(VertPosition); VertexPositions[AddedVertexId] = VertexPosition; OutMorphTargetMeshDescription.CreateVertexInstance(AddedVertexId); VertexIDs.Add(AddedVertexId); } // First get all DNA deltas for current Morph Target. TArrayView BlendShapeVertexIndices = InDNAReader->GetBlendShapeTargetVertexIndices(DnaMeshIndex, DnaMorphTargetIndex); const int32 DeltaCount = BlendShapeVertexIndices.Num(); // Add morph target deltas to vertex arrays. for (int32 DeltaIndex = 0; DeltaIndex < DeltaCount; ++DeltaIndex) { const int32 RealVertexIndex = VertexOffset + DeltaIndex; const FVector3f DeltaPosition = FVector3f(InDNAReader->GetBlendShapeTargetDelta(DnaMeshIndex, DnaMorphTargetIndex, DeltaIndex)); //Add delta to get the final morph target position. if (VertexIDs.IsValidIndex(BlendShapeVertexIndices[DeltaIndex])) { const FVertexID MorphTargetVertexID = VertexIDs[BlendShapeVertexIndices[DeltaIndex]]; VertexPositions[MorphTargetVertexID] += DeltaPosition; } } OutMorphTargetMeshDescription.ResumeVertexIndexing(); } return true; } #undef LOCTEXT_NAMESPACE