// Copyright Epic Games, Inc. All Rights Reserved. #include "MetaHumanCharacterSkelMeshUtils.h" #include "Engine/SkeletalMesh.h" #include "SkelMeshDNAUtils.h" #include "DNAAsset.h" #include "DNAUtils.h" #include "Rendering/SkeletalMeshModel.h" #include "Rendering/SkeletalMeshLODModel.h" #include "AnimationRuntime.h" #include "InterchangeDnaModule.h" #include "MetaHumanRigEvaluatedState.h" #include "MetaHumanCoreTechMeshUtils.h" #include "Animation/AnimCurveMetadata.h" #include "Animation/AnimBlueprint.h" #include "Animation/AnimInstance.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Materials/Material.h" #include "Materials/MaterialInstanceDynamic.h" #include "Misc/FileHelper.h" #include "Logging/StructuredLog.h" #include "AssetToolsModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "Interfaces/IPluginManager.h" #include "Framework/Application/SlateApplication.h" #include "Subsystem/MetaHumanCharacterBuild.h" #include "Subsystem/MetaHumanCharacterSkinMaterials.h" #include "Engine/SkeletalMeshLODSettings.h" #include "ControlRigBlueprintLegacy.h" #include "PhysicsEngine/PhysicsAsset.h" #include "SkeletalMeshAttributes.h" #include "Async/ParallelFor.h" #include "VectorUtil.h" #include "MetaHumanCharacter.h" #include "MetaHumanCharacterEditorLog.h" #include "UI/Widgets/DNAImportDialogWidget.h" #include "MetaHumanCommonDataUtils.h" static FAutoConsoleCommand CVarImportDNA( TEXT("MH.Import.DNA"), TEXT("Launches the DNA import dialog."), FConsoleCommandDelegate::CreateLambda([]() { TArray OutFiles; TSharedRef Window = SNew(SDNAImportDialogWidget); FSlateApplication::Get().AddModalWindow(Window, FGlobalTabmanager::Get()->GetRootWindow()); const FString DNAPath = Window->GetFilePath(); const FString FileName = Window->GetImportName(); const FString ImportPath = TEXT("/Game/ImportedMesh"); TArray DNADataAsBuffer; if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *DNAPath)) { TSharedPtr DNAReader = ReadDNAFromBuffer(&DNADataAsBuffer, EDNADataLayer::All); if (DNAReader) { EMetaHumanImportDNAType ImportType = Window->GetMeshType() == "Face" ? EMetaHumanImportDNAType::Face : EMetaHumanImportDNAType::Body; EMetaHumanCharacterSkinPreviewMaterial MaterialType = *Window->GetSelectedMaterial().Get(); const FString UniqueAssetName = MakeUniqueObjectName(GetTransientPackage(), USkeletalMesh::StaticClass(), FName{ FileName }, EUniqueObjectNameOptions::GloballyUnique).ToString(); USkeletalMesh* SkelMeshAsset = FMetaHumanCharacterSkelMeshUtils::GetSkeletalMeshAssetFromDNA(DNAReader, ImportPath, UniqueAssetName, ImportType); FMetaHumanCharacterSkelMeshUtils::PopulateSkelMeshData(SkelMeshAsset, DNAReader, true); const bool bUseMaterialsWithVTSupport = false; FMetaHumanCharacterFaceMaterialSet Materials = FMetaHumanCharacterSkinMaterials::GetHeadPreviewMaterialInstance(MaterialType, bUseMaterialsWithVTSupport); FMetaHumanCharacterSkinMaterials::SetHeadMaterialsOnMesh(Materials, SkelMeshAsset); if (MaterialType == EMetaHumanCharacterSkinPreviewMaterial::Clay) { Materials.ForEachSkinMaterial( [](EMetaHumanCharacterSkinMaterialSlot, UMaterialInstanceDynamic* Material) { Material->SetScalarParameterValue(TEXT("ClayMaterial"), 1.0f); } ); } } } }) ); namespace UE::MetaHuman { static TAutoConsoleVariable CVarMHCRebuildMeshDescriptionAfterInterchange { TEXT("mh.Character.RebuildMeshDescriptionAfterInterchange"), true, TEXT("Set to true to force an update of the skeletal mesh description after it has been imported through the DNA interchange."), ECVF_Default }; template static void UpdateLODModelVertexPositions( USkeletalMesh* InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals, const IdentityState& InState, const FDNAToSkelMeshMap* InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption, TObjectPtr InMergedHeadAndBody, const UE::MetaHuman::FMergedMeshMapping::FVertexMap* InMergedMeshVertexMap, bool bInUpdateMergedMeshNormals) { FSkeletalMeshModel* ImportedModel = InSkelMesh->GetImportedModel(); // Expects vertex map to be initialized beforehand int32 LODStart = 0; int32 LODRangeSize = 1; switch (InUpdateOption) { case ELodUpdateOption::LOD0Only: // the default break; case ELodUpdateOption::LOD0AndLOD1Only: LODRangeSize = FMath::Min(2, ImportedModel->LODModels.Num()); break; case ELodUpdateOption::LOD1AndHigher: LODStart = 1; LODRangeSize = ImportedModel->LODModels.Num(); break; case ELodUpdateOption::All: LODRangeSize = ImportedModel->LODModels.Num(); break; default: check(false); } for (int32 LODIndex = LODStart; LODIndex < LODRangeSize; LODIndex++) { FMeshDescription* MergedMeshDescription = nullptr; const TArray* OriginalMeshToMergedMesh = nullptr; TVertexAttributesRef MergedVertexPositions; TVertexInstanceAttributesRef MergedInstanceNormals; if (InMergedHeadAndBody && InMergedMeshVertexMap && ensure(InMergedMeshVertexMap->OriginalLODVertexMap.IsValidIndex(LODIndex))) { const FMergedMeshMapping::FLODVertexMap& LODMap = InMergedMeshVertexMap->OriginalLODVertexMap[LODIndex]; if (LODMap.IsValid() && InMergedHeadAndBody->IsValidLODIndex(LODMap.MergedMeshLODIndex) && InMergedHeadAndBody->HasMeshDescription(LODMap.MergedMeshLODIndex)) { MergedMeshDescription = InMergedHeadAndBody->GetMeshDescription(LODMap.MergedMeshLODIndex); FSkeletalMeshAttributes Attributes(*MergedMeshDescription); MergedVertexPositions = Attributes.GetVertexPositions(); MergedInstanceNormals = Attributes.GetVertexInstanceNormals(); OriginalMeshToMergedMesh = &LODMap.OriginalMeshToMergedMesh; } } FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; int32 SectionIndex = 0; int32 NumInvalidSourceVertexIndices = 0; for (FSkelMeshSection& Section : LODModel.Sections) { auto SetNormalForSoftSkinVertex = [ InVertexUpdateOption, bInUpdateMergedMeshNormals, MergedMeshDescription, OriginalMeshToMergedMesh, &MergedVertexPositions, &MergedInstanceNormals, &NumInvalidSourceVertexIndices, &LODModel, SectionBaseVertexIndex = Section.BaseVertexIndex ]( const FVector3f& InPosition, const FVector3f& InNormal, int32 InVertexIndex, FSoftSkinVertex& OutVertex) { if (InVertexUpdateOption == EVertexPositionsAndNormals::PositionOnly || InVertexUpdateOption == EVertexPositionsAndNormals::Both) { OutVertex.Position = InPosition; } if (InVertexUpdateOption == EVertexPositionsAndNormals::NormalsOnly || InVertexUpdateOption == EVertexPositionsAndNormals::Both) { // Normalize the input normal to ensure it's unit length. FVector3f TangentZVector = InNormal.GetSafeNormal(); // Store the normal and handedness (always right-handed) in TangentZ. // Note that TangentX and TangentY will be regenerated OutVertex.TangentZ = FVector4f(TangentZVector, /*Handedness*/-1.0f); } if (MergedMeshDescription) { const int32 SourceVertexIndex = SectionBaseVertexIndex + InVertexIndex; if (LODModel.GetRawPointIndices().IsValidIndex(SourceVertexIndex)) { // The vertex in the original face or body mesh const int32 OriginalMeshVertexIndex = LODModel.GetRawPointIndices()[SourceVertexIndex]; // OriginalMeshToMergedMesh should contain this index if it was constructed correctly if (ensure(OriginalMeshToMergedMesh->IsValidIndex(OriginalMeshVertexIndex))) { // The vertex in the merged face and body mesh const int32 MergedTargetVertexIndex = (*OriginalMeshToMergedMesh)[OriginalMeshVertexIndex]; const FVertexID MergedVertexID(MergedTargetVertexIndex); if (InVertexUpdateOption == EVertexPositionsAndNormals::PositionOnly || InVertexUpdateOption == EVertexPositionsAndNormals::Both) { if (ensure(MergedTargetVertexIndex < MergedVertexPositions.GetNumElements())) { MergedVertexPositions.Set(MergedVertexID, OutVertex.Position); } } if (bInUpdateMergedMeshNormals && (InVertexUpdateOption == EVertexPositionsAndNormals::NormalsOnly || InVertexUpdateOption == EVertexPositionsAndNormals::Both)) { const TArrayView VertexInstances = MergedMeshDescription->GetVertexVertexInstanceIDs(MergedVertexID); for (const FVertexInstanceID Instance : VertexInstances) { if (ensure(Instance.GetValue() < MergedInstanceNormals.GetNumElements())) { MergedInstanceNormals.Set(Instance, OutVertex.TangentZ); } } } } } else { NumInvalidSourceVertexIndices++; } } }; const int32 DNAMeshIndex = InDNAToSkelMeshMap->ImportVtxToDNAMeshIndex[LODIndex][Section.GetVertexBufferIndex()]; const int32 NumSoftVertices = Section.GetNumVertices(); const TArray>& OverlappingMap = InDNAToSkelMeshMap->OverlappingVertices[LODIndex][SectionIndex]; int32 VertexBufferIndex = Section.GetVertexBufferIndex(); const TArray& ImportVtxToDNAVtxIndex = InDNAToSkelMeshMap->ImportVtxToDNAVtxIndex[LODIndex]; for (int32 VertexIndex = 0; VertexIndex < NumSoftVertices; VertexIndex++) { const int32 DNAVertexIndex = ImportVtxToDNAVtxIndex[VertexBufferIndex]; if (DNAVertexIndex >= 0) { const FVector3f Position = InState.GetVertex(InVerticesAndNormals.Vertices, DNAMeshIndex, DNAVertexIndex); const FVector3f Normal = InState.GetVertex(InVerticesAndNormals.VertexNormals, DNAMeshIndex, DNAVertexIndex); SetNormalForSoftSkinVertex(Position, Normal, VertexIndex, Section.SoftVertices[VertexIndex]); // Check if the current vertex has overlapping vertices, and then update them as well. const TArray& OverlappedIndices = OverlappingMap[VertexIndex]; for (int32 OverlappingVertexIndex : OverlappedIndices) { SetNormalForSoftSkinVertex(Position, Normal, OverlappingVertexIndex, Section.SoftVertices[OverlappingVertexIndex]); } } VertexBufferIndex++; } SectionIndex++; } if (NumInvalidSourceVertexIndices > 0) { UE_LOGFMT(LogMetaHumanCharacterEditor, Error, "Failed to update merged mesh: Original mesh has {NumInvalidSourceVertexIndices} invalid raw point indices", NumInvalidSourceVertexIndices); } } } static void UpdateJoints(USkeletalMesh* InSkelMesh, IDNAReader* InDNAReader, FDNAToSkelMeshMap* InDNAToSkelMeshMap, EMetaHumanCharacterOrientation InCharacterOrientation) { { // Scoping of RefSkelModifier FReferenceSkeletonModifier RefSkelModifier(InSkelMesh->GetRefSkeleton(), InSkelMesh->GetSkeleton()); // copy here TArray RawBonePose = InSkelMesh->GetRefSkeleton().GetRawRefBonePose(); // calculate component space ahead of current transform TArray ComponentTransforms; FAnimationRuntime::FillUpComponentSpaceTransforms(InSkelMesh->GetRefSkeleton(), RawBonePose, ComponentTransforms); const TArray& RawBoneInfo = InSkelMesh->GetRefSkeleton().GetRawRefBoneInfo(); // Skipping root joint (index 0) to avoid blinking of the mesh due to bounding box issue for (uint16 JointIndex = 0; JointIndex < InDNAReader->GetJointCount(); JointIndex++) { int32 BoneIndex = InDNAToSkelMeshMap->GetUEBoneIndex(JointIndex); FTransform DNATransform = FTransform::Identity; // Updating bind pose affects just translations. FVector Translate = InDNAReader->GetNeutralJointTranslation(JointIndex); FVector RotationVector = InDNAReader->GetNeutralJointRotation(JointIndex); FRotator Rotation(RotationVector.X, RotationVector.Y, RotationVector.Z); if (InDNAReader->GetJointParentIndex(JointIndex) == JointIndex) // This is the highest joint of the dna - not necessarily the UE root bone { if (InCharacterOrientation == EMetaHumanCharacterOrientation::Y_UP) { FQuat YUpToZUpRotation = FQuat(FRotator(0, 0, 90)); FQuat ComponentRotation = YUpToZUpRotation * FQuat(Rotation); DNATransform.SetTranslation(FVector(Translate.X, Translate.Z, -Translate.Y)); DNATransform.SetRotation(ComponentRotation); } else if (InCharacterOrientation == EMetaHumanCharacterOrientation::Z_UP) { DNATransform.SetTranslation(Translate); DNATransform.SetRotation(Rotation.Quaternion()); } else { check(false); } ComponentTransforms[BoneIndex] = DNATransform; } else { DNATransform.SetTranslation(Translate); DNATransform.SetRotation(Rotation.Quaternion()); if (ensure(RawBoneInfo[BoneIndex].ParentIndex != INDEX_NONE)) { ComponentTransforms[BoneIndex] = DNATransform * ComponentTransforms[RawBoneInfo[BoneIndex].ParentIndex]; } } ComponentTransforms[BoneIndex].NormalizeRotation(); } for (uint16 BoneIndex = 0; BoneIndex < RawBoneInfo.Num(); BoneIndex++) { FTransform LocalTransform; if (BoneIndex == 0) { LocalTransform = ComponentTransforms[BoneIndex]; } else { LocalTransform = ComponentTransforms[BoneIndex].GetRelativeTransform(ComponentTransforms[RawBoneInfo[BoneIndex].ParentIndex]); } LocalTransform.NormalizeRotation(); RefSkelModifier.UpdateRefPoseTransform(BoneIndex, LocalTransform); } } InSkelMesh->GetRefBasesInvMatrix().Reset(); InSkelMesh->CalculateInvRefMatrices(); // Needs to be called after RefSkelModifier is destroyed } static FVector3f GetOrientatedPosition(const FVector& InPosition, EMetaHumanCharacterOrientation InCharacterOrientation) { FVector3f OutPosition; if (InCharacterOrientation == EMetaHumanCharacterOrientation::Y_UP) { OutPosition = FVector3f{ InPosition }; } else if (InCharacterOrientation == EMetaHumanCharacterOrientation::Z_UP) { OutPosition[0] = InPosition.X; OutPosition[1] = -InPosition.Z; OutPosition[2] = InPosition.Y; } else { check(false); } return OutPosition; } static void UpdateBaseMesh(USkeletalMesh* InSkelMesh, IDNAReader* InDNAReader, FDNAToSkelMeshMap* InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EMetaHumanCharacterOrientation InCharacterOrientation) { FSkeletalMeshModel* ImportedModel = InSkelMesh->GetImportedModel(); int32 LODStart = 0; int32 LODRangeSize = ImportedModel->LODModels.Num(); if (InUpdateOption == ELodUpdateOption::LOD1AndHigher) { LODStart = 1; } else if (InUpdateOption == ELodUpdateOption::LOD0Only && LODRangeSize > 0) { LODRangeSize = 1; } else if (InUpdateOption == ELodUpdateOption::LOD0Only && LODRangeSize > 1) { LODRangeSize = 2; } // Expects vertex map to be initialized beforehand for (int32 LODIndex = LODStart; LODIndex < LODRangeSize; LODIndex++) { FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; int32 SectionIndex = 0; for (FSkelMeshSection& Section : LODModel.Sections) { int32& DNAMeshIndex = InDNAToSkelMeshMap->ImportVtxToDNAMeshIndex[LODIndex][Section.GetVertexBufferIndex()]; const int32 NumSoftVertices = Section.GetNumVertices(); auto& OverlappingMap = InDNAToSkelMeshMap->OverlappingVertices[LODIndex][SectionIndex]; int32 VertexBufferIndex = Section.GetVertexBufferIndex(); for (int32 VertexIndex = 0; VertexIndex < NumSoftVertices; VertexIndex++) { int32& DNAVertexIndex = InDNAToSkelMeshMap->ImportVtxToDNAVtxIndex[LODIndex][VertexBufferIndex]; if (DNAVertexIndex >= 0) { const FVector Position = InDNAReader->GetVertexPosition(DNAMeshIndex, DNAVertexIndex); FSoftSkinVertex& Vertex = Section.SoftVertices[VertexIndex]; Vertex.Position = GetOrientatedPosition(Position, InCharacterOrientation); // Check if the current vertex has overlapping vertices, and then update them as well. TArray& OverlappedIndices = OverlappingMap[VertexIndex]; int32 OverlappingCount = OverlappedIndices.Num(); for (int32 OverlappingIndex = 0; OverlappingIndex < OverlappingCount; ++OverlappingIndex) { int32 OverlappingVertexIndex = OverlappedIndices[OverlappingIndex]; FSoftSkinVertex& OverlappingVertex = Section.SoftVertices[OverlappingVertexIndex]; OverlappingVertex.Position = GetOrientatedPosition(Position, InCharacterOrientation); } } VertexBufferIndex++; } SectionIndex++; } } } struct DuplicateUVInfo { FVector3f Position; TArray NeighbouringUVs; }; void GetTemplateUVsAndPositions(FMeshDescription& InTemplateMeshDescription, TSet* InVertexIDFilter, TArray& OutTemplateUVs, TArray& OutTemplatePositions, TMap>& OutDuplicateUVInfos) { OutTemplateUVs.Empty(); OutTemplatePositions.Empty(); OutDuplicateUVInfos.Empty(); FStaticMeshAttributes Attributes(InTemplateMeshDescription); TVertexInstanceAttributesConstRef UVs = Attributes.GetVertexInstanceUVs(); TVertexAttributesRef MeshVertices = Attributes.GetVertexPositions(); TMap FirstUVIndexMap; TArray UVIndexToVertexId; TMap> VertexIdToUVIndex; TMap> DuplicateUVIndices; for (const FVertexInstanceID VertexInstanceID : InTemplateMeshDescription.VertexInstances().GetElementIDs()) { const FVertexID VertexID = InTemplateMeshDescription.GetVertexInstanceVertex(VertexInstanceID); if (InVertexIDFilter && !InVertexIDFilter->Contains(VertexID)) { continue; } constexpr int32 UVChannel = 0; const FVector2f UV = UVs.Get(VertexInstanceID, UVChannel); const FVector3f Position = MeshVertices.Get(VertexID); const int32 UVIndex = OutTemplateUVs.Num(); OutTemplateUVs.Add(UV); OutTemplatePositions.Add(Position); // Look for duplicates and build indices map if (int32* FoundFirstUVIndexPtr = FirstUVIndexMap.Find(UV)) { int32 FoundFirstUVIndex = *FoundFirstUVIndexPtr; if (OutTemplatePositions[FoundFirstUVIndex] != Position) { TArray& DupicatedIndices = DuplicateUVIndices.FindOrAdd(UV); if (DupicatedIndices.IsEmpty()) { DupicatedIndices.Add(FoundFirstUVIndex); } DupicatedIndices.Add(UVIndex); } } else { FirstUVIndexMap.Add(UV, UVIndex); } UVIndexToVertexId.Add(VertexID); VertexIdToUVIndex.FindOrAdd(VertexID).Add(UVIndex); } // Build UV info containing neighbouring UVs for each set of duplicates for (const TPair>& DuplicateUVGroup : DuplicateUVIndices) { FVector2f UV = DuplicateUVGroup.Key; TArray& DuplicateInfosGroup = OutDuplicateUVInfos.Emplace(UV); for (int32 UVIndex : DuplicateUVGroup.Value) { UE::MetaHuman::DuplicateUVInfo UVInfo; UVInfo.Position = OutTemplatePositions[UVIndex]; FVertexID VertexID = UVIndexToVertexId[UVIndex]; for (const FEdgeID EdgeID : InTemplateMeshDescription.GetVertexConnectedEdgeIDs(VertexID)) { const FVertexID Vertex0 = InTemplateMeshDescription.GetEdgeVertex(EdgeID, 0); const FVertexID Vertex1 = InTemplateMeshDescription.GetEdgeVertex(EdgeID, 1); FVertexID NeighbourVertexID = (Vertex0 == VertexID) ? Vertex1 : Vertex0; TArray NeighbourUVIndices = VertexIdToUVIndex[NeighbourVertexID]; for (int32 NeighbourUVIndex : NeighbourUVIndices) { UVInfo.NeighbouringUVs.Add(OutTemplateUVs[NeighbourUVIndex]); } } DuplicateInfosGroup.Add(UVInfo); } } } int32 CountSharedUVs(const TArray& A, const TArray& B) { TSet SetA(A); int32 Count = 0; for (const FVector2f& Value : B) { if (SetA.Contains(Value)) { ++Count; } } return Count; } bool GetUVCorrespondingVertices( const TArray& InTemplateUVs, const TArray& InTemplateVertexPositions, const TMap>& InDuplicateUVInfos, const TSharedPtr& InArchetypeDNAReader, int32 InDNAMeshIndex, TArray& OutVertices) { OutVertices.Empty(); if (InDNAMeshIndex >= InArchetypeDNAReader->GetMeshCount()) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("DNA archetype does not contain mesh for index %d"), InDNAMeshIndex); return false; } const uint32 NumVertices = InArchetypeDNAReader->GetVertexPositionCount(InDNAMeshIndex); if (NumVertices == 0) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("DNA archetype contains no vertices for mesh index %d"), InDNAMeshIndex); return false; } const int32 NumUVs = InArchetypeDNAReader->GetVertexTextureCoordinateCount(InDNAMeshIndex); if (NumUVs == 0) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("DNA archetype contains no UVs for mesh index %d"), InDNAMeshIndex); return false; } // Get UVs from the archetype in dna UV order and keep a mapping from dna position to UV indices TArray ArchetypeUVs; ArchetypeUVs.Init({}, NumUVs); TArray UVSet; UVSet.Init(false, NumUVs); TMap DNAPositionToUVIndices; DNAPositionToUVIndices.Reserve(NumUVs); TArray DNAPositionsWithDuplicateUVs; for (int32 UVIndex = 0; UVIndex < NumUVs; UVIndex++) { FVertexLayout VertexLayout = InArchetypeDNAReader->GetVertexLayout(InDNAMeshIndex, UVIndex); const int32 DNAPositionIndex = VertexLayout.Position; const int32 DNATextureCoordinateIndex = VertexLayout.TextureCoordinate; const FTextureCoordinate DNATextureCoord = InArchetypeDNAReader->GetVertexTextureCoordinate(InDNAMeshIndex, DNATextureCoordinateIndex); // Skel Mesh UV buffer is mirrored in V to DNA. See conversion from DNA to UE basis in MetaHumanInterchangeDnaTranslator.cpp const FVector2f ArchetypeUV = FVector2f(DNATextureCoord.U, 1.0f - DNATextureCoord.V); ArchetypeUVs[DNATextureCoordinateIndex] = ArchetypeUV; UVSet[DNATextureCoordinateIndex] = true; DNAPositionToUVIndices.Emplace(DNAPositionIndex, DNATextureCoordinateIndex); if (InDuplicateUVInfos.Contains(ArchetypeUV)) { DNAPositionsWithDuplicateUVs.Add(DNAPositionIndex); } } // Check map has populated all vertex positions if (DNAPositionToUVIndices.Num() != NumVertices) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("DNA archetype has invalid vertex layout for mesh index %d. Number of vertex positions does not match number referenced by vertex layout."), InDNAMeshIndex); return false; } // Check UVs have been set for each texture coordinate index int32 NumSetUVs = 0; for (int32 V = 0; V < UVSet.Num(); ++V) { if (UVSet[V]) { NumSetUVs++; } } if (NumSetUVs != NumUVs) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("DNA archetype has invalid vertex layout for mesh index %d. Number of texture coordinates does not match number referenced by vertex layout."), InDNAMeshIndex); return false; } // Get template indices in DNA UV order by finding closest corresponding UVs TArray> TemplateUVIndices = UE::MetaHuman::GetClosestUVIndices(InTemplateUVs, ArchetypeUVs); // Pre-compute DNA neighbours. Indices are DNA positions TMap> PositionNeighbours = UE::MetaHuman::GetNeighbouringVertices(InArchetypeDNAReader, InDNAMeshIndex, DNAPositionsWithDuplicateUVs); // Iterate in DNA vertex order and get the template vertex position using the corresponding indices OutVertices.AddUninitialized(NumVertices); for (uint32 DNAPositionIndex = 0; DNAPositionIndex < NumVertices; DNAPositionIndex++) { int32 DNATextureCoordIndex = DNAPositionToUVIndices[DNAPositionIndex]; float Dist = TemplateUVIndices[DNATextureCoordIndex].Value; if (Dist > UE_KINDA_SMALL_NUMBER) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("UV mismatch. Closest vertex to DNA Position Index %d is above UV distance threshold: %f"), DNAPositionIndex, Dist); OutVertices.Empty(); return false; } int32 TemplateMeshIndex = TemplateUVIndices[DNATextureCoordIndex].Key; OutVertices[DNAPositionIndex] = InTemplateVertexPositions[TemplateMeshIndex]; // Check if there are duplicates and update position using neighbours FVector2f ArchetypeUV = ArchetypeUVs[DNATextureCoordIndex]; if (const TArray* DuplicateUVInfos = InDuplicateUVInfos.Find(ArchetypeUV)) { TArray NeighbouringUVs; if (const TArray* NeighbourPositions = PositionNeighbours.Find(DNAPositionIndex)) { for (int32 NeighbourPositionIndex : *NeighbourPositions) { if (int32* NeighbourDNATextureCoordIndex = DNAPositionToUVIndices.Find(NeighbourPositionIndex)) { NeighbouringUVs.Add(ArchetypeUVs[*NeighbourDNATextureCoordIndex]); } } } int32 MaxMatchingNeighboursCount = 0; bool bMatchedPosition = false; FVector3f BestMatchedPosition = {}; for (const DuplicateUVInfo& CandidateUVInfo : *DuplicateUVInfos) { int NumMatchingNeighbours = CountSharedUVs(CandidateUVInfo.NeighbouringUVs, NeighbouringUVs); if (NumMatchingNeighbours > MaxMatchingNeighboursCount) { MaxMatchingNeighboursCount = NumMatchingNeighbours; BestMatchedPosition = CandidateUVInfo.Position; bMatchedPosition = true; } } if (bMatchedPosition) { OutVertices[DNAPositionIndex] = BestMatchedPosition; } } } return true; } } void FMetaHumanCharacterSkelMeshUtils::UpdateSkelMeshFromDNA(TSharedRef InDNAReader, EUpdateFlags InUpdateFlags, TSharedRef& InOutDNAToSkelMeshMap, EMetaHumanCharacterOrientation InCharacterOrientation, TNotNull OutSkeletalMesh) { // The order of execution in this function is fairly important and is split in 3 steps: // 1. The USkelMeshDNAUtils update the Import Model LOD data of the Skeletal Mesh since this is the reference for the DNA vertex map // 2. The Mesh Description of the Skeletal Mesh is updated from the Import Model so that the internal mesh state is in sync with the changes // 3. The Skeleta Mesh is built with the DDC & render data being fully updated // // TODO: Note that it is not necessary to do update the whole mesh; the process could be simplified by updating the Mesh Description // directly from the DNA and potentially not re-building the entire Skeletal Mesh instead only the required parts of the cache/DDC if (EnumHasAllFlags(InUpdateFlags, EUpdateFlags::Joints)) { InOutDNAToSkelMeshMap->MapJoints(&InDNAReader.Get()); UE::MetaHuman::UpdateJoints(OutSkeletalMesh, &InDNAReader.Get(), &InOutDNAToSkelMeshMap.Get(), InCharacterOrientation); } if (EnumHasAllFlags(InUpdateFlags, EUpdateFlags::BaseMesh)) { UE::MetaHuman::UpdateBaseMesh(OutSkeletalMesh, &InDNAReader.Get(), &InOutDNAToSkelMeshMap.Get(), ELodUpdateOption::All, InCharacterOrientation); } if (EnumHasAllFlags(InUpdateFlags, EUpdateFlags::SkinWeights)) { USkelMeshDNAUtils::UpdateSkinWeights(OutSkeletalMesh, &InDNAReader.Get(), &InOutDNAToSkelMeshMap.Get(), ELodUpdateOption::All); } if (EnumHasAnyFlags(InUpdateFlags, EUpdateFlags::DNABehavior | EUpdateFlags::DNAGeometry)) { // Set the Behavior part of DNA in skeletal mesh AssetUserData if (UAssetUserData* UserData = OutSkeletalMesh->GetAssetUserDataOfClass(UDNAAsset::StaticClass())) { UDNAAsset* DNAAsset = CastChecked(UserData); if (EnumHasAllFlags(InUpdateFlags, EUpdateFlags::DNABehavior)) { DNAAsset->SetBehaviorReader(InDNAReader); } if (EnumHasAllFlags(InUpdateFlags, EUpdateFlags::DNAGeometry)) { DNAAsset->SetGeometryReader(InDNAReader); } } } // Skeletal mesh has changed, so mark it as dirty //OutSkeletalMesh->Modify(); OutSkeletalMesh->MarkPackageDirty(); // Commit a Mesh Descriptions for each ImportModel LOD UpdateMeshDescriptionFromLODModel(OutSkeletalMesh); //OutSkeletalMesh->InvalidateDeriveDataCacheGUID(); OutSkeletalMesh->PostEditChange(); // Update the DNA vertex map since building the Skeletal Mesh can result in re-ordering of the render vertices InOutDNAToSkelMeshMap = TSharedRef(USkelMeshDNAUtils::CreateMapForUpdatingNeutralMesh(OutSkeletalMesh)); } void FMetaHumanCharacterSkelMeshUtils::UpdateMeshDescriptionFromLODModel(TNotNull InSkeletalMesh) { for (int32 LODIndex = 0; LODIndex < InSkeletalMesh->GetImportedModel()->LODModels.Num(); ++LODIndex) { const FSkeletalMeshLODModel& LODModel = InSkeletalMesh->GetImportedModel()->LODModels[LODIndex]; FMeshDescription MeshDescription; LODModel.GetMeshDescription(InSkeletalMesh, LODIndex, MeshDescription); InSkeletalMesh->CreateMeshDescription(LODIndex, MoveTemp(MeshDescription)); InSkeletalMesh->CommitMeshDescription(LODIndex); } } void FMetaHumanCharacterSkelMeshUtils::UpdateMeshDescriptionFromLODModelVerticesNormalsAndTangents(TNotNull InSkeletalMesh) { // based on void FSkeletalMeshLODModel::GetMeshDescription(const USkeletalMesh * InSkeletalMesh, const int32 InLODIndex, FMeshDescription & OutMeshDescription) const TArray IsUpdated; IsUpdated.AddDefaulted(InSkeletalMesh->GetImportedModel()->LODModels.Num()); // run all LODs in parallel as USkeletalMesh::CommitMeshDescription is threadsafe ParallelFor(InSkeletalMesh->GetImportedModel()->LODModels.Num(), [&](int32 LODIndex) { IsUpdated[LODIndex] = false; const FSkeletalMeshLODModel& LODModel = InSkeletalMesh->GetImportedModel()->LODModels[LODIndex]; FMeshDescription* MeshDescription = InSkeletalMesh->GetMeshDescription(LODIndex); if (!MeshDescription) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("No mesh description for LOD %d"), LODIndex); return; } InSkeletalMesh->ModifyMeshDescription(LODIndex); FSkeletalMeshAttributes MeshAttributes(*MeshDescription); TVertexAttributesRef VertexPositions = MeshAttributes.GetVertexPositions(); TVertexInstanceAttributesRef VertexInstanceNormals = MeshAttributes.GetVertexInstanceNormals(); TVertexInstanceAttributesRef VertexInstanceTangents = MeshAttributes.GetVertexInstanceTangents(); TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshAttributes.GetVertexInstanceBinormalSigns(); // Map the section vertices back to the import vertices to remove seams, but only if there's // mapping available. TArray SourceToTargetVertexMap; int32 TargetVertexCount = 0; if (LODModel.GetRawPointIndices().Num() == LODModel.NumVertices) { SourceToTargetVertexMap.Reserve(LODModel.GetRawPointIndices().Num()); for (const uint32 VertexIndex : LODModel.GetRawPointIndices()) { SourceToTargetVertexMap.Add(VertexIndex); TargetVertexCount = FMath::Max(TargetVertexCount, static_cast(VertexIndex)); } TargetVertexCount += 1; } else { SourceToTargetVertexMap.Reserve(LODModel.NumVertices); for (uint32 Index = 0; Index < LODModel.NumVertices; Index++) { SourceToTargetVertexMap.Add(Index); } TargetVertexCount = LODModel.NumVertices; } if (MeshDescription->Vertices().Num() != TargetVertexCount) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Mesh Description does not match Skeletal Mesh model for LOD %d"), LODIndex); return; } // verify that the target normals, tangents, and sign match in size int32 NextVertexInstanceID = 0; for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++) { const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; NextVertexInstanceID += Section.NumTriangles * 3; } if (NextVertexInstanceID != MeshDescription->VertexInstances().Num()) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Mesh Description does not match Skeletal Mesh model for LOD %d"), LODIndex); return; } NextVertexInstanceID = 0; for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++) { const FSkelMeshSection& Section = LODModel.Sections[SectionIndex]; const TArray& SourceVertices = Section.SoftVertices; for (int32 VertexIndex = 0; VertexIndex < SourceVertices.Num(); VertexIndex++) { const int32 SourceVertexIndex = VertexIndex + Section.BaseVertexIndex; const int32 TargetVertexIndex = SourceToTargetVertexMap[SourceVertexIndex]; // the original method creates a target VertexIDs array that is incremental //const FVertexID VertexID = VertexIDs[TargetVertexIndex]; const FVertexID VertexID = FVertexID(TargetVertexIndex); VertexPositions.Set(VertexID, SourceVertices[VertexIndex].Position); } for (int32 TriangleID = 0; TriangleID < int32(Section.NumTriangles); TriangleID++) { const int32 VertexIndexBase = TriangleID * 3 + Section.BaseIndex; for (int32 Corner = 0; Corner < 3; Corner++) { const int32 SourceVertexIndex = LODModel.IndexBuffer[VertexIndexBase + Corner]; const int32 TargetVertexIndex = SourceToTargetVertexMap[SourceVertexIndex]; //const FVertexID VertexID = VertexIDs[TargetVertexIndex]; const FVertexID VertexID = FVertexID(TargetVertexIndex); const FVertexInstanceID VertexInstanceID = FVertexInstanceID(NextVertexInstanceID++); const FSoftSkinVertex& SourceVertex = SourceVertices[SourceVertexIndex - Section.BaseVertexIndex]; // set normals, tangents, and sign VertexInstanceNormals.Set(VertexInstanceID, SourceVertex.TangentZ); VertexInstanceTangents.Set(VertexInstanceID, SourceVertex.TangentX); VertexInstanceBinormalSigns.Set(VertexInstanceID, FMatrix44f( SourceVertex.TangentX.GetSafeNormal(), SourceVertex.TangentY.GetSafeNormal(), FVector3f(SourceVertex.TangentZ.GetSafeNormal()), FVector3f::ZeroVector).Determinant() < 0.0f ? -1.0f : +1.0f); } } } IsUpdated[LODIndex] = true; InSkeletalMesh->CommitMeshDescription(LODIndex); }); for (int32 LODIndex = 0; LODIndex < InSkeletalMesh->GetImportedModel()->LODModels.Num(); ++LODIndex) { if (!IsUpdated[LODIndex]) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Full mesh description update for lod %d"), LODIndex); const FSkeletalMeshLODModel& LODModel = InSkeletalMesh->GetImportedModel()->LODModels[LODIndex]; FMeshDescription MeshDescription; LODModel.GetMeshDescription(InSkeletalMesh, LODIndex, MeshDescription); InSkeletalMesh->CreateMeshDescription(LODIndex, MoveTemp(MeshDescription)); InSkeletalMesh->CommitMeshDescription(LODIndex); } } } bool FMetaHumanCharacterSkelMeshUtils::CompareDnaToSkelMeshVertices(TSharedPtr InDNAReader, TNotNull InSkeletalMesh, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, float Tolerance /*= UE_KINDA_SMALL_NUMBER*/) { const FSkeletalMeshModel* ImportedModel = InSkeletalMesh->GetImportedModel(); if (!ImportedModel) { return false; } const int32 MeshCount = InDNAReader->GetMeshCount(); for (int32 LODIndex = 0; LODIndex < InDNAReader->GetLODCount(); LODIndex++) { if (ImportedModel->LODModels.IsValidIndex(LODIndex)) { // Skeletal mesh might have fewer LODs than DNA, and that is fine. const FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; for (int32 MeshIndex = 0; MeshIndex < MeshCount; MeshIndex++) { const int32 VertexCount = InDNAReader->GetVertexPositionCount(MeshIndex); for (int32 DNAVertexIndex = 0; DNAVertexIndex < VertexCount; DNAVertexIndex++) { const int32 VertexIndex = InDNAToSkelMeshMap.ImportDNAVtxToUEVtxIndex[LODIndex][MeshIndex][DNAVertexIndex]; TArray Vertices; LODModel.GetVertices(Vertices); if (Vertices.IsValidIndex(VertexIndex)) { const FVector UpdatedPosition = InDNAReader->GetVertexPosition(MeshIndex, DNAVertexIndex); const bool bPositionsEqual = Vertices[VertexIndex].Position.Equals(FVector3f{ UpdatedPosition }, Tolerance); if (!bPositionsEqual) { // TODO: Log vertex index with mismatching position. return false; } } else { // TODO: Log mismatching vertex index/ DNA index not found. return false; } } } } } return true; } bool FMetaHumanCharacterSkelMeshUtils::CompareDnaToStateVerticesAndNormals(TSharedPtr InDNAReader, const TArray& InStateVertices, const TArray& InStateNormals, TSharedPtr InState, float Tolerance /*= UE_KINDA_SMALL_NUMBER*/) { const int32 MeshCount = InDNAReader->GetMeshCount(); for (int32 MeshIndex = 0; MeshIndex < MeshCount; MeshIndex++) { const int32 VertexCount = InDNAReader->GetVertexPositionCount(MeshIndex); for (int32 DNAVertexIndex = 0; DNAVertexIndex < VertexCount; DNAVertexIndex++) { const FVector UpdatedPosition = InDNAReader->GetVertexPosition(MeshIndex, DNAVertexIndex); const FVector3f StatePosition = InState->GetVertex(InStateVertices, MeshIndex, DNAVertexIndex); const bool bPositionsEqual = StatePosition.Equals(FVector3f{ UpdatedPosition }, Tolerance); if (!bPositionsEqual) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Vertex position mismatch at mesh %d (%s) and index %d, DNA: %f,%f,%f, State: %f,%f,%f"),MeshIndex, *InDNAReader->GetMeshName(MeshIndex), DNAVertexIndex, UpdatedPosition.X, UpdatedPosition.Y, UpdatedPosition.Z, StatePosition.X, StatePosition.Y, StatePosition.Z); return false; } const FVector UpdatedNormal = InDNAReader->GetVertexNormal(MeshIndex, DNAVertexIndex); const FVector3f StateNormal = InState->GetVertex(InStateNormals, MeshIndex, DNAVertexIndex); const bool bNormalsEqual = StateNormal.Equals(FVector3f{ UpdatedNormal }, Tolerance); if (!bNormalsEqual) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Vertex normal mismatch at mesh %d (%s) and index %d, DNA: %f,%f,%f, State: %f,%f,%f"), MeshIndex, *InDNAReader->GetMeshName(MeshIndex), DNAVertexIndex, UpdatedNormal.X, UpdatedNormal.Y, UpdatedNormal.Z, StateNormal.X, StateNormal.Y, StateNormal.Z); return false; } } } return true; } bool FMetaHumanCharacterSkelMeshUtils::CheckDNACompatibility(IDNAReader* InDnaReaderA, IDNAReader* InDnaReaderB) { if (!InDnaReaderA || !InDnaReaderB) { return false; } // Joints { const uint16 JointCountA = InDnaReaderA->GetJointCount(); const uint16 JointCountB = InDnaReaderB->GetJointCount(); // Compare joint count if (JointCountA != JointCountB) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("Joint count mismatch: %d vs %d"), JointCountA, JointCountB); return false; } bool bJointsOk = true; FStringBuilderBase ResultMsg; for (uint16 JointIndex = 0; JointIndex < JointCountA; JointIndex++) { const uint16 JointParentA = InDnaReaderA->GetJointParentIndex(JointIndex); const uint16 JointParentB = InDnaReaderB->GetJointParentIndex(JointIndex); // Compare joint names if (InDnaReaderA->GetJointName(JointIndex) != InDnaReaderB->GetJointName(JointIndex)) { ResultMsg.Appendf(TEXT("Joint name mismatch: '%s' vs '%s'"), *InDnaReaderA->GetJointName(JointParentA), *InDnaReaderB->GetJointName(JointParentB)); ResultMsg.AppendChar('\n'); bJointsOk = false; continue; } // Compare parents if (InDnaReaderA->GetJointParentIndex(JointIndex) != InDnaReaderB->GetJointParentIndex(JointIndex)) { ResultMsg.Appendf(TEXT("Joint parent mismatch for joint '%s': '%s' vs '%s'"), *InDnaReaderA->GetJointName(JointIndex), *InDnaReaderA->GetJointName(JointParentA), *InDnaReaderA->GetJointName(JointParentB)); ResultMsg.AppendChar('\n'); bJointsOk = false; } } if (!bJointsOk) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("%s"), *ResultMsg); return false; } } // Meshes { bool bMeshesOk = true; const uint16 MeshCountA = InDnaReaderA->GetMeshCount(); const uint16 MeshCountB = InDnaReaderB->GetMeshCount(); const uint16 MeshCount = FMath::Max(MeshCountA, MeshCountB); FStringBuilderBase ResultMsg; for (uint16 MeshIndex = 0; MeshIndex < MeshCount; MeshIndex++) { if (MeshIndex < MeshCountA && MeshIndex < MeshCountB) { const uint16 VertexCountA = InDnaReaderA->GetVertexPositionCount(MeshIndex); const uint16 VertexCountB = InDnaReaderB->GetVertexPositionCount(MeshIndex); // Compare vertex count if (VertexCountA != VertexCountB) { ResultMsg.Appendf(TEXT("Vertex count mismatch on mesh '%s' (mesh index: %u): %u vs %u"), *InDnaReaderA->GetMeshName(MeshIndex), MeshIndex, VertexCountA, VertexCountB); ResultMsg.AppendChar('\n'); bMeshesOk = false; } } else { break; } } if (!bMeshesOk) { UE_LOG(LogMetaHumanCharacterEditor, Warning, TEXT("%s"), *ResultMsg); return false; } } return true; } void FMetaHumanCharacterSkelMeshUtils::UpdateLODModelVertexPositions( TNotNull InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals, const FMetaHumanCharacterIdentity::FState& InState, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption, TObjectPtr InMergedHeadAndBody, const UE::MetaHuman::FMergedMeshMapping* InMergedMeshMapping, bool bInUpdateMergedMeshNormals) { UE::MetaHuman::UpdateLODModelVertexPositions( InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption, InMergedHeadAndBody, InMergedMeshMapping ? &InMergedMeshMapping->FaceVertexMap : nullptr, bInUpdateMergedMeshNormals); } void FMetaHumanCharacterSkelMeshUtils::UpdateLODModelVertexPositions( TNotNull InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals, const FMetaHumanCharacterBodyIdentity::FState& InState, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption, TObjectPtr InMergedHeadAndBody, const UE::MetaHuman::FMergedMeshMapping* InMergedMeshMapping, bool bInUpdateMergedMeshNormals) { UE::MetaHuman::UpdateLODModelVertexPositions( InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption, InMergedHeadAndBody, InMergedMeshMapping ? &InMergedMeshMapping->BodyVertexMap : nullptr, bInUpdateMergedMeshNormals); } void FMetaHumanCharacterSkelMeshUtils::UpdateBindPoseFromSource(TNotNull InSourceSkelMesh, TNotNull InTargetSkelMesh) { // Scoping of RefSkelModifier { FReferenceSkeletonModifier RefSkelModifier(InTargetSkelMesh->GetRefSkeleton(), InTargetSkelMesh->GetSkeleton()); TArray SourceRawBonePose = InSourceSkelMesh->GetRefSkeleton().GetRawRefBonePose(); TArray SourceBoneInfo = InSourceSkelMesh->GetRefSkeleton().GetRefBoneInfo(); // Set bone transforms from source pose by matching bone name for (int32 SourceBoneIndex = 0; SourceBoneIndex < SourceBoneInfo.Num(); SourceBoneIndex++) { int32 TargetBoneIndex = InTargetSkelMesh->GetRefSkeleton().FindBoneIndex(SourceBoneInfo[SourceBoneIndex].Name); if (TargetBoneIndex == INDEX_NONE) { continue; } RefSkelModifier.UpdateRefPoseTransform(TargetBoneIndex, SourceRawBonePose[SourceBoneIndex]); } } InTargetSkelMesh->GetRefBasesInvMatrix().Reset(); InTargetSkelMesh->CalculateInvRefMatrices(); // Needs to be called after RefSkelModifier is destroyed } void FMetaHumanCharacterSkelMeshUtils::EnableRecomputeTangents(TNotNull InSkelMesh) { // Code extracted from PersonaMeshDetails for Recompute Tangents update auto SetSkelMeshSourceSectionUserData = [](FSkeletalMeshLODModel& LODModel, const int32 SectionIndex, const int32 OriginalSectionIndex) { FSkelMeshSourceSectionUserData& SourceSectionUserData = LODModel.UserSectionsData.FindOrAdd(OriginalSectionIndex); SourceSectionUserData.bDisabled = LODModel.Sections[SectionIndex].bDisabled; SourceSectionUserData.bCastShadow = LODModel.Sections[SectionIndex].bCastShadow; SourceSectionUserData.bVisibleInRayTracing = LODModel.Sections[SectionIndex].bVisibleInRayTracing; SourceSectionUserData.bRecomputeTangent = LODModel.Sections[SectionIndex].bRecomputeTangent; SourceSectionUserData.RecomputeTangentsVertexMaskChannel = LODModel.Sections[SectionIndex].RecomputeTangentsVertexMaskChannel; SourceSectionUserData.GenerateUpToLodIndex = LODModel.Sections[SectionIndex].GenerateUpToLodIndex; SourceSectionUserData.CorrespondClothAssetIndex = LODModel.Sections[SectionIndex].CorrespondClothAssetIndex; SourceSectionUserData.ClothingData = LODModel.Sections[SectionIndex].ClothingData; }; // Green mask for recompute tangents is currently set to LODs [0-3] int32 LODNumberInMesh = InSkelMesh->GetImportedModel()->LODModels.Num(); int32 LODsForRecompute = LODNumberInMesh > 4 ? 4 : LODNumberInMesh; for (int32 LODIndex = 0; LODIndex < LODsForRecompute; ++LODIndex) { FSkeletalMeshModel* ImportedModel = InSkelMesh->GetImportedModel(); if (!ImportedModel || LODIndex >= ImportedModel->LODModels.Num()) { UE_LOGFMT(LogMetaHumanCharacterEditor, Warning, "No imported model data for LOD {LODIndex}", LODIndex); continue; } FSkeletalMeshLODInfo* LODInfo = InSkelMesh->GetLODInfo(LODIndex); LODInfo->SkinCacheUsage = ESkinCacheUsage::Enabled; FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; // Recompute tangents from green mask is only valid for section with skin FSkelMeshSection& Section = LODModel.Sections[0]; Section.bRecomputeTangent = true; Section.RecomputeTangentsVertexMaskChannel = ESkinVertexColorChannel::Green; SetSkelMeshSourceSectionUserData(LODModel, 0, Section.OriginalDataSectionIndex); } InSkelMesh->Build(); InSkelMesh->PostEditChange(); InSkelMesh->InitResources(); } void FMetaHumanCharacterSkelMeshUtils::PopulateSkelMeshData(TNotNull InSkelMesh, TSharedPtr InDNAReader, bool bIsFaceMesh) { FInterchangeDnaModule::GetModule().SetSkelMeshDNAData(InSkelMesh, InDNAReader); constexpr EMetaHumanCharacterTemplateType TemplateType = EMetaHumanCharacterTemplateType::MetaHuman; if (bIsFaceMesh) { // Assign the physics asset to the newly create skeletal mesh InSkelMesh->SetPhysicsAsset(GetFaceArchetypePhysicsAsset(TemplateType)); // Assign the LOD Settings to the face mesh InSkelMesh->SetLODSettings(GetFaceArchetypeLODSettings(TemplateType)); // Assign the Face Board Control Rig InSkelMesh->SetDefaultAnimatingRig(GetFaceArchetypeDefaultAnimatingRig(TemplateType)); TArray& MeshMaterials = InSkelMesh->GetMaterials(); for (FSkeletalMaterial& Material : MeshMaterials) { const FString Name = Material.MaterialSlotName.ToString(); // TODO: Do this in a proper way through MetaHumanCharacteSkinMaterials. if (Name == "eyeshell_shader_shader") { UMaterialInterface* EyeShellMaterial = LoadObject(nullptr, TEXT("/Script/Engine.MaterialInstanceConstant'/" UE_PLUGIN_NAME "/Lookdev_UHM/Eye/Materials/MI_eye_occlusion_unified.MI_eye_occlusion_unified'")); Material.MaterialInterface = EyeShellMaterial; } else if (!Name.Contains("head") && !Name.Contains("teeth") && !Name.Contains("eyeLeft") && !Name.Contains("eyeRight") && !Name.Contains("body") && !Name.Contains("combined")) { UMaterialInterface* EmptyMaterial = LoadObject(nullptr, TEXT("/Script/Engine.MaterialInstanceConstant'/" UE_PLUGIN_NAME "/Materials/M_Hide.M_Hide'")); Material.MaterialInterface = EmptyMaterial; } } EnableRecomputeTangents(InSkelMesh); } else { InSkelMesh->SetLODSettings(GetBodyArchetypeLODSettings(TemplateType)); InSkelMesh->SetDefaultAnimatingRig(GetBodyArchetypeDefaultAnimatingRig(TemplateType)); InSkelMesh->PostEditChange(); } } TArray FMetaHumanCharacterSkelMeshUtils::GetComponentSpaceJointTranslations(TNotNull InSkelMesh) { TArray RawBonePose = InSkelMesh->GetRefSkeleton().GetRawRefBonePose(); TArray ComponentTransforms; FAnimationRuntime::FillUpComponentSpaceTransforms(InSkelMesh->GetRefSkeleton(), RawBonePose, ComponentTransforms); TArray ComponentSpaceTranslations; ComponentSpaceTranslations.AddUninitialized(ComponentTransforms.Num()); for (int32 I = 0; I < ComponentTransforms.Num(); I++) { FVector ComponentTranslation = ComponentTransforms[I].GetTranslation(); ComponentSpaceTranslations[I] = FVector3f(ComponentTranslation.X, ComponentTranslation.Y, ComponentTranslation.Z); } return ComponentSpaceTranslations; } bool FMetaHumanCharacterSkelMeshUtils::GetStaticMeshVertices(const TNotNull InTemplateStaticMesh, int32 InLodIndex, TArray& OutVertices) { OutVertices.Empty(); FMeshDescription* MeshDescription = InTemplateStaticMesh->GetMeshDescription(InLodIndex); if (!MeshDescription) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("Template mesh does not contain mesh description for Lod %d"), InLodIndex); return false; } FStaticMeshAttributes Attributes(*MeshDescription); TVertexAttributesRef TemplateMeshVertexPositions = Attributes.GetVertexPositions(); OutVertices.Reset(TemplateMeshVertexPositions.GetNumElements()); for (int32 Index = 0; Index < TemplateMeshVertexPositions.GetNumElements(); ++Index) { const FVector3f OriginalVertex = TemplateMeshVertexPositions.Get(Index); OutVertices.Add(OriginalVertex); } return true; } bool FMetaHumanCharacterSkelMeshUtils::GetSkeletalMeshVertices( const TNotNull InTemplateSkeletalMesh, int32 InLodIndex, const TNotNull InDNAToSkelMeshMap, int32 InDNAMeshIndex, TArray& OutVertices) { OutVertices.Empty(); OutVertices.AddUninitialized(InDNAToSkelMeshMap->ImportDNAVtxToUEVtxIndex[InLodIndex][InDNAMeshIndex].Num()); const FSkeletalMeshLODModel& LODModel = InTemplateSkeletalMesh->GetImportedModel()->LODModels[InLodIndex]; TArray VerticesSet; VerticesSet.Init(false, OutVertices.Num()); int32 TotalNumSoftVertices = 0; for (const FSkelMeshSection& Section : LODModel.Sections) { TotalNumSoftVertices += Section.GetNumVertices(); } for (const FSkelMeshSection& Section : LODModel.Sections) { const int32& DNAMeshIndex = InDNAToSkelMeshMap->ImportVtxToDNAMeshIndex[InLodIndex][Section.GetVertexBufferIndex()]; if (DNAMeshIndex == InDNAMeshIndex) { const int32 NumSoftVertices = Section.GetNumVertices(); int32 VertexBufferIndex = Section.GetVertexBufferIndex(); for (int32 VertexIndex = 0; VertexIndex < NumSoftVertices; VertexIndex++) { const int32& DNAVertexIndex = InDNAToSkelMeshMap->ImportVtxToDNAVtxIndex[InLodIndex][VertexBufferIndex++]; if (DNAVertexIndex >= 0 && DNAVertexIndex < OutVertices.Num()) { const FSoftSkinVertex& Vertex = Section.SoftVertices[VertexIndex]; OutVertices[DNAVertexIndex] = FVector3f{ Vertex.Position.X, Vertex.Position.Y, Vertex.Position.Z }; VerticesSet[DNAVertexIndex] = true; } else { return false; } } } } // Check all vertices have been set for (int32 V = 0; V < VerticesSet.Num(); ++V) { if (!VerticesSet[V]) { return false; } } return true; } bool FMetaHumanCharacterSkelMeshUtils::GetUVCorrespondingStaticMeshVertices( const TNotNull InTemplateStaticMesh, int32 InLodIndex, const TSharedPtr& InArchetypeDNAReader, int32 InDNAMeshIndex, TArray& OutVertices) { OutVertices.Empty(); FMeshDescription* MeshDescription = InTemplateStaticMesh->GetMeshDescription(InLodIndex); if (!MeshDescription) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("Template mesh does not contain mesh description for Lod %d"), InLodIndex); return false; } TArray TemplateUVs; TArray TemplateVertexPositions; TMap> DuplicateUVInfos; UE::MetaHuman::GetTemplateUVsAndPositions(*MeshDescription, nullptr, TemplateUVs, TemplateVertexPositions, DuplicateUVInfos); return UE::MetaHuman::GetUVCorrespondingVertices(TemplateUVs, TemplateVertexPositions, DuplicateUVInfos, InArchetypeDNAReader, InDNAMeshIndex, OutVertices); } bool FMetaHumanCharacterSkelMeshUtils::GetUVCorrespondingSkeletalMeshVertices( const TNotNull InTemplateSkelMesh, int32 InLodIndex, const TNotNull InDNAToSkelMeshMap, const TSharedPtr& InArchetypeDNAReader, int32 InDNAMeshIndex, TArray& OutVertices) { OutVertices.Empty(); FSkeletalMeshModel* ImportedModel = InTemplateSkelMesh->GetImportedModel(); if (InLodIndex > ImportedModel->LODModels.Num() - 1) { UE_LOG(LogMetaHumanCharacterEditor, Error, TEXT("Template mesh does not contain LOD %d"), InLodIndex); return false; } // Get UVs and positions from template mesh const FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[InLodIndex]; FMeshDescription MeshDescription; LODModel.GetMeshDescription(InTemplateSkelMesh, InLodIndex, MeshDescription); // Create a vertex id filter to only check vertices that map to the input DNA mesh index TSet VertexIdsFilter; { TArray SourceToTargetVertexMap; if (LODModel.GetRawPointIndices().Num() == LODModel.NumVertices) { SourceToTargetVertexMap.Reserve(LODModel.GetRawPointIndices().Num()); for (const uint32 VertexIndex : LODModel.GetRawPointIndices()) { SourceToTargetVertexMap.Add(VertexIndex); } } else { SourceToTargetVertexMap.Reserve(LODModel.NumVertices); for (uint32 Index = 0; Index < LODModel.NumVertices; Index++) { SourceToTargetVertexMap.Add(Index); } } for (const FSkelMeshSection& Section : LODModel.Sections) { const int32 DNAMeshIndex = InDNAToSkelMeshMap->ImportVtxToDNAMeshIndex[InLodIndex][Section.GetVertexBufferIndex()]; if (DNAMeshIndex == InDNAMeshIndex) { const uint32 NumSoftVertices = Section.GetNumVertices(); for (uint32 VertexIndex = 0; VertexIndex < NumSoftVertices; VertexIndex++) { const FSoftSkinVertex& SoftVertex = Section.SoftVertices[VertexIndex]; const uint32 SourceVertexIndex = VertexIndex + Section.BaseVertexIndex; const uint32 TargetVertexIndex = SourceToTargetVertexMap[SourceVertexIndex]; const FVertexID VertexID = FVertexID(TargetVertexIndex); VertexIdsFilter.Add(VertexID); } } } } TArray TemplateUVs; TArray TemplateVertexPositions; TMap> DuplicateUVInfos; UE::MetaHuman::GetTemplateUVsAndPositions(MeshDescription, &VertexIdsFilter, TemplateUVs, TemplateVertexPositions, DuplicateUVInfos); return UE::MetaHuman::GetUVCorrespondingVertices(TemplateUVs, TemplateVertexPositions, DuplicateUVInfos, InArchetypeDNAReader, InDNAMeshIndex, OutVertices); } USkeletalMesh* FMetaHumanCharacterSkelMeshUtils::GetSkeletalMeshAssetFromDNA(const TSharedPtr& InDNAReader, const FString& InAssetPath, const FString& InAssetName, const EMetaHumanImportDNAType InImportDNAType) { FInterchangeDnaModule& DNAImportModule = FInterchangeDnaModule::GetModule(); USkeleton* Skeleton = nullptr; if (InImportDNAType == EMetaHumanImportDNAType::Face) { Skeleton = LoadObject(nullptr, FMetaHumanCommonDataUtils::GetCharacterPluginFaceSkeletonPath()); } else if (InImportDNAType == EMetaHumanImportDNAType::Body) { Skeleton = LoadObject(nullptr, FMetaHumanCommonDataUtils::GetCharacterPluginBodySkeletonPath()); } if (USkeletalMesh* SkelMeshAsset = DNAImportModule.ImportSync(InAssetName, InAssetPath, InDNAReader, Skeleton)) { // Interchange system doesn't make an asset transient when the transient path is supplied if (InAssetPath.Contains("Engine/Transient") || InAssetPath.Contains("Engine.Transient")) { SkelMeshAsset->GetPackage()->SetFlags(RF_Transient); } if (UE::MetaHuman::CVarMHCRebuildMeshDescriptionAfterInterchange.GetValueOnAnyThread()) { // This seems to clear any extra allocated data for blend shapes in the mesh description during the interchange calls UpdateMeshDescriptionFromLODModel(SkelMeshAsset); // This frees around 1.5GB of memory after importing the mesh from DNA CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } return SkelMeshAsset; } return nullptr; } USkeletalMesh* FMetaHumanCharacterSkelMeshUtils::CreateArchetypeSkelMeshFromDNA(const EMetaHumanImportDNAType InImportDNAType, TSharedPtr& OutArchetypeDnaReader) { USkeletalMesh* SkelMeshAsset = nullptr; const FString DNAPath = FMetaHumanCommonDataUtils::GetArchetypeDNAPath(InImportDNAType); TArray DNADataAsBuffer; if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *DNAPath)) { FString ArchetypeAssetName = GetTransientArchetypeMeshAssetName(InImportDNAType); OutArchetypeDnaReader = ReadDNAFromBuffer(&DNADataAsBuffer, EDNADataLayer::All); if (OutArchetypeDnaReader) { const FString UniqueAssetName = MakeUniqueObjectName(GetTransientPackage(), USkeletalMesh::StaticClass(), FName{ ArchetypeAssetName }, EUniqueObjectNameOptions::GloballyUnique).ToString(); SkelMeshAsset = GetSkeletalMeshAssetFromDNA(OutArchetypeDnaReader, TEXT("/Engine/Transient"), UniqueAssetName, InImportDNAType); } } return SkelMeshAsset; } UDNAAsset* FMetaHumanCharacterSkelMeshUtils::GetArchetypeDNAAseet(const EMetaHumanImportDNAType InImportDNAType, UObject* InOuter) { const FString DNAPath = FMetaHumanCommonDataUtils::GetArchetypeDNAPath(InImportDNAType); return GetDNAAssetFromFile(DNAPath, InOuter); } FString FMetaHumanCharacterSkelMeshUtils::GetTransientArchetypeMeshAssetName(const EMetaHumanImportDNAType InImportDNAType) { FString ArchetypeAssetName; switch (InImportDNAType) { case EMetaHumanImportDNAType::Face: ArchetypeAssetName = TEXT("Face"); break; case EMetaHumanImportDNAType::Body: ArchetypeAssetName = TEXT("Body"); break; case EMetaHumanImportDNAType::Combined: ArchetypeAssetName = TEXT("Combined"); break; default: ArchetypeAssetName = TEXT("Default"); break; } return ArchetypeAssetName; } UPhysicsAsset* FMetaHumanCharacterSkelMeshUtils::GetFaceArchetypePhysicsAsset(EMetaHumanCharacterTemplateType InTemplateType) { UPhysicsAsset* FaceArchetypePhysics = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { FaceArchetypePhysics = LoadObject(nullptr, TEXT("/Script/Engine.PhysicsAsset'/" UE_PLUGIN_NAME "/Face/PHYS_Face.PHYS_Face'")); } return FaceArchetypePhysics; } USkeletalMeshLODSettings* FMetaHumanCharacterSkelMeshUtils::GetFaceArchetypeLODSettings(EMetaHumanCharacterTemplateType InTemplateType) { USkeletalMeshLODSettings* FaceArchetypeLODSettings = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { FaceArchetypeLODSettings = LoadObject(nullptr, TEXT("/Script/Engine.SkeletalMeshLODSettings'/" UE_PLUGIN_NAME "/Face/Face_LODSettings.Face_LODSettings'")); } return FaceArchetypeLODSettings; } UControlRigBlueprint* FMetaHumanCharacterSkelMeshUtils::GetFaceArchetypeDefaultAnimatingRig(EMetaHumanCharacterTemplateType InTemplateType) { UControlRigBlueprint* FaceArchetypeDefaultAnimatingRig = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { const TSoftObjectPtr FaceboardControlRigAsset = FMetaHumanCommonDataUtils::GetDefaultFaceControlRig(FMetaHumanCommonDataUtils::GetCharacterPluginFaceControlRigPath()); if (!FaceboardControlRigAsset.IsNull()) { FaceArchetypeDefaultAnimatingRig = Cast(FaceboardControlRigAsset.LoadSynchronous()); } } return FaceArchetypeDefaultAnimatingRig; } UPhysicsAsset* FMetaHumanCharacterSkelMeshUtils::GetBodyArchetypePhysicsAsset(EMetaHumanCharacterTemplateType InTemplateType) { UPhysicsAsset* BodyArchetypePhysics = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { BodyArchetypePhysics = LoadObject(nullptr, TEXT("/Script/Engine.PhysicsAsset'/" UE_PLUGIN_NAME "/Body/IdentityTemplate/PHYS_Body.PHYS_Body'")); } return BodyArchetypePhysics; } USkeletalMeshLODSettings* FMetaHumanCharacterSkelMeshUtils::GetBodyArchetypeLODSettings(EMetaHumanCharacterTemplateType InTemplateType) { USkeletalMeshLODSettings* BodyArchetypeLODSettings = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { BodyArchetypeLODSettings = LoadObject(nullptr, TEXT("/Script/Engine.SkeletalMeshLODSettings'/" UE_PLUGIN_NAME "/Body/IdentityTemplate/Body_LODSettings.Body_LODSettings'")); } return BodyArchetypeLODSettings; } UControlRigBlueprint* FMetaHumanCharacterSkelMeshUtils::GetBodyArchetypeDefaultAnimatingRig(EMetaHumanCharacterTemplateType InTemplateType) { UControlRigBlueprint* BodyArchetypeDefaultAnimatingRig = nullptr; if (ensureAlways(InTemplateType == EMetaHumanCharacterTemplateType::MetaHuman)) { BodyArchetypeDefaultAnimatingRig = LoadObject(nullptr, TEXT("/Script/ControlRigDeveloper.ControlRigBlueprint'/" UE_PLUGIN_NAME "/Common/MetaHuman_ControlRig.MetaHuman_ControlRig'")); } return BodyArchetypeDefaultAnimatingRig; }