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.
 
 
 

157 lines
5.3 KiB

// Georgy Treshchev 2025.
#include "BlendRuntimeMetaHumanLaughter.h"
#include "RuntimeMetaHumanLipSyncModule.h"
#include "Animation/AnimInstanceProxy.h"
#include "Misc/EngineVersionComparison.h"
FAnimNode_BlendRuntimeMetaHumanLaughter::FAnimNode_BlendRuntimeMetaHumanLaughter()
{
// Initialize with zeroed weights
TargetLaughterWeight = 0.0f;
CurrentLaughterWeight = 0.0f;
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
SourcePose.Initialize(Context);
RebuildPoseList(Context.AnimInstanceProxy->GetRequiredBones(), PoseAsset);
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
{
SourcePose.CacheBones(Context);
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::Evaluate_AnyThread(FPoseContext& Output)
{
SourcePose.Evaluate(Output);
if (!VisemeGenerator.IsValid())
{
UE_LOG(LogRuntimeMetaHumanLipSync, VeryVerbose, TEXT("Unable to blend laughter for MetaHuman because the VisemeGenerator is invalid"));
return;
}
const UPoseAsset* CachedPoseAsset = CurrentPoseAsset.Get();
if (CachedPoseAsset && bLaughterPoseFound)
{
// Apply interpolated weight to pose curve
PoseExtractContext.PoseCurves.Empty(1);
LaughterPoseCurve.Value = CurrentLaughterWeight;
PoseExtractContext.PoseCurves.Add(LaughterPoseCurve);
FAnimationPoseData OutputAnimationPoseData(Output);
CurrentPoseAsset->GetAnimationPose(OutputAnimationPoseData, PoseExtractContext);
}
else if (!bLaughterPoseFound)
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend laughter for MetaHuman because the laughter pose '%s' was not found in the pose asset"), *LaughterPoseName.ToString());
}
else
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend laughter for MetaHuman because the pose asset is invalid"));
Output.ResetToRefPose();
}
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::GatherDebugData(FNodeDebugData& DebugData)
{
FAnimNode_Base::GatherDebugData(DebugData);
FString DebugLine = FString::Printf(TEXT("Laughter Weight: %.2f"), CurrentLaughterWeight);
DebugData.AddDebugItem(DebugLine);
SourcePose.GatherDebugData(DebugData.BranchFlow(1.f));
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::RebuildPoseList(const FBoneContainer& InBoneContainer, const UPoseAsset* InPoseAsset)
{
if (!InPoseAsset)
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend laughter for MetaHuman because the pose asset is invalid"));
bLaughterPoseFound = false;
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 laughter for MetaHuman because the pose asset has no poses"));
bLaughterPoseFound = false;
return;
}
PoseExtractContext.PoseCurves.Reset();
const int32 PoseIndex = InPoseAsset->GetPoseIndexByName(LaughterPoseName);
if (PoseIndex != INDEX_NONE)
{
if (PoseNames.IsValidIndex(PoseIndex))
{
LaughterPoseCurve = FPoseCurve(PoseIndex,
#if UE_VERSION_NEWER_THAN(5, 2, 9)
PoseNames[PoseIndex],
#else
PoseNames[PoseIndex].UID,
#endif
0.f);
bLaughterPoseFound = true;
}
}
else
{
UE_LOG(LogRuntimeMetaHumanLipSync, Error, TEXT("Unable to blend laughter for MetaHuman because the laughter pose name is invalid: %s"), *LaughterPoseName.ToString());
bLaughterPoseFound = false;
}
}
void FAnimNode_BlendRuntimeMetaHumanLaughter::UpdateAssetPlayer(const FAnimationUpdateContext& Context)
{
FAnimNode_PoseHandler::UpdateAssetPlayer(Context);
SourcePose.Update(Context);
// Rebuild pose list if needed
if (PoseExtractContext.PoseCurves.Num() == 0 && !bLaughterPoseFound)
{
RebuildPoseList(Context.AnimInstanceProxy->GetRequiredBones(), PoseAsset);
}
// Update target weights from VisemeGenerator
if (VisemeGenerator.IsValid())
{
// Get laughter score from the VisemeGenerator
const float LaughterScore = VisemeGenerator->GetLaughterScore();
// Check if laughter has changed
if (FMath::Abs(LaughterScore - TargetLaughterWeight) > KINDA_SMALL_NUMBER)
{
TargetLaughterWeight = FMath::Clamp(LaughterScore * MaxLaughterWeight, 0.0f, 1.0f);
TimeSinceLastLaughterChange = 0.0f;
}
else
{
// Increment time since last change
TimeSinceLastLaughterChange += Context.GetDeltaTime();
// Reset to zero if no change for specified duration
if (TimeSinceLastLaughterChange >= ResetTime && TargetLaughterWeight > 0.0f)
{
TargetLaughterWeight = 0.0f;
}
}
}
// Smoothly interpolate current weight towards target weight
const float DeltaTime = Context.GetDeltaTime();
CurrentLaughterWeight = FMath::FInterpTo(
CurrentLaughterWeight,
TargetLaughterWeight,
DeltaTime,
InterpolationSpeed
);
}