Files
UnrealEngine/Engine/Plugins/MetaHuman/MetaHumanSDK/Source/InterchangeDNA/Private/MetaHumanInterchangeDnaTranslator.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

822 lines
34 KiB
C++

// 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<FString> DNAMissingJoints = { "root", "pelvis", "spine_01", "spine_02", "spine_03" };
static const TMap<FString, FString> 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<FString> UMetaHumanInterchangeDnaTranslator::GetSupportedFormats() const
{
TArray<FString> 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<uint8> DNADataAsBuffer;
if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *FilePath))
{
const_cast<UMetaHumanInterchangeDnaTranslator*>(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<TPair<FString, FString>> 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<const uint16> 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<UInterchangeMeshNode>(NodeContainer.GetNode(MeshUniqueId));
UInterchangeMeshNode* MeshNode = nullptr;
if (ExistingMeshNode)
{
//This mesh node was already created.
continue;
}
MeshNode = NewObject<UInterchangeMeshNode>(&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<FString> JointNodeUniqueIDs;
for (int32 VertexIndex = 0; VertexIndex < MeshVertexCount; VertexIndex++)
{
TArrayView<const uint16> 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<FDnaMeshPayloadContext> DnaMeshPayload = MakeShared<FDnaMeshPayloadContext>();
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<const UInterchangeMeshNode>(NodeContainer.GetNode(MorphTargetUniqueID));
if (!ExistingMorphTargetNode)
{
UInterchangeMeshNode* MorphTargetNode = NewObject<UInterchangeMeshNode>(&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<FDnaMorphTargetPayloadContext> DnaMorphTargetPayload = MakeShared<FDnaMorphTargetPayloadContext>();
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<UInterchangeSceneNode>(&NodeContainer, NAME_None);
const FString RootNodeUid = TEXT("RootNode");
const FString RootNodeName = RootNodeUid;
NodeContainer.SetupNode(RootNode, RootNodeUid, RootNodeName, EInterchangeNodeContainerType::TranslatedScene);
UInterchangeSceneNode* CurrentMeshNode = NewObject<UInterchangeSceneNode>(&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<UInterchangeSceneNode>(&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<UInterchangeSceneNode>(&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<const uint16> 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<UInterchangeSceneNode>(NodeContainer.GetNode(NodeUniqueId));
if (ExistingSceneNode)
{
//This scene node was already created.
continue;
}
UInterchangeSceneNode* SceneNode = NewObject<UInterchangeSceneNode>(&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<UInterchangeSceneNode>(&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<IDNAReader> InDNAReader, int32 JointIndex) const
{
TArray<FString> 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<FString, FTransform> 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<UInterchangeSceneNode>(&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<UE::Interchange::FMeshPayloadData> 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<UE::Interchange::FMeshPayloadData> Result;
UE::Interchange::FMeshPayloadData MeshPayLoadData;
if (FetchMeshPayloadData(PayLoadKey.UniqueId, MeshGlobalTransform, MeshPayLoadData))
{
if (!FStaticMeshOperations::ValidateAndFixData(MeshPayLoadData.MeshDescription, PayLoadKey.UniqueId))
{
UInterchangeResultError_Generic* ErrorResult = AddMessage<UInterchangeResultError_Generic>();
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<UInterchangeResultError_Generic>();
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<UInterchangeResultError_Generic>();
Message->Text = LOCTEXT("CannotRetrievePayloadContext", "Cannot retrieve payload; payload key doesn't have any context.");
return false;
}
TSharedPtr<FDnaPayloadContextBase>& PayloadContext = PayloadContexts.FindChecked(PayloadKey);
return PayloadContext->FetchMeshPayload(DNAReader, MeshGlobalTransform, OutMeshPayloadData);
}
bool FDnaMeshPayloadContext::FetchMeshPayload(TSharedPtr<IDNAReader> InDNAReader, const FTransform& MeshGlobalTransform, UE::Interchange::FMeshPayloadData& OutMeshPayloadData)
{
if (FetchMeshPayloadInternal(InDNAReader, MeshGlobalTransform, OutMeshPayloadData.MeshDescription, OutMeshPayloadData.JointNames))
{
return true;
}
return false;
}
bool FDnaMeshPayloadContext::FetchMeshPayloadInternal(TSharedPtr<IDNAReader> InDNAReader, const FTransform& MeshGlobalTransform, FMeshDescription& OutMeshDescription, TArray<FString>& 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<FVertexID, TArray<FBoneWeight>> RawBoneWeights;
int32 NumSkinVerts = InDNAReader->GetVertexPositionCount(DnaMeshIndex);
for (int32 SkinVert = 0; SkinVert < NumSkinVerts; ++SkinVert)
{
TArrayView<const uint16> JointIndices = InDNAReader->GetSkinWeightsJointIndices(DnaMeshIndex, SkinVert);
TArrayView<const float> VertexWeights = InDNAReader->GetSkinWeightsValues(DnaMeshIndex, SkinVert);
TArray<FBoneWeight> 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<FVertexID, TArray<FBoneWeight>>& 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<int32> 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<FVector3f> 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<int32> 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<FVertexInstanceID, TInlineAllocator<8>> VertexInstanceIDs;
auto UVToUEBasis = [](const FVector2f& InVector)
{
return FVector2f(InVector.X, 1.0f - InVector.Y);
};
TVertexInstanceAttributesRef<FVector4f> VertexColor = Attributes.GetVertexInstanceColors();
UDNAMeshVertexColorDataAsset* ColorAsset = LoadObject<UDNAMeshVertexColorDataAsset>(nullptr, FaceColorMaskAssetPath);
for (int32 FaceIndex = 0; FaceIndex < NumOfFaces; ++FaceIndex)
{
VertexInstanceIDs.Reset();
TArray<FVertexInstanceID> VertexInstanceIds;
TArrayView<const uint32> 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<IDNAReader> InDNAReader, const FTransform& MeshGlobalTransform, UE::Interchange::FMeshPayloadData& OutMeshPayloadData)
{
return FetchMeshPayloadInternal(InDNAReader, MeshGlobalTransform, OutMeshPayloadData.MeshDescription);
}
bool FDnaMorphTargetPayloadContext::FetchMeshPayloadInternal(TSharedPtr<IDNAReader> 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<FVector3f> 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<FVertexID> 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<const uint32> 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