// 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 "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/MetaHumanCharacterSkinMaterials.h" #include "Engine/SkeletalMeshLODSettings.h" #include "ControlRigBlueprint.h" #include "PhysicsEngine/PhysicsAsset.h" #include "SkeletalMeshAttributes.h" #include "Async/ParallelFor.h" #include "MetaHumanCharacter.h" #include "MetaHumanCharacterEditorLog.h" #include "UI/Widgets/DNAImportDialogWidget.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); FMetaHumanCharacterFaceMaterialSet Materials = FMetaHumanCharacterSkinMaterials::GetHeadPreviewMaterialInstance(MaterialType); FMetaHumanCharacterSkinMaterials::SetHeadMaterialsOnMesh(Materials, SkelMeshAsset); if (MaterialType == EMetaHumanCharacterSkinPreviewMaterial::Clay) { Materials.ForEachSkinMaterial( [](EMetaHumanCharacterSkinMaterialSlot, UMaterialInstanceDynamic* Material) { Material->SetScalarParameterValue(TEXT("ClayMaterial"), 1.0f); } ); } } } }) ); namespace UE::MetaHuman { void SetNormalForSoftSkinVertex( const FVector3f& InPosition, const FVector3f& InNormal, FSoftSkinVertex& OutVertex, EVertexPositionsAndNormals InVertexUpdateOption) { 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); } } template static void UpdateLODModelVertexPositions( USkeletalMesh* InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals, const IdentityState& InState, const FDNAToSkelMeshMap* InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption) { 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::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++) { FSkeletalMeshLODModel& LODModel = ImportedModel->LODModels[LODIndex]; int32 SectionIndex = 0; for (FSkelMeshSection& Section : LODModel.Sections) { const int32 DNAMeshIndex = InDNAToSkelMeshMap->ImportVtxToDNAMeshIndex[LODIndex][Section.GetVertexBufferIndex()]; const int32 NumSoftVertices = Section.GetNumVertices(); const TArray>& OverlappingMap = InDNAToSkelMeshMap->OverlappingVertices[LODIndex][SectionIndex]; int32 VertexBufferIndex = Section.GetVertexBufferIndex(); for (int32 VertexIndex = 0; VertexIndex < NumSoftVertices; VertexIndex++) { const int32 DNAVertexIndex = InDNAToSkelMeshMap->ImportVtxToDNAVtxIndex[LODIndex][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, Section.SoftVertices[VertexIndex], InVertexUpdateOption); // 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, Section.SoftVertices[OverlappingVertexIndex], InVertexUpdateOption); } } VertexBufferIndex++; } SectionIndex++; } } } 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; } // 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++; } } } } 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) { UE::MetaHuman::UpdateLODModelVertexPositions(InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption); } void FMetaHumanCharacterSkelMeshUtils::UpdateLODModelVertexPositions(TNotNull InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals, const FMetaHumanCharacterBodyIdentity::FState& InState, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption) { UE::MetaHuman::UpdateLODModelVertexPositions(InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption); } 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 } static FAssetData GetFirstAssetData(const FName& InPackageName) { const IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked(); TArray AnimBPData; AssetRegistry.GetAssetsByPackageName(InPackageName, AnimBPData); if (!AnimBPData.IsEmpty()) { return AnimBPData[0]; } return FAssetData(); } void FMetaHumanCharacterSkelMeshUtils::SetPostProcessAnimBP(TNotNull InSkelMesh, FName PackageName) { FAssetData AnimBPAsset = GetFirstAssetData(PackageName); if (AnimBPAsset.IsValid()) { if (AnimBPAsset.IsInstanceOf(UAnimBlueprint::StaticClass())) { // UE editor is going through this route UAnimBlueprint* LoadedAnimBP = Cast(AnimBPAsset.GetAsset()); InSkelMesh->SetPostProcessAnimBlueprint(LoadedAnimBP->GetAnimBlueprintGeneratedClass()); } else if (AnimBPAsset.IsInstanceOf(UAnimBlueprintGeneratedClass::StaticClass())) { // Cooked UEFN seems to be going via this route UAnimBlueprintGeneratedClass* LoadedAnimBP = Cast(AnimBPAsset.GetAsset()); InSkelMesh->SetPostProcessAnimBlueprint(LoadedAnimBP); } } else { InSkelMesh->SetPostProcessAnimBlueprint(nullptr); } } 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) { TObjectPtr DNAAsset = NewObject(InSkelMesh); DNAAsset->SetBehaviorReader(InDNAReader); DNAAsset->SetGeometryReader(InDNAReader); if (DNAAsset) { InSkelMesh->AddAssetUserData(DNAAsset); } // Update bind pose. TODO: This should be moved to skeleton creation in Interchange. const TSharedRef FaceDnaToSkelMeshMap = MakeShareable(USkelMeshDNAUtils::CreateMapForUpdatingNeutralMesh(InSkelMesh)); FaceDnaToSkelMeshMap->MapJoints(InDNAReader.Get()); UE::MetaHuman::UpdateJoints(InSkelMesh, InDNAReader.Get(), &FaceDnaToSkelMeshMap.Get(), EMetaHumanCharacterOrientation::Y_UP); constexpr EMetaHumanCharacterTemplateType TemplateType = EMetaHumanCharacterTemplateType::MetaHuman; if (bIsFaceMesh) { SetPostProcessAnimBP(InSkelMesh, TEXT("/" UE_PLUGIN_NAME "/Face/ABP_Face_PostProcess")); // 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 { SetPostProcessAnimBP(InSkelMesh, TEXT("/" UE_PLUGIN_NAME "/Body/ABP_Body_PostProcess")); InSkelMesh->SetLODSettings(GetBodyArchetypeLODSettings(TemplateType)); InSkelMesh->SetDefaultAnimatingRig(GetBodyArchetypeDefaultAnimatingRig(TemplateType)); } } 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; } USkeletalMesh* FMetaHumanCharacterSkelMeshUtils::GetSkeletalMeshAssetFromDNA(TSharedPtr& InDNAReader, const FString& InAssetPath, const FString& InAssetName, const EMetaHumanImportDNAType InImportDNAType) { FInterchangeDnaModule& DNAImportModule = FInterchangeDnaModule::GetModule(); if (USkeletalMesh* SkelMeshAsset = DNAImportModule.ImportSync(InAssetName, InAssetPath, InDNAReader, InImportDNAType)) { // 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); } return SkelMeshAsset; } return nullptr; } USkeletalMesh* FMetaHumanCharacterSkelMeshUtils::CreateArchetypeSkelMeshFromDNA(const EMetaHumanImportDNAType InImportDNAType, TSharedPtr& OutArchetypeDnaReader) { USkeletalMesh* SkelMeshAsset = nullptr; const FString DNAPath = 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 = 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; } FString FMetaHumanCharacterSkelMeshUtils::GetArchetypeDNAPath(const EMetaHumanImportDNAType InImportDNAType) { const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT(UE_PLUGIN_NAME))->GetContentDir(); FString DNAPath; switch (InImportDNAType) { case EMetaHumanImportDNAType::Face: DNAPath = PluginDir + TEXT("/Face/IdentityTemplate/SKM_Face.dna"); break; case EMetaHumanImportDNAType::Body: DNAPath = PluginDir + TEXT("/Body/IdentityTemplate/SKM_Body.dna"); break; case EMetaHumanImportDNAType::Combined: DNAPath = PluginDir + TEXT("/Body/IdentityTemplate/body_head_combined.dna"); default: break; } return DNAPath; } 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)) { FaceArchetypeDefaultAnimatingRig = LoadObject(nullptr, TEXT("/Script/ControlRigDeveloper.ControlRigBlueprint'/" UE_PLUGIN_NAME "/Face/Face_ControlBoard_CtrlRig.Face_ControlBoard_CtrlRig'")); } 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; }