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

179 lines
5.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetaHumanLiveLinkSubjectSettings.h"
#include "UObject/Package.h"
UMetaHumanLiveLinkSubjectSettings::UMetaHumanLiveLinkSubjectSettings()
{
// Calibration
Properties = FMetaHumanRealtimeCalibration::GetDefaultProperties();
// Smoothing
static constexpr const TCHAR* SmoothingPath = TEXT("/MetaHumanCoreTech/RealtimeMono/DefaultSmoothing.DefaultSmoothing");
Parameters = LoadObject<UMetaHumanRealtimeSmoothingParams>(GetTransientPackage(), SmoothingPath);
}
#if WITH_EDITOR
void UMetaHumanLiveLinkSubjectSettings::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& InPropertyChangedEvent)
{
Super::PostEditChangeChainProperty(InPropertyChangedEvent);
const FProperty* Property = InPropertyChangedEvent.Property;
// Calibration
if (Calibration)
{
if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(ThisClass, Properties))
{
Calibration->SetProperties(Properties);
}
else if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(ThisClass, Alpha))
{
Calibration->SetAlpha(Alpha);
}
else if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(ThisClass, NeutralFrame))
{
Calibration->SetNeutralFrame(NeutralFrame);
}
}
// Smoothing
if (Smoothing)
{
if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(ThisClass, Parameters))
{
Smoothing.Reset();
}
}
}
#endif
bool UMetaHumanLiveLinkSubjectSettings::PreProcess(const FLiveLinkBaseStaticData& InStaticData, FLiveLinkBaseFrameData& InOutFrameData)
{
bool bOK = true;
TArray<float>& FrameData = InOutFrameData.PropertyValues;
const double Now = FPlatformTime::Seconds();
const double DeltaTime = Now - LastTime;
LastTime = Now;
// Calibration
if (!Calibration)
{
Calibration = MakeShared<FMetaHumanRealtimeCalibration>(Properties, NeutralFrame, Alpha);
}
if (Calibration && CaptureNeutralFrameCountdown == -1) // Dont calibrate while capturing calibration neutral
{
bOK &= Calibration->ProcessFrame(InStaticData.PropertyNames, FrameData);
}
// Smoothing
if (!Smoothing && Parameters)
{
Smoothing = MakeShared<FMetaHumanRealtimeSmoothing>(Parameters->Parameters);
}
if (Smoothing)
{
bOK &= Smoothing->ProcessFrame(InStaticData.PropertyNames, FrameData, DeltaTime);
}
// Set calibration neutral
if (Calibration && CaptureNeutralFrameCountdown == 0)
{
NeutralFrame = FrameData;
Calibration->SetNeutralFrame(NeutralFrame);
}
if (CaptureNeutralFrameCountdown != -1)
{
CaptureNeutralFrameCountdown--;
}
// Head translation
const int32 HeadXIndex = InStaticData.PropertyNames.Find("HeadTranslationX");
const int32 HeadYIndex = InStaticData.PropertyNames.Find("HeadTranslationY");
const int32 HeadZIndex = InStaticData.PropertyNames.Find("HeadTranslationZ");
if (!ensureMsgf(HeadXIndex != INDEX_NONE, TEXT("Can not find HeadTranslationX property")))
{
return false;
}
if (!ensureMsgf(HeadYIndex != INDEX_NONE, TEXT("Can not find HeadTranslationY property")))
{
return false;
}
if (!ensureMsgf(HeadZIndex != INDEX_NONE, TEXT("Can not find HeadTranslationZ property")))
{
return false;
}
const FVector HeadTranslation = FVector(FrameData[HeadXIndex], FrameData[HeadYIndex], FrameData[HeadZIndex]);
if (CaptureNeutralHeadTranslationCountdown == 0)
{
NeutralHeadTranslation = HeadTranslation;
}
if (CaptureNeutralHeadTranslationCountdown != -1)
{
CaptureNeutralHeadTranslationCountdown--;
}
if (InOutFrameData.MetaData.StringMetaData.Contains("HeadPoseMode"))
{
const int32 HeadPoseMode = FCString::Atoi(*InOutFrameData.MetaData.StringMetaData["HeadPoseMode"]);
if (HeadPoseMode == 1 && CaptureNeutralHeadTranslationCountdown == -1 && !NeutralHeadTranslation.IsZero()) // Camera relative head translation, convert into body relative if translation has finished smoothing
{
FrameData[HeadXIndex] = HeadTranslation.X - NeutralHeadTranslation.X;
FrameData[HeadYIndex] = HeadTranslation.Y - NeutralHeadTranslation.Y;
FrameData[HeadZIndex] = HeadTranslation.Z - NeutralHeadTranslation.Z;
}
else
{
FrameData[HeadXIndex] = 0;
FrameData[HeadYIndex] = 0;
FrameData[HeadZIndex] = 0;
}
}
return bOK;
}
void UMetaHumanLiveLinkSubjectSettings::CaptureNeutrals()
{
CaptureNeutralFrame();
CaptureNeutralHeadTranslation();
}
void UMetaHumanLiveLinkSubjectSettings::CaptureNeutralFrame()
{
// Somewhat arbitrary number of frames to wait before capturing the calibration
// neutral values. The calibration neutrals needs to be captured after smoothing
// but without any previous calibration applied. Turning off the previous calibration
// in order to capture a new one causes a jump in animation values and that needs
// time to be smoothed out. Ideally here we would switch off the usual smoothing while
// capturing a neutral and instead apply a known size rolling average since the head
// should be steady while capturing a neutral and we are only interesting in removing
// the noise introduced by the solve and not trying to smooth out any head motion.
CaptureNeutralFrameCountdown = 5;
}
void UMetaHumanLiveLinkSubjectSettings::CaptureNeutralHeadTranslation()
{
// See comment above. Larger number used here to first capture neutral, then wait until
// that is smoothed before capturing head translation.
CaptureNeutralHeadTranslationCountdown = 10;
}