// Copyright (c) Meta Platforms, Inc. and affiliates. #include "OculusXRFaceTrackingComponent.h" #include "OculusXRHMD.h" #include "OculusXRPluginWrapper.h" #include "OculusXRMovementFunctionLibrary.h" #include "OculusXRMovementHelpers.h" #include "OculusXRMovementLog.h" #include "OculusXRTelemetryMovementEvents.h" #include "Engine/SkeletalMesh.h" #include "Components/SkeletalMeshComponent.h" #include "Math/UnrealMathUtility.h" int UOculusXRFaceTrackingComponent::TrackingInstanceCount = 0; UOculusXRFaceTrackingComponent::UOculusXRFaceTrackingComponent() : TargetMeshComponentName(NAME_None) , InvalidFaceDataResetTime(2.0f) , bUpdateFace(true) , TargetMeshComponent(nullptr) { PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bStartWithTickEnabled = true; // Some defaults ExpressionNames.Add(EOculusXRFaceExpression::BrowLowererL, "browLowerer_L"); ExpressionNames.Add(EOculusXRFaceExpression::BrowLowererR, "browLowerer_R"); ExpressionNames.Add(EOculusXRFaceExpression::CheekPuffL, "cheekPuff_L"); ExpressionNames.Add(EOculusXRFaceExpression::CheekPuffR, "cheekPuff_R"); ExpressionNames.Add(EOculusXRFaceExpression::CheekRaiserL, "cheekRaiser_L"); ExpressionNames.Add(EOculusXRFaceExpression::CheekRaiserR, "cheekRaiser_R"); ExpressionNames.Add(EOculusXRFaceExpression::CheekSuckL, "cheekSuck_L"); ExpressionNames.Add(EOculusXRFaceExpression::CheekSuckR, "cheekSuck_R"); ExpressionNames.Add(EOculusXRFaceExpression::ChinRaiserB, "chinRaiser_B"); ExpressionNames.Add(EOculusXRFaceExpression::ChinRaiserT, "chinRaiser_T"); ExpressionNames.Add(EOculusXRFaceExpression::DimplerL, "dimpler_L"); ExpressionNames.Add(EOculusXRFaceExpression::DimplerR, "dimpler_R"); ExpressionNames.Add(EOculusXRFaceExpression::EyesClosedL, "eyesClosed_L"); ExpressionNames.Add(EOculusXRFaceExpression::EyesClosedR, "eyesClosed_R"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookDownL, "eyesLookDown_L"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookDownR, "eyesLookDown_R"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookLeftL, "eyesLookLeft_L"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookLeftR, "eyesLookLeft_R"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookRightL, "eyesLookRight_L"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookRightR, "eyesLookRight_R"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookUpL, "eyesLookUp_L"); ExpressionNames.Add(EOculusXRFaceExpression::EyesLookUpR, "eyesLookUp_R"); ExpressionNames.Add(EOculusXRFaceExpression::InnerBrowRaiserL, "innerBrowRaiser_L"); ExpressionNames.Add(EOculusXRFaceExpression::InnerBrowRaiserR, "innerBrowRaiser_R"); ExpressionNames.Add(EOculusXRFaceExpression::JawDrop, "jawDrop"); ExpressionNames.Add(EOculusXRFaceExpression::JawSidewaysLeft, "jawSidewaysLeft"); ExpressionNames.Add(EOculusXRFaceExpression::JawSidewaysRight, "jawSidewaysRight"); ExpressionNames.Add(EOculusXRFaceExpression::JawThrust, "jawThrust"); ExpressionNames.Add(EOculusXRFaceExpression::LidTightenerL, "lidTightener_L"); ExpressionNames.Add(EOculusXRFaceExpression::LidTightenerR, "lidTightener_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipCornerDepressorL, "lipCornerDepressor_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipCornerDepressorR, "lipCornerDepressor_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipCornerPullerL, "lipCornerPuller_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipCornerPullerR, "lipCornerPuller_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerLB, "lipFunneler_LB"); ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerLT, "lipFunneler_LT"); ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerRB, "lipFunneler_RB"); ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerRT, "lipFunneler_RT"); ExpressionNames.Add(EOculusXRFaceExpression::LipPressorL, "lipPressor_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipPressorR, "lipPressor_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipPuckerL, "lipPucker_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipPuckerR, "lipPucker_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipStretcherL, "lipStretcher_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipStretcherR, "lipStretcher_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipSuckLB, "lipSuck_LB"); ExpressionNames.Add(EOculusXRFaceExpression::LipSuckLT, "lipSuck_LT"); ExpressionNames.Add(EOculusXRFaceExpression::LipSuckRB, "lipSuck_RB"); ExpressionNames.Add(EOculusXRFaceExpression::LipSuckRT, "lipSuck_RT"); ExpressionNames.Add(EOculusXRFaceExpression::LipTightenerL, "lipTightener_L"); ExpressionNames.Add(EOculusXRFaceExpression::LipTightenerR, "lipTightener_R"); ExpressionNames.Add(EOculusXRFaceExpression::LipsToward, "lipsToward"); ExpressionNames.Add(EOculusXRFaceExpression::LowerLipDepressorL, "lowerLipDepressor_L"); ExpressionNames.Add(EOculusXRFaceExpression::LowerLipDepressorR, "lowerLipDepressor_R"); ExpressionNames.Add(EOculusXRFaceExpression::MouthLeft, "mouthLeft"); ExpressionNames.Add(EOculusXRFaceExpression::MouthRight, "mouthRight"); ExpressionNames.Add(EOculusXRFaceExpression::NoseWrinklerL, "noseWrinkler_L"); ExpressionNames.Add(EOculusXRFaceExpression::NoseWrinklerR, "noseWrinkler_R"); ExpressionNames.Add(EOculusXRFaceExpression::OuterBrowRaiserL, "outerBrowRaiser_L"); ExpressionNames.Add(EOculusXRFaceExpression::OuterBrowRaiserR, "outerBrowRaiser_R"); ExpressionNames.Add(EOculusXRFaceExpression::UpperLidRaiserL, "upperLidRaiser_L"); ExpressionNames.Add(EOculusXRFaceExpression::UpperLidRaiserR, "upperLidRaiser_R"); ExpressionNames.Add(EOculusXRFaceExpression::UpperLipRaiserL, "upperLipRaiser_L"); ExpressionNames.Add(EOculusXRFaceExpression::UpperLipRaiserR, "upperLipRaiser_R"); ExpressionNames.Add(EOculusXRFaceExpression::TongueTipInterdental, "tongueTipInterdental"); ExpressionNames.Add(EOculusXRFaceExpression::TongueTipAlveolar, "tongueTipAlveolar"); ExpressionNames.Add(EOculusXRFaceExpression::TongueFrontDorsalPalate, "tongueFrontDorsalPalate"); ExpressionNames.Add(EOculusXRFaceExpression::TongueMidDorsalPalate, "tongueMidDorsalPalate"); ExpressionNames.Add(EOculusXRFaceExpression::TongueBackDorsalVelar, "tongueBackDorsalVelar"); ExpressionNames.Add(EOculusXRFaceExpression::TongueOut, "tongueOut"); const int defaultFaceExpressionModifierLength = 33; ExpressionModifiers.SetNum(defaultFaceExpressionModifierLength); ExpressionModifiers[0].FaceExpressions = { EOculusXRFaceExpression::EyesClosedL, EOculusXRFaceExpression::EyesClosedR }; ExpressionModifiers[1].FaceExpressions = { EOculusXRFaceExpression::EyesLookDownL, EOculusXRFaceExpression::EyesLookDownR }; ExpressionModifiers[2].FaceExpressions = { EOculusXRFaceExpression::EyesLookLeftL, EOculusXRFaceExpression::EyesLookLeftR }; ExpressionModifiers[3].FaceExpressions = { EOculusXRFaceExpression::EyesLookRightL, EOculusXRFaceExpression::EyesLookRightR }; ExpressionModifiers[4].FaceExpressions = { EOculusXRFaceExpression::EyesLookUpL, EOculusXRFaceExpression::EyesLookUpR }; ExpressionModifiers[5].FaceExpressions = { EOculusXRFaceExpression::LidTightenerL, EOculusXRFaceExpression::LidTightenerR }; ExpressionModifiers[6].FaceExpressions = { EOculusXRFaceExpression::UpperLidRaiserL, EOculusXRFaceExpression::UpperLidRaiserR }; ExpressionModifiers[7].FaceExpressions = { EOculusXRFaceExpression::JawDrop }; ExpressionModifiers[8].FaceExpressions = { EOculusXRFaceExpression::JawSidewaysLeft, EOculusXRFaceExpression::JawSidewaysRight }; ExpressionModifiers[9].FaceExpressions = { EOculusXRFaceExpression::JawThrust }; ExpressionModifiers[10].FaceExpressions = { EOculusXRFaceExpression::LipFunnelerLB, EOculusXRFaceExpression::LipFunnelerLT }; ExpressionModifiers[11].FaceExpressions = { EOculusXRFaceExpression::LipFunnelerRB, EOculusXRFaceExpression::LipFunnelerRT }; ExpressionModifiers[12].FaceExpressions = { EOculusXRFaceExpression::LipPuckerL, EOculusXRFaceExpression::LipPuckerR }; ExpressionModifiers[13].FaceExpressions = { EOculusXRFaceExpression::LipSuckLB, EOculusXRFaceExpression::LipSuckLT }; ExpressionModifiers[14].FaceExpressions = { EOculusXRFaceExpression::LipSuckRB, EOculusXRFaceExpression::LipSuckRT }; ExpressionModifiers[15].FaceExpressions = { EOculusXRFaceExpression::LipsToward }; ExpressionModifiers[16].FaceExpressions = { EOculusXRFaceExpression::LowerLipDepressorL, EOculusXRFaceExpression::LowerLipDepressorR }; ExpressionModifiers[17].FaceExpressions = { EOculusXRFaceExpression::ChinRaiserB, EOculusXRFaceExpression::ChinRaiserT }; ExpressionModifiers[18].FaceExpressions = { EOculusXRFaceExpression::LipCornerDepressorL, EOculusXRFaceExpression::LipCornerDepressorR }; ExpressionModifiers[19].FaceExpressions = { EOculusXRFaceExpression::LipCornerPullerL, EOculusXRFaceExpression::LipCornerPullerR }; ExpressionModifiers[20].FaceExpressions = { EOculusXRFaceExpression::LipStretcherL, EOculusXRFaceExpression::LipStretcherR }; ExpressionModifiers[21].FaceExpressions = { EOculusXRFaceExpression::MouthLeft, EOculusXRFaceExpression::MouthRight }; ExpressionModifiers[22].FaceExpressions = { EOculusXRFaceExpression::LipPressorL, EOculusXRFaceExpression::LipPressorR }; ExpressionModifiers[23].FaceExpressions = { EOculusXRFaceExpression::LipTightenerL, EOculusXRFaceExpression::LipTightenerR }; ExpressionModifiers[24].FaceExpressions = { EOculusXRFaceExpression::UpperLipRaiserL, EOculusXRFaceExpression::UpperLipRaiserR }; ExpressionModifiers[25].FaceExpressions = { EOculusXRFaceExpression::CheekPuffL, EOculusXRFaceExpression::CheekPuffR }; ExpressionModifiers[26].FaceExpressions = { EOculusXRFaceExpression::CheekRaiserL, EOculusXRFaceExpression::CheekRaiserR }; ExpressionModifiers[27].FaceExpressions = { EOculusXRFaceExpression::CheekSuckL, EOculusXRFaceExpression::CheekSuckR }; ExpressionModifiers[28].FaceExpressions = { EOculusXRFaceExpression::DimplerL, EOculusXRFaceExpression::DimplerR }; ExpressionModifiers[29].FaceExpressions = { EOculusXRFaceExpression::NoseWrinklerL, EOculusXRFaceExpression::NoseWrinklerR }; ExpressionModifiers[30].FaceExpressions = { EOculusXRFaceExpression::BrowLowererL, EOculusXRFaceExpression::BrowLowererR }; ExpressionModifiers[31].FaceExpressions = { EOculusXRFaceExpression::InnerBrowRaiserL, EOculusXRFaceExpression::InnerBrowRaiserR }; ExpressionModifiers[32].FaceExpressions = { EOculusXRFaceExpression::OuterBrowRaiserL, EOculusXRFaceExpression::OuterBrowRaiserR }; OculusXRTelemetry::TScopedMarker(static_cast(GetTypeHash(this))); } void UOculusXRFaceTrackingComponent::BeginPlay() { Super::BeginPlay(); if (!UOculusXRMovementFunctionLibrary::IsFaceTrackingSupported()) { // Early exit if face tracking isn't supported UE_LOG(LogOculusXRMovement, Warning, TEXT("Face tracking is not supported. (%s:%s)"), *GetOwner()->GetName(), *GetName()); SetComponentTickEnabled(false); return; } if (TargetMeshComponentName == NAME_None) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Invalid mesh component name. (%s:%s)"), *GetOwner()->GetName(), *GetName()); SetComponentTickEnabled(false); return; } if (!InitializeFaceTracking()) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to initialize face tracking. (%s:%s)"), *GetOwner()->GetName(), *GetName()); SetComponentTickEnabled(false); return; } if (!UOculusXRMovementFunctionLibrary::StartFaceTracking()) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to start face tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); SetComponentTickEnabled(false); return; } ++TrackingInstanceCount; } void UOculusXRFaceTrackingComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (IsComponentTickEnabled()) { if (--TrackingInstanceCount == 0) { if (!UOculusXRMovementFunctionLibrary::StopFaceTracking()) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to stop face tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); } } } Super::EndPlay(EndPlayReason); } void UOculusXRFaceTrackingComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!IsValid(TargetMeshComponent)) { UE_LOG(LogOculusXRMovement, VeryVerbose, TEXT("No target mesh specified. (%s:%s)"), *GetOwner()->GetName(), *GetName()); return; } if (UOculusXRMovementFunctionLibrary::TryGetFaceState(FaceState) && bUpdateFace) { InvalidFaceStateTimer = 0.0f; MorphTargets.ResetMorphTargetCurves(TargetMeshComponent); for (int32 FaceExpressionIndex = 0; FaceExpressionIndex < static_cast(EOculusXRFaceExpression::COUNT); ++FaceExpressionIndex) { if (ExpressionValid[FaceExpressionIndex]) { FName ExpressionName = ExpressionNames[static_cast(FaceExpressionIndex)]; MorphTargets.SetMorphTarget(ExpressionName, FaceState.ExpressionWeights[FaceExpressionIndex]); } } if (bUseModifiers) { for (int32 FaceExpressionModifierIndex = 0; FaceExpressionModifierIndex < ExpressionModifiers.Num(); ++FaceExpressionModifierIndex) { for (int32 FaceExpressionIndex = 0; FaceExpressionIndex < ExpressionModifiers[FaceExpressionModifierIndex].FaceExpressions.Num(); ++FaceExpressionIndex) { auto Expression = ExpressionModifiers[FaceExpressionModifierIndex].FaceExpressions[FaceExpressionIndex]; if (ExpressionValid[static_cast(Expression)]) { FName ExpressionName = ExpressionNames[Expression]; float currentValue = MorphTargets.GetMorphTarget(ExpressionName); currentValue = FMath::Clamp( currentValue * ExpressionModifiers[FaceExpressionModifierIndex].Multiplier, ExpressionModifiers[FaceExpressionModifierIndex].MinValue, ExpressionModifiers[FaceExpressionModifierIndex].MaxValue); MorphTargets.SetMorphTarget(ExpressionName, currentValue); } } } } } else { InvalidFaceStateTimer += DeltaTime; if (InvalidFaceStateTimer >= InvalidFaceDataResetTime) { MorphTargets.ResetMorphTargetCurves(TargetMeshComponent); } } MorphTargets.ApplyMorphTargets(TargetMeshComponent); } void UOculusXRFaceTrackingComponent::SetExpressionValue(EOculusXRFaceExpression Expression, float Value) { if (Expression >= EOculusXRFaceExpression::COUNT) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot set expression value with invalid expression index.")); return; } if (!ExpressionValid[static_cast(Expression)]) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot set expression value for an expression with an invalid associated morph target name. Expression name: %s"), *StaticEnum()->GetValueAsString(Expression)); return; } FName ExpressionName = ExpressionNames[Expression]; MorphTargets.SetMorphTarget(ExpressionName, Value); } float UOculusXRFaceTrackingComponent::GetExpressionValue(EOculusXRFaceExpression Expression) const { if (Expression >= EOculusXRFaceExpression::COUNT) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot request expression value using an invalid expression index.")); return 0.0f; } FName ExpressionName = ExpressionNames[Expression]; if (ExpressionName == NAME_None) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot request expression value for an expression with an invalid associated morph target name. Expression name: %s"), *StaticEnum()->GetValueAsString(Expression)); return 0.0f; } return MorphTargets.GetMorphTarget(ExpressionName); } void UOculusXRFaceTrackingComponent::ClearExpressionValues() { MorphTargets.ClearMorphTargets(); } bool UOculusXRFaceTrackingComponent::InitializeFaceTracking() { TargetMeshComponent = OculusXRUtility::FindComponentByName(GetOwner(), TargetMeshComponentName); if (!IsValid(TargetMeshComponent)) { UE_LOG(LogOculusXRMovement, Warning, TEXT("Could not find skeletal mesh component with name: (%s). (%s:%s)"), *TargetMeshComponentName.ToString(), *GetOwner()->GetName(), *GetName()); return false; } if (TargetMeshComponent != nullptr) { USkeletalMesh* TargetMesh = Cast(TargetMeshComponent->GetSkinnedAsset()); if (TargetMesh != nullptr) { const TMap& MorphTargetIndexMap = TargetMesh->GetMorphTargetIndexMap(); for (const auto& it : ExpressionNames) { ExpressionValid[static_cast(it.Key)] = MorphTargetIndexMap.Contains(it.Value); } return true; } } return false; }