Files
UnrealEngine/Engine/Plugins/MetaHuman/MetaHumanAnimator/Source/MetaHumanIdentity/Private/MetaHumanTemplateMeshComponent.cpp
2025-05-18 13:04:45 +08:00

524 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetaHumanTemplateMeshComponent.h"
#include "MetaHumanIdentityParts.h"
#include "MetaHumanIdentityPose.h"
#include "MetaHumanIdentityLog.h"
#include "UObject/ConstructorHelpers.h"
#include "Components/DynamicMeshComponent.h"
#include "DynamicMesh/MeshNormals.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "Misc/FileHelper.h"
#include "Interfaces/IPluginManager.h"
#include "Materials/Material.h"
#include "Engine/StaticMesh.h"
#include "DNAUtils.h"
#include "MaterialDomain.h"
#include "InterchangeDnaTranslator.h"
static inline TSet<int32> GetObjToUEVertexMapping(const FString& InObjFileName)
{
const FString PluginContentDir = IPluginManager::Get().FindPlugin(UE_PLUGIN_NAME)->GetContentDir();
const FString ObjFilePath = FString::Format(TEXT("{0}/MeshFitting/Template/{1}.obj"), { PluginContentDir, InObjFileName });
TSet<int32> Indices;
FFileHelper ObjParser;
ObjParser.LoadFileToStringWithLineVisitor(*ObjFilePath, [&Indices](FStringView InView)
{
if (InView.Left(2).Equals("f "))
{
InView.RightChopInline(2);
int32 IndexDash = INDEX_NONE;
int32 IndexWhitespace = INDEX_NONE;
int32 Itr = 0;
while (Itr < 4)
{
InView.FindChar(TCHAR('/'), IndexDash);
InView.FindChar(TCHAR(' '), IndexWhitespace);
auto NumStrin = FString(InView.Left(IndexDash));
int32 Ind = FCString::Atoi(*NumStrin);
Indices.Add(Ind - 1);
InView.RightChopInline(IndexWhitespace + 1);
++Itr;
}
}
});
return Indices;
}
inline FVector UMetaHumanTemplateMeshComponent::ConvertVertex(const FVector& InVertex, ETemplateVertexConversion InConversionType)
{
switch (InConversionType)
{
case ETemplateVertexConversion::ConformerToUE:
return FVector{ InVertex.Z, InVertex.X, -InVertex.Y };
case ETemplateVertexConversion::UEToConformer:
return FVector{ InVertex.Y, -InVertex.Z, InVertex.X };
case ETemplateVertexConversion::None:
default:
break;
}
return InVertex;
}
FTransform UMetaHumanTemplateMeshComponent::UEToRigSpaceTransform{ FRotator{ 180.0, -90.0, 0.0 } };
UMetaHumanTemplateMeshComponent::UMetaHumanTemplateMeshComponent()
: bShowFittedTeeth{ true }
, bShowEyes{ true }
, bShowTeethMesh { true }
{
HeadMeshComponent = CreateDefaultSubobject<UDynamicMeshComponent>(TEXT("Template Head Mesh Component"));
TeethMeshComponent = CreateDefaultSubobject<UDynamicMeshComponent>(TEXT("Template Teeth Mesh Component"));
LeftEyeComponent = CreateDefaultSubobject<UDynamicMeshComponent>(TEXT("Template Left Eye Mesh Component"));
RightEyeComponent = CreateDefaultSubobject<UDynamicMeshComponent>(TEXT("Template Left Right Mesh Component"));
OriginalTeethMesh = CreateDefaultSubobject<UDynamicMesh>(TEXT("Original Teeth Mesh"));
FittedTeethMesh = CreateDefaultSubobject<UDynamicMesh>(TEXT("Fitted Teeth Mesh"));
PoseHeadMeshes =
{
{ EIdentityPoseType::Neutral, CreateDefaultSubobject<UDynamicMesh>(TEXT("Neutral Head Mesh")) },
{ EIdentityPoseType::Teeth, CreateDefaultSubobject<UDynamicMesh>(TEXT("Teeth Head Mesh")) }
};
HeadMeshComponent->SetupAttachment(this);
TeethMeshComponent->SetupAttachment(this);
LeftEyeComponent->SetupAttachment(this);
RightEyeComponent->SetupAttachment(this);
}
void UMetaHumanTemplateMeshComponent::OnRegister()
{
Super::OnRegister();
HeadMeshComponent->RegisterComponent();
TeethMeshComponent->RegisterComponent();
LeftEyeComponent->RegisterComponent();
RightEyeComponent->RegisterComponent();
}
void UMetaHumanTemplateMeshComponent::OnUnregister()
{
Super::OnUnregister();
HeadMeshComponent->UnregisterComponent();
TeethMeshComponent->UnregisterComponent();
LeftEyeComponent->UnregisterComponent();
RightEyeComponent->UnregisterComponent();
}
void UMetaHumanTemplateMeshComponent::OnVisibilityChanged()
{
Super::OnVisibilityChanged();
LeftEyeComponent->SetVisibility(bShowEyes);
RightEyeComponent->SetVisibility(bShowEyes);
TeethMeshComponent->SetVisibility(bShowTeethMesh);
}
void UMetaHumanTemplateMeshComponent::LoadMaterialsForMeshes()
{
static UMaterial* TemplateHeadMaterial = LoadObject<UMaterial>(nullptr, TEXT("/" UE_PLUGIN_NAME "/IdentityTemplate/M_MetaHumanIdentity_Head.M_MetaHumanIdentity_Head"));
static UMaterial* TemplateTeethMaterial = LoadObject<UMaterial>(nullptr, TEXT("/" UE_PLUGIN_NAME "/IdentityTemplate/M_MetaHumanIdentity_Teeth.M_MetaHumanIdentity_Teeth"));
static UMaterial* TemplateEyeMaterial = LoadObject<UMaterial>(nullptr, TEXT("/" UE_PLUGIN_NAME "/IdentityTemplate/M_MetaHumanIdentity_Eye.M_MetaHumanIdentity_Eye"));
HeadMeshComponent->SetOverrideRenderMaterial(TemplateHeadMaterial);
TeethMeshComponent->SetOverrideRenderMaterial(TemplateTeethMaterial);
LeftEyeComponent->SetOverrideRenderMaterial(TemplateEyeMaterial);
RightEyeComponent->SetOverrideRenderMaterial(TemplateEyeMaterial);
}
void UMetaHumanTemplateMeshComponent::LoadMeshAssets()
{
LoadMaterialsForMeshes();
HeadMeshComponent->GetDynamicMesh()->InitializeMesh();
TeethMeshComponent->GetDynamicMesh()->InitializeMesh();
LeftEyeComponent->GetDynamicMesh()->InitializeMesh();
RightEyeComponent->GetDynamicMesh()->InitializeMesh();
OriginalTeethMesh->InitializeMesh();
FittedTeethMesh->InitializeMesh();
bool bLoadedTemplateMeshFromDNA = false;
const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT(UE_PLUGIN_NAME))->GetContentDir();
const FString PathToDNA = PluginDir + TEXT("/IdentityTemplate/Face_Archetype.ardna");
TArray<uint8> DNADataAsBuffer;
if (FFileHelper::LoadFileToArray(DNADataAsBuffer, *PathToDNA))
{
if (TSharedPtr<IDNAReader> DNAReader = ReadDNAFromBuffer(&DNADataAsBuffer, EDNADataLayer::All))
{
FMeshDescription HeadMeshDescription;
FMeshDescription TeethMeshDescription;
FMeshDescription EyeLeftMeshDescription;
FMeshDescription EyeRightMeshDescription;
// Populate mesh description from dna data. Mesh index for relevant geometries match layout in DNA file
UInterchangeDnaTranslator::PopulateStaticMeshDescription(HeadMeshDescription, *DNAReader.Get(), 0);
UInterchangeDnaTranslator::PopulateStaticMeshDescription(TeethMeshDescription, *DNAReader.Get(), 1);
UInterchangeDnaTranslator::PopulateStaticMeshDescription(EyeLeftMeshDescription, *DNAReader.Get(), 3);
UInterchangeDnaTranslator::PopulateStaticMeshDescription(EyeRightMeshDescription, *DNAReader.Get(), 4);
FDynamicMesh3 HeadMesh;
FDynamicMesh3 TeethMesh;
FMeshDescriptionToDynamicMesh DynamicMeshConverter;
DynamicMeshConverter.Convert(&HeadMeshDescription, HeadMesh);
DynamicMeshConverter.Convert(&TeethMeshDescription, TeethMesh);
DynamicMeshConverter.Convert(&EyeLeftMeshDescription, *LeftEyeComponent->GetMesh());
DynamicMeshConverter.Convert(&EyeRightMeshDescription, *RightEyeComponent->GetMesh());
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(HeadMesh);
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(TeethMesh);
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(*LeftEyeComponent->GetMesh());
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(*RightEyeComponent->GetMesh());
// Initialize all pose head meshes so they start all in their default state
for (TPair<EIdentityPoseType, TObjectPtr<UDynamicMesh>>& PoseHeadMeshPair : PoseHeadMeshes)
{
PoseHeadMeshPair.Value->SetMesh(HeadMesh);
}
// Initializes both teeth meshes so they start at their default state
OriginalTeethMesh->SetMesh(TeethMesh);
FittedTeethMesh->SetMesh(TeethMesh);
// Set the neutral pose head mesh as the currently active head mesh
HeadMeshComponent->GetDynamicMesh()->SetMesh(PoseHeadMeshes[EIdentityPoseType::Neutral]->GetMeshRef());
TeethMeshComponent->GetDynamicMesh()->SetMesh(OriginalTeethMesh->GetMeshRef());
bLoadedTemplateMeshFromDNA = true;
}
}
if (!bLoadedTemplateMeshFromDNA)
{
UE_LOG(LogMetaHumanIdentity, Error, TEXT("Failed to create a template mesh from the dna file: %s"), *PathToDNA);
}
}
#if WITH_EDITOR
void UMetaHumanTemplateMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& InPropertyChangedEvent)
{
Super::PostEditChangeProperty(InPropertyChangedEvent);
if (FProperty* Property = InPropertyChangedEvent.Property)
{
const FName PropertyName = *Property->GetName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(ThisClass, bShowFittedTeeth))
{
TeethMeshComponent->GetDynamicMesh()->SetMesh(bShowFittedTeeth ? FittedTeethMesh->GetMeshRef() : OriginalTeethMesh->GetMeshRef());
OnTemplateMeshChanged.Broadcast();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ThisClass, bShowEyes))
{
// Only set the visibility of eyes if the component itself is visible
RightEyeComponent->SetVisibility(bShowEyes && IsVisible());
LeftEyeComponent->SetVisibility(bShowEyes && IsVisible());
OnTemplateMeshChanged.Broadcast();
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(ThisClass, bShowTeethMesh))
{
TeethMeshComponent->SetVisibility(bShowTeethMesh && IsVisible());
OnTemplateMeshChanged.Broadcast();
}
}
}
#endif
FBoxSphereBounds UMetaHumanTemplateMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const
{
return HeadMeshComponent->Bounds.TransformBy(InLocalToWorld);
}
UDynamicMesh* UMetaHumanTemplateMeshComponent::GetPoseHeadMesh(EIdentityPoseType InPoseType) const
{
check(PoseHeadMeshes.Contains(InPoseType));
return PoseHeadMeshes[InPoseType];
}
void UMetaHumanTemplateMeshComponent::ShowHeadMeshForPose(EIdentityPoseType InPoseType) const
{
check(PoseHeadMeshes.Contains(InPoseType));
HeadMeshComponent->GetDynamicMesh()->SetMesh(PoseHeadMeshes[InPoseType]->GetMeshRef());
OnTemplateMeshChanged.Broadcast();
}
void UMetaHumanTemplateMeshComponent::SetPoseHeadMeshVertices(EIdentityPoseType InPoseType, TConstArrayView<FVector3f> InNewVertices, ETemplateVertexConversion InConversionType) const
{
UDynamicMesh* PoseHeadMesh = GetPoseHeadMesh(InPoseType);
if (InNewVertices.Num() == PoseHeadMesh->GetMeshRef().VertexCount())
{
PoseHeadMesh->EditMesh([this, InNewVertices, InConversionType](FDynamicMesh3& InMesh3D)
{
for (int32 VertId = 0; VertId < InMesh3D.VertexCount(); VertId++)
{
const FVector3f& InputVertex = InNewVertices[VertId];
const FVector Vertex = ConvertVertex(FVector(InputVertex), InConversionType);
InMesh3D.SetVertex(VertId, Vertex);
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
}, EDynamicMeshChangeType::MeshVertexChange, EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents);
OnTemplateMeshChanged.Broadcast();
}
else
{
UE_LOG(LogMetaHumanIdentity, Error, TEXT("Mismatch in number of vertices when setting mesh for %s pose. %d vertices provided but %d are expected"), *UMetaHumanIdentityPose::PoseTypeAsString(InPoseType), InNewVertices.Num(), PoseHeadMesh->GetMeshRef().VertexCount());
}
}
void UMetaHumanTemplateMeshComponent::GetPoseHeadMeshVertices(EIdentityPoseType InPoseType, const FTransform& InTransform, ETemplateVertexConversion InConvertionType, TArray<FVector>& OutPoseHeadVertices) const
{
UDynamicMesh* HeadMesh = GetPoseHeadMesh(InPoseType);
const UE::Geometry::FDynamicMesh3& Mesh = HeadMesh->GetMeshRef();
OutPoseHeadVertices.SetNumUninitialized(Mesh.VertexCount());
for (int32 VertexId = 0; VertexId < Mesh.VertexCount(); ++VertexId)
{
FVector Vertex = Mesh.GetVertex(VertexId);
Vertex = InTransform.TransformPosition(Vertex);
OutPoseHeadVertices[VertexId] = ConvertVertex(Vertex, InConvertionType);
}
}
void UMetaHumanTemplateMeshComponent::GetEyeMeshesVertices(const FTransform& InTransform, ETemplateVertexConversion InConversionType, TArray<FVector>& OutLeftEyeVertices, TArray<FVector>& OutRightEyeVertices) const
{
check(LeftEyeComponent->GetMesh()->VertexCount() == RightEyeComponent->GetMesh()->VertexCount());
const int32 NumVerts = LeftEyeComponent->GetMesh()->VertexCount();
OutLeftEyeVertices.SetNumUninitialized(NumVerts);
OutRightEyeVertices.SetNumUninitialized(NumVerts);
for (int32 VertexId = 0; VertexId < NumVerts; ++VertexId)
{
FVector LeftEyeVertex = LeftEyeComponent->GetMesh()->GetVertex(VertexId);
FVector RightEyeVertex = RightEyeComponent->GetMesh()->GetVertex(VertexId);
// Transform the vertices to scan space
LeftEyeVertex = InTransform.TransformPosition(LeftEyeVertex);
RightEyeVertex = InTransform.TransformPosition(RightEyeVertex);
OutLeftEyeVertices[VertexId] = ConvertVertex(LeftEyeVertex, InConversionType);
OutRightEyeVertices[VertexId] = ConvertVertex(RightEyeVertex, InConversionType);
}
}
void UMetaHumanTemplateMeshComponent::GetTeethMeshVertices(const FTransform& InTransform, ETemplateVertexConversion InConversionType, TArray<FVector>& OutTeethVertices) const
{
const int32 NumVerts = TeethMeshComponent->GetMesh()->VertexCount();
OutTeethVertices.SetNumUninitialized(NumVerts);
for (int32 VertexId = 0; VertexId < NumVerts; ++VertexId)
{
FVector TeethVertex = TeethMeshComponent->GetMesh()->GetVertex(VertexId);
// Transform the vertices to scan space
TeethVertex = InTransform.TransformPosition(TeethVertex);
OutTeethVertices[VertexId] = ConvertVertex(TeethVertex, InConversionType);
}
}
void UMetaHumanTemplateMeshComponent::SetEyeMeshesVertices(TConstArrayView<FVector3f> InLeftEyeVertices, TConstArrayView<FVector3f> InRightEyeVertices, ETemplateVertexConversion InConversionType)
{
check(LeftEyeComponent->GetDynamicMesh());
check(RightEyeComponent->GetDynamicMesh());
if (InLeftEyeVertices.Num() == LeftEyeComponent->GetMesh()->VertexCount() && InRightEyeVertices.Num() == RightEyeComponent->GetMesh()->VertexCount())
{
constexpr EDynamicMeshAttributeChangeFlags ChangeFlags = EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents;
LeftEyeComponent->GetDynamicMesh()->EditMesh([InLeftEyeVertices, InConversionType](FDynamicMesh3& InMesh3D)
{
int32 Ctr = 0;
for (const FVector3f& Vertex : InLeftEyeVertices)
{
InMesh3D.SetVertex(Ctr++, ConvertVertex(FVector(Vertex), InConversionType));
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
}, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
RightEyeComponent->GetDynamicMesh()->EditMesh([InRightEyeVertices, InConversionType](FDynamicMesh3& InMesh3D)
{
int32 Ctr = 0;
for (const FVector3f& Vertex : InRightEyeVertices)
{
InMesh3D.SetVertex(Ctr++, ConvertVertex(FVector(Vertex), InConversionType));
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
}, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
OnTemplateMeshChanged.Broadcast();
}
else
{
UE_LOG(LogMetaHumanIdentity,
Error,
TEXT("Mismatch in number of vertices when setting mesh for eyes. Expected %d for left eye but %d provided. Expected %d for right eye but %d provided"),
LeftEyeComponent->GetMesh()->VertexCount(),
InLeftEyeVertices.Num(),
RightEyeComponent->GetMesh()->VertexCount(),
InRightEyeVertices.Num());
}
}
void UMetaHumanTemplateMeshComponent::SetEyeMeshesVisibility(bool bInVisible)
{
bShowEyes = bInVisible;
RightEyeComponent->SetVisibility(IsVisible() && bShowEyes);
LeftEyeComponent->SetVisibility(IsVisible() && bShowEyes);
OnTemplateMeshChanged.Broadcast();
}
void UMetaHumanTemplateMeshComponent::SetTeethMeshVisibility(bool bInVisible)
{
bShowTeethMesh = bInVisible;
TeethMeshComponent->SetVisibility(IsVisible() && bShowTeethMesh);
OnTemplateMeshChanged.Broadcast();
}
void UMetaHumanTemplateMeshComponent::BakeEyeMeshesTransform(const FTransform& InTransform)
{
check(LeftEyeComponent->GetDynamicMesh());
check(RightEyeComponent->GetDynamicMesh());
constexpr EDynamicMeshAttributeChangeFlags ChangeFlags = EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents;
auto BakeTransform = [&InTransform](FDynamicMesh3& InMesh3D)
{
for (int32 VertexId = 0; VertexId < InMesh3D.VertexCount(); ++VertexId)
{
const FVector Vertex = InMesh3D.GetVertex(VertexId);
InMesh3D.SetVertex(VertexId, InTransform.TransformPosition(Vertex));
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
};
LeftEyeComponent->GetDynamicMesh()->EditMesh(BakeTransform, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
RightEyeComponent->GetDynamicMesh()->EditMesh(BakeTransform, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
OnTemplateMeshChanged.Broadcast();
}
void UMetaHumanTemplateMeshComponent::SetTeethMeshVertices(TConstArrayView<FVector3f> InNewVertices, ETemplateVertexConversion InConversionType) const
{
check(FittedTeethMesh);
if (FittedTeethMesh->GetMeshRef().VertexCount() == InNewVertices.Num())
{
FittedTeethMesh->EditMesh([InNewVertices, InConversionType](FDynamicMesh3& InMesh3D)
{
int32 Ctr = 0;
for (const FVector3f& Vertex : InNewVertices)
{
InMesh3D.SetVertex(Ctr++, ConvertVertex(FVector(Vertex), InConversionType));
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
}, EDynamicMeshChangeType::MeshVertexChange, EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents);
TeethMeshComponent->GetDynamicMesh()->SetMesh(FittedTeethMesh->GetMeshRef());
OnTemplateMeshChanged.Broadcast();
}
else
{
UE_LOG(LogMetaHumanIdentity, Error, TEXT("Mismatch in number of vertices when setting mesh for teeth. %d vertices provided but %d are expected"), InNewVertices.Num(), FittedTeethMesh->GetMeshRef().VertexCount());
}
}
void UMetaHumanTemplateMeshComponent::BakeTeethMeshTransform(const FTransform& InTransform)
{
// Populate the meshes for the CDO if needed
UMetaHumanTemplateMeshComponent* TemplateMeshComponentCDO = GetMutableDefault<UMetaHumanTemplateMeshComponent>();
if (TemplateMeshComponentCDO->OriginalTeethMesh->IsEmpty() || TemplateMeshComponentCDO->FittedTeethMesh->IsEmpty())
{
TemplateMeshComponentCDO->LoadMeshAssets();
}
OriginalTeethMesh->SetMesh(TemplateMeshComponentCDO->OriginalTeethMesh->GetMeshRef());
FittedTeethMesh->SetMesh(TemplateMeshComponentCDO->FittedTeethMesh->GetMeshRef());
if (!InTransform.Identical(&FTransform::Identity, 0))
{
auto BakeTransform = [&InTransform](FDynamicMesh3& InMesh3D)
{
for (int32 VertexId = 0; VertexId < InMesh3D.VertexCount(); ++VertexId)
{
const FVector Vertex = InMesh3D.GetVertex(VertexId);
InMesh3D.SetVertex(VertexId, InTransform.TransformPosition(Vertex));
}
// Need to recompute the overlay normals so it renders the mesh correctly
UE::Geometry::FMeshNormals::QuickRecomputeOverlayNormals(InMesh3D);
};
constexpr EDynamicMeshAttributeChangeFlags ChangeFlags = EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents;
OriginalTeethMesh->EditMesh(BakeTransform, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
FittedTeethMesh->EditMesh(BakeTransform, EDynamicMeshChangeType::MeshVertexChange, ChangeFlags);
}
// Reset the teeth mesh in the teeth mesh component so the change is reflected in the instance being visualized
TeethMeshComponent->GetDynamicMesh()->SetMesh(bShowFittedTeeth ? FittedTeethMesh->GetMeshRef() : OriginalTeethMesh->GetMeshRef());
OnTemplateMeshChanged.Broadcast();
}
void UMetaHumanTemplateMeshComponent::ResetMeshes()
{
UMetaHumanTemplateMeshComponent* TemplateMeshComponentCDO = GetMutableDefault<UMetaHumanTemplateMeshComponent>();
if (TemplateMeshComponentCDO->HeadMeshComponent->GetDynamicMesh()->IsEmpty())
{
TemplateMeshComponentCDO->LoadMeshAssets();
}
OriginalTeethMesh->SetMesh(TemplateMeshComponentCDO->OriginalTeethMesh->GetMeshRef());
FittedTeethMesh->SetMesh(TemplateMeshComponentCDO->FittedTeethMesh->GetMeshRef());
LeftEyeComponent->GetDynamicMesh()->SetMesh(TemplateMeshComponentCDO->LeftEyeComponent->GetDynamicMesh()->GetMeshRef());
RightEyeComponent->GetDynamicMesh()->SetMesh(TemplateMeshComponentCDO->RightEyeComponent->GetDynamicMesh()->GetMeshRef());
for (const TPair<EIdentityPoseType, TObjectPtr<UDynamicMesh>>& PoseHeadMeshPair : TemplateMeshComponentCDO->PoseHeadMeshes)
{
PoseHeadMeshes[PoseHeadMeshPair.Key]->SetMesh(PoseHeadMeshPair.Value->GetMeshRef());
}
// Reset the meshes in the components that display the data
HeadMeshComponent->GetDynamicMesh()->SetMesh(PoseHeadMeshes[EIdentityPoseType::Neutral]->GetMeshRef());
TeethMeshComponent->GetDynamicMesh()->SetMesh(bShowFittedTeeth ? FittedTeethMesh->GetMeshRef() : OriginalTeethMesh->GetMeshRef());
OnTemplateMeshChanged.Broadcast();
}