Projekt for SPIE - Avatar for safety briefing / managment event
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

183 lines
5.6 KiB

// Georgy Treshchev 2025.
#include "BlendRuntimeMetaHumanLipSync.h"
#include "RuntimeMetaHumanLipSyncModule.h"
#include "Animation/AnimInstanceProxy.h"
#include "Misc/EngineVersionComparison.h"
FAnimNode_BlendRuntimeMetaHumanLipSync::FAnimNode_BlendRuntimeMetaHumanLipSync()
{
// Initialize with 15 zeros
TargetVisemeWeights.Init(0.0f, 15);
CurrentVisemeWeights.Init(0.0f, 15);
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
PoseByNameNodes.Empty();
TArray<FString> VisemeNames = URuntimeVisemeGenerator::GetVisemeNames();
for (const FString& VisemeName : VisemeNames)
{
FAnimNode_PoseByName PoseByNameNode;
PoseByNameNode.PoseName = FName(*VisemeName);
PoseByNameNodes.Add(PoseByNameNode);
}
SourcePose.Initialize(Context);
RebuildPoseList(Context.AnimInstanceProxy->GetRequiredBones(), PoseAsset);
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
SourcePose.CacheBones(Context);
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::Evaluate_AnyThread(FPoseContext& Output)
{
SourcePose.Evaluate(Output);
if (!VisemeGenerator.IsValid())
{
UE_LOG(LogRuntimeMetaHumanLipSync, VeryVerbose, TEXT("Unable to blend lip sync for MetaHuman because the VisemeGenerator is invalid"));
return;
}
if (CurrentVisemeWeights.Num() != PoseByNameNodes.Num())
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend lip sync for MetaHuman because the number of visemes is invalid: (CurrentVisemeWeights.Num: %d, PoseByNameNodes.Num: %d)"), CurrentVisemeWeights.Num(), PoseByNameNodes.Num());
return;
}
const UPoseAsset* CachedPoseAsset = CurrentPoseAsset.Get();
if (CachedPoseAsset && PoseExtractContext.PoseCurves.Num() == PoseByNameNodes.Num())
{
// Apply interpolated weights to pose curves
for (int32 Index = 0; Index < PoseByNameNodes.Num(); ++Index)
{
PoseExtractContext.PoseCurves[Index].Value = CurrentVisemeWeights[Index];
}
FAnimationPoseData OutputAnimationPoseData(Output);
CurrentPoseAsset->GetAnimationPose(OutputAnimationPoseData, PoseExtractContext);
}
else
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend lip sync for MetaHuman because either the pose asset or the number of visemes is invalid: (CurrentPoseAsset.IsValid: %d, PoseByNameNodes.Num: %d)"), CurrentPoseAsset.IsValid(), PoseByNameNodes.Num());
Output.ResetToRefPose();
}
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::GatherDebugData(FNodeDebugData& DebugData)
{
FAnimNode_Base::GatherDebugData(DebugData);
SourcePose.GatherDebugData(DebugData.BranchFlow(1.f));
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::RebuildPoseList(const FBoneContainer& InBoneContainer, const UPoseAsset* InPoseAsset)
{
if (!InPoseAsset)
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend lip sync for MetaHuman because the pose asset is invalid"));
return;
}
#if UE_VERSION_NEWER_THAN(5, 2, 9)
const TArray<FName>& PoseNames = InPoseAsset->GetPoseFNames();
#else
const TArray<FSmartName> PoseNames = InPoseAsset->GetPoseNames();
#endif
if (PoseNames.Num() == 0)
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend lip sync for MetaHuman because the pose asset has no poses"));
return;
}
PoseExtractContext.PoseCurves.Reset();
for (const FAnimNode_PoseByName& PoseByNameNode : PoseByNameNodes)
{
const int32 PoseIndex = InPoseAsset->GetPoseIndexByName(PoseByNameNode.PoseName);
if (PoseIndex != INDEX_NONE)
{
if (PoseNames.IsValidIndex(PoseIndex))
{
PoseExtractContext.PoseCurves.Add(FPoseCurve(PoseIndex,
#if UE_VERSION_NEWER_THAN(5, 2, 9)
PoseNames[PoseIndex],
#else
PoseNames[PoseIndex].UID,
#endif
0.f));
}
}
else
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend lip sync for MetaHuman because the pose name is invalid: %s"), *PoseByNameNode.PoseName.ToString());
}
}
}
void FAnimNode_BlendRuntimeMetaHumanLipSync::UpdateAssetPlayer(const FAnimationUpdateContext& Context)
{
FAnimNode_PoseHandler::UpdateAssetPlayer(Context);
SourcePose.Update(Context);
// Rebuild pose list if needed
if (PoseExtractContext.PoseCurves.Num() == 0)
{
RebuildPoseList(Context.AnimInstanceProxy->GetRequiredBones(), PoseAsset);
}
// Update target weights from VisemeGenerator
if (VisemeGenerator.IsValid() && VisemeGenerator->GetVisemeWeights().Num() == 15)
{
TArray<float> VisemeWeights = VisemeGenerator->GetVisemeWeights();
if (VisemeWeights.Num() == PoseByNameNodes.Num())
{
// Check if visemes have changed
bool bVisemesChanged = false;
for (int32 Index = 0; Index < PoseByNameNodes.Num(); ++Index)
{
if (FMath::Abs(VisemeWeights[Index] - TargetVisemeWeights[Index]) > KINDA_SMALL_NUMBER)
{
bVisemesChanged = true;
break;
}
}
// Update target weights and reset timer if visemes changed
if (bVisemesChanged)
{
TargetVisemeWeights = MoveTemp(VisemeWeights);
TimeSinceLastVisemeChange = 0.0f;
}
else
{
// Increment time since last change
TimeSinceLastVisemeChange += Context.GetDeltaTime();
// Reset to zero if no change for specified duration
if (TimeSinceLastVisemeChange >= ResetTime)
{
TargetVisemeWeights.Init(0.0f, PoseByNameNodes.Num());
VisemeGenerator->SetVisemeWeights(TargetVisemeWeights);
}
}
}
}
// Smoothly interpolate current weights towards target weights
const float DeltaTime = Context.GetDeltaTime();
for (int32 Index = 0; Index < PoseByNameNodes.Num(); ++Index)
{
CurrentVisemeWeights[Index] = FMath::FInterpTo(
CurrentVisemeWeights[Index],
TargetVisemeWeights[Index],
DeltaTime,
InterpolationSpeed
);
}
}