Files
2025-05-18 13:04:45 +08:00

1052 lines
42 KiB
C++

// 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<FString> OutFiles;
TSharedRef<SDNAImportDialogWidget> 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<uint8> DNADataAsBuffer;
if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *DNAPath))
{
TSharedPtr<IDNAReader> 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<UMaterialInstanceDynamic>(
[](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<class IdentityState>
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<TArray<int32>>& 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<int32>& 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<FTransform> RawBonePose = InSkelMesh->GetRefSkeleton().GetRawRefBonePose();
// calculate component space ahead of current transform
TArray<FTransform> ComponentTransforms;
FAnimationRuntime::FillUpComponentSpaceTransforms(InSkelMesh->GetRefSkeleton(), RawBonePose, ComponentTransforms);
const TArray<FMeshBoneInfo>& 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<int32>& 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<IDNAReader> InDNAReader, EUpdateFlags InUpdateFlags, TSharedRef<FDNAToSkelMeshMap>& InOutDNAToSkelMeshMap, EMetaHumanCharacterOrientation InCharacterOrientation, TNotNull<USkeletalMesh*> 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<UDNAAsset>(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<FDNAToSkelMeshMap>(USkelMeshDNAUtils::CreateMapForUpdatingNeutralMesh(OutSkeletalMesh));
}
void FMetaHumanCharacterSkelMeshUtils::UpdateMeshDescriptionFromLODModel(TNotNull<USkeletalMesh*> 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<USkeletalMesh*> InSkeletalMesh)
{
// based on void FSkeletalMeshLODModel::GetMeshDescription(const USkeletalMesh * InSkeletalMesh, const int32 InLODIndex, FMeshDescription & OutMeshDescription) const
TArray<bool> 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<FVector3f> VertexPositions = MeshAttributes.GetVertexPositions();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = MeshAttributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = MeshAttributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = MeshAttributes.GetVertexInstanceBinormalSigns();
// Map the section vertices back to the import vertices to remove seams, but only if there's
// mapping available.
TArray<int32> 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<int32>(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<FSoftSkinVertex>& 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<const IDNAReader> InDNAReader, TNotNull<const USkeletalMesh*> 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<FSoftSkinVertex> 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<const IDNAReader> InDNAReader, const TArray<FVector3f>& InStateVertices, const TArray<FVector3f>& InStateNormals, TSharedPtr<const FMetaHumanCharacterIdentity::FState> 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<USkeletalMesh*> InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals,
const FMetaHumanCharacterIdentity::FState& InState, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption)
{
UE::MetaHuman::UpdateLODModelVertexPositions<FMetaHumanCharacterIdentity::FState>(InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption);
}
void FMetaHumanCharacterSkelMeshUtils::UpdateLODModelVertexPositions(TNotNull<USkeletalMesh*> InSkelMesh, const FMetaHumanRigEvaluatedState& InVerticesAndNormals,
const FMetaHumanCharacterBodyIdentity::FState& InState, const FDNAToSkelMeshMap& InDNAToSkelMeshMap, ELodUpdateOption InUpdateOption, EVertexPositionsAndNormals InVertexUpdateOption)
{
UE::MetaHuman::UpdateLODModelVertexPositions<FMetaHumanCharacterBodyIdentity::FState>(InSkelMesh, InVerticesAndNormals, InState, &InDNAToSkelMeshMap, InUpdateOption, InVertexUpdateOption);
}
void FMetaHumanCharacterSkelMeshUtils::UpdateBindPoseFromSource(TNotNull<USkeletalMesh*> InSourceSkelMesh, TNotNull<USkeletalMesh*> InTargetSkelMesh)
{
// Scoping of RefSkelModifier
{
FReferenceSkeletonModifier RefSkelModifier(InTargetSkelMesh->GetRefSkeleton(), InTargetSkelMesh->GetSkeleton());
TArray<FTransform> SourceRawBonePose = InSourceSkelMesh->GetRefSkeleton().GetRawRefBonePose();
TArray<FMeshBoneInfo> 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<FAssetData> AnimBPData;
AssetRegistry.GetAssetsByPackageName(InPackageName, AnimBPData);
if (!AnimBPData.IsEmpty())
{
return AnimBPData[0];
}
return FAssetData();
}
void FMetaHumanCharacterSkelMeshUtils::SetPostProcessAnimBP(TNotNull<USkeletalMesh*> 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<UAnimBlueprint>(AnimBPAsset.GetAsset());
InSkelMesh->SetPostProcessAnimBlueprint(LoadedAnimBP->GetAnimBlueprintGeneratedClass());
}
else if (AnimBPAsset.IsInstanceOf(UAnimBlueprintGeneratedClass::StaticClass()))
{
// Cooked UEFN seems to be going via this route
UAnimBlueprintGeneratedClass* LoadedAnimBP = Cast<UAnimBlueprintGeneratedClass>(AnimBPAsset.GetAsset());
InSkelMesh->SetPostProcessAnimBlueprint(LoadedAnimBP);
}
}
else
{
InSkelMesh->SetPostProcessAnimBlueprint(nullptr);
}
}
void FMetaHumanCharacterSkelMeshUtils::EnableRecomputeTangents(TNotNull<USkeletalMesh*> 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<USkeletalMesh*> InSkelMesh, TSharedPtr<IDNAReader> InDNAReader, bool bIsFaceMesh)
{
TObjectPtr<UDNAAsset> DNAAsset = NewObject<UDNAAsset>(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<FDNAToSkelMeshMap> 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<FSkeletalMaterial>& 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<UMaterialInterface>(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<UMaterialInterface>(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<FVector3f> FMetaHumanCharacterSkelMeshUtils::GetComponentSpaceJointTranslations(TNotNull<USkeletalMesh*> InSkelMesh)
{
TArray<FTransform> RawBonePose = InSkelMesh->GetRefSkeleton().GetRawRefBonePose();
TArray<FTransform> ComponentTransforms;
FAnimationRuntime::FillUpComponentSpaceTransforms(InSkelMesh->GetRefSkeleton(), RawBonePose, ComponentTransforms);
TArray<FVector3f> 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<IDNAReader>& 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<IDNAReader>& OutArchetypeDnaReader)
{
USkeletalMesh* SkelMeshAsset = nullptr;
const FString DNAPath = GetArchetypeDNAPath(InImportDNAType);
TArray<uint8> 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<UPhysicsAsset>(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<USkeletalMeshLODSettings>(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<UControlRigBlueprint>(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<UPhysicsAsset>(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<USkeletalMeshLODSettings>(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<UControlRigBlueprint>(nullptr, TEXT("/Script/ControlRigDeveloper.ControlRigBlueprint'/" UE_PLUGIN_NAME "/Common/MetaHuman_ControlRig.MetaHuman_ControlRig'"));
}
return BodyArchetypeDefaultAnimatingRig;
}