// Fill out your copyright notice in the Description page of Project Settings. #include "FaceAnimation/VHFaceAnimation.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Animation/AnimSequence.h" #include "Components/SkeletalMeshComponent.h" #include "HAL/PlatformFileManager.h" #include "Misc/Paths.h" #include "Misc/FileHelper.h" #include "VirtualHuman.h" #include "CharacterPluginLogging.h" #include "FaceAnimation/VHFacialExpressions.h" UVHFaceAnimation::UVHFaceAnimation() { PrimaryComponentTick.bCanEverTick = true; } void UVHFaceAnimation::BeginPlay() { Super::BeginPlay(); if (!AnimationFilename.IsEmpty()) { LoadAnimationFile(AnimationFilename); } } void UVHFaceAnimation::Play() { PlayFromTime(0.0f); } void UVHFaceAnimation::PlayFromTime(float StartTime) { bCurrentlyPlaying = true; CurrentPlayTime = StartTime - ConstantDelay; LastFrameNr = 0; SearchClosestFrameDataIndex(); if (bUseFaceCalibrationFrame) { Calibration(TimeForCalibration); } if (bResetFaceAfterFinish) { //if we are resetting the face we are also fading in FadeFraction = 0.0f; } } void UVHFaceAnimation::Stop() { bCurrentlyPlaying = false; ResetFace(); } bool UVHFaceAnimation::IsPlaying() { return bCurrentlyPlaying; } float UVHFaceAnimation::GetDuration() const { return AnimationData.TimeSteps.Last().Timestamp; } bool UVHFaceAnimation::LoadAnimationFile(FString Filename) { AnimInstance = Cast<AVirtualHuman>(GetOwner())->GetFaceAnimInstance(); if (!AnimInstance) { VH_WARN("No AnimInstance at LoadAnimationFile of UVHFaceAnimation\n"); } UseMorphs = Cast<AVirtualHuman>(GetOwner())->SupportsMorphs(); if (CachedAnimationData.Contains(Filename)) { AnimationData = CachedAnimationData[Filename]; VH_LOG("Use cached animation data for %s\n", *Filename); } else if (InternalLoadFile(Filename)) { AnimationFilename = Filename; CachedAnimationData.Add(Filename, AnimationData); VH_LOG("Sucessfully loaded and cached animation from file %s\n", *Filename); } else { VH_WARN("Unable to load animation from file %s\n", *Filename); return false; } AnimationData.CurrentFrameValues.Reset(AnimationData.BlendshapeNames.Num()); AnimationData.CurrentFrameValues.AddZeroed(AnimationData.BlendshapeNames.Num()); //sanity check whether all blendshapes are present for the character TArray<FName> PresentMorphTargets; USkeleton* Skeleton = AnimInstance->GetSkelMeshComponent()->GetSkeletalMeshAsset()->GetSkeleton(); Skeleton->GetCurveMetaDataNames(PresentMorphTargets); for (int BlendshapeIndex = 0; BlendshapeIndex < AnimationData.BlendshapeNames.Num(); ++BlendshapeIndex) { FString Blendshape = AnimationData.BlendshapeNames[BlendshapeIndex]; if (!PresentMorphTargets.Contains(FName(*Blendshape))) { if (Blendshape == "Merged_Open_Mouth") { //this morph target is apparently not present in all CC3 exports (but for Henry), so we substitute it with jaw movement for (FFrameData& TimeStep : AnimationData.TimeSteps) { TimeStep.JawRotation = TimeStep.BlendshapeActivations[BlendshapeIndex] * FRotator( 0.0f, -30.0f, 0.0f); } } else if (Blendshape.StartsWith("CTRL_expressions_head") || Blendshape.StartsWith( "CTRL_expressions_bodyNeck")) { //do nothing as we don't want to move the head. } else { VH_WARN("Mapped blendshape for face animation %s is not present, the animation will not fully work!\n", *Blendshape); } } } return true; } bool UVHFaceAnimation::Calibrate(FString CalibrationFilename, float CalibrationTime) { FString CurrentlyLoadedAnim = AnimationFilename; if (!LoadAnimationFile(CalibrationFilename)) { VH_WARN("Animation %s which should be used for calibration cannot be loaded", *CalibrationFilename); return false; } Calibration(CalibrationTime); if (!CurrentlyLoadedAnim.IsEmpty()) { LoadAnimationFile(CurrentlyLoadedAnim); } return true; } // Called every frame void UVHFaceAnimation::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (bCurrentlyPlaying) { CurrentPlayTime += DeltaTime; float TimeLeft = AnimationData.TimeSteps.Last().Timestamp - CurrentPlayTime; if (AnimationData.TimeSteps.Num() == 0 || TimeLeft < 0) { bCurrentlyPlaying = false; if (bResetFaceAfterFinish) { ResetFace(); } return; } if (TimeLeft < FadeInOutTime && bResetFaceAfterFinish) { //we should be fading out //in this case FadeDurationLeft is counted up FadeFraction = TimeLeft / FadeInOutTime; } else { FadeFraction += DeltaTime / FadeInOutTime; } FadeFraction = FMath::Clamp(FadeFraction, 0.0f, 1.0f); int FrameNr = SearchClosestFrameDataIndex(); LastFrameNr = FrameNr; ApplyFrameData(FrameNr); bIsFaceReset = false; } else if (bResetFaceAfterFinish && !bIsFaceReset) { //in case this is used together with a FacialExpression //we have to do this, so that the right data is pulled from the // FacialExpression component every frame (especially before staring that actual animation) ResetFace(); bIsFaceReset = true; } } bool UVHFaceAnimation::InternalLoadFile(FString FileName) { VH_WARN("This should be implemented by a subclass!!! Using this class directly is not intended.\n"); return false; } void UVHFaceAnimation::ApplyFrameData(int FrameNr) { float AmountBefore = 1.0f; float AmountAfter = 0.0f; if (FrameNr + 1 < AnimationData.TimeSteps.Num()) { //we might be in between two timesteps of our data const float TimeDiff = AnimationData.TimeSteps[FrameNr + 1].Timestamp - AnimationData.TimeSteps[ FrameNr].Timestamp; const float InterFrameTime = CurrentPlayTime - AnimationData.TimeSteps[FrameNr].Timestamp; AmountAfter = InterFrameTime / TimeDiff; AmountBefore = 1.0f - AmountAfter; } for (int i = 0; i < AnimationData.TimeSteps[FrameNr].BlendshapeActivations.Num(); ++i) { float Value = AnimationData.TimeSteps[FrameNr].BlendshapeActivations[i]; if (AmountAfter > 0.01f) //if it doesn't play a role only use timestep before { Value = AmountBefore * Value + AmountAfter * AnimationData.TimeSteps[FrameNr + 1].BlendshapeActivations[i]; } if (IsCalibrationPresent()) { Value -= CalibrationFrame.BlendshapeActivations[i]; } //if we are resetting also fade in Value *= FadeFraction; //if we are fading check whether there is a FacialExpression set which should be faded to Value += (1.0f - FadeFraction) * GetFacialExpressionMorphTargetValue(*AnimationData.BlendshapeNames[i]); AnimationData.CurrentFrameValues[i] = Value; if (UseMorphs) { // in this case set it already otherwise the animation blueprint will pull the data AnimInstance->SetMorphTarget(*AnimationData.BlendshapeNames[i], Value); } } if (bUseHeadRotation) { FRotator HeadRotation = AnimationData.TimeSteps[FrameNr].HeadRotation; if (AmountAfter > 0.01f) { HeadRotation = AmountBefore * HeadRotation + AmountAfter * AnimationData.TimeSteps[ FrameNr + 1].HeadRotation; } if (IsCalibrationPresent()) { HeadRotation -= CalibrationFrame.HeadRotation; } AnimInstance->SkelControl_Head = HeadRotation; } if (bUseEyeRotations) { FRotator LeftEye = AnimationData.TimeSteps[FrameNr].LeftEyeRotation; FRotator RightEye = AnimationData.TimeSteps[FrameNr].RightEyeRotation; if (AmountAfter > 0.01f) { LeftEye = AmountBefore * LeftEye + AmountAfter * AnimationData.TimeSteps[FrameNr + 1].LeftEyeRotation; RightEye = AmountBefore * RightEye + AmountAfter * AnimationData.TimeSteps[FrameNr + 1].RightEyeRotation; } if (IsCalibrationPresent()) { LeftEye -= CalibrationFrame.LeftEyeRotation; RightEye -= CalibrationFrame.RightEyeRotation; } AnimInstance->SkelControl_LeftEyeRot = LeftEye; AnimInstance->SkelControl_RightEyeRot = RightEye; } FRotator JawRotation = AnimationData.TimeSteps[FrameNr].JawRotation; if (AmountAfter > 0.01f) { JawRotation = AmountBefore * JawRotation + AmountAfter * AnimationData.TimeSteps[ FrameNr + 1].JawRotation; } if (IsCalibrationPresent()) { JawRotation -= CalibrationFrame.JawRotation; //since the jaw has to be rotated in a neutral position for CC3, we have to add that back in FFrameData NeutralData; JawRotation += NeutralData.JawRotation; } AnimInstance->SkelControl_JawRot = JawRotation; } void UVHFaceAnimation::ResetFace() { if (AnimationData.BlendshapeNames.Num() == 0) return; int i = 0; for (FString Blendshape : AnimationData.BlendshapeNames) { float Value = GetFacialExpressionMorphTargetValue(FName(*Blendshape)); AnimationData.CurrentFrameValues[i] = Value; if (UseMorphs) { AnimInstance->SetMorphTarget(*Blendshape, Value); } i++; } FFrameData NeutralFrame; if (bUseHeadRotation) { AnimInstance->SkelControl_Head = NeutralFrame.HeadRotation; } if (bUseEyeRotations) { AnimInstance->SkelControl_LeftEyeRot = NeutralFrame.LeftEyeRotation; AnimInstance->SkelControl_RightEyeRot = NeutralFrame.RightEyeRotation; } AnimInstance->SkelControl_JawRot = NeutralFrame.JawRotation; } int UVHFaceAnimation::SearchClosestFrameDataIndex() { if (AnimationData.TimeSteps[LastFrameNr].Timestamp <= CurrentPlayTime && ( LastFrameNr + 1 >= AnimationData.TimeSteps.Num() || AnimationData.TimeSteps[ LastFrameNr + 1].Timestamp > CurrentPlayTime)) { return LastFrameNr; } if (LastFrameNr + 2 < AnimationData.TimeSteps.Num() && AnimationData.TimeSteps[LastFrameNr + 1]. Timestamp <= CurrentPlayTime && AnimationData.TimeSteps[ LastFrameNr + 2].Timestamp > CurrentPlayTime) { return LastFrameNr + 1; } //so the two most obvious solutions don't work so do a full search for (int i = 0; i < AnimationData.TimeSteps.Num() - 1; ++i) { if (AnimationData.TimeSteps[i].Timestamp <= CurrentPlayTime && AnimationData.TimeSteps[i + 1]. Timestamp > CurrentPlayTime) { return i; } } return AnimationData.TimeSteps.Num() - 1; } void UVHFaceAnimation::Calibration(float Time) { for (FFrameData Date : AnimationData.TimeSteps) { if (Date.Timestamp <= Time) { CalibrationFrame = Date; return; } } } bool UVHFaceAnimation::IsCalibrationPresent() const { return CalibrationFrame.BlendshapeActivations.Num() > 0; } float UVHFaceAnimation::GetFacialExpressionMorphTargetValue(const FName& MorphName) { UVHFacialExpressions* Expression = Cast<UVHFacialExpressions>( GetOwner()->GetComponentByClass(UVHFacialExpressions::StaticClass())); if (Expression == nullptr) { //the actor does not have a UVHFacialExpressions component, so return neutral face value return 0.0f; } const float* Value = Expression->GetCurrentAnimationValues().Find(MorphName); if (Value != nullptr) return *Value; return 0.0f; } const FAnimationData& UVHFaceAnimation::GetAnimData() { return AnimationData; } UAnimSequence* UVHFaceAnimation::SaveAsAnimSequence(FString AnimName) { VH_ERROR( "This function is out of order because of deprecated functions and variables. If needed, reimplement it.\n"); return nullptr; //#if WITH_EDITOR // const FString AnimationRelative = FPaths::Combine( // TEXT("/Game"), AnimName); // const FString AnimationAbsolute = FPaths::Combine( // FPaths::ProjectContentDir(), AnimName); // //remove the path if any // const FString AnimNameOnly = FPaths::GetBaseFilename(AnimName); // // UAnimSequence* AnimationSequence = nullptr; // // UPackage* Package = LoadPackage(nullptr, *AnimationAbsolute, LOAD_None); // // if (Package != nullptr) // { // AnimationSequence = FindObject<UAnimSequence>(Package, *AnimNameOnly); // } // else // { // Package = CreatePackage(*AnimationRelative); // } // // if (AnimationSequence == nullptr) // { // AnimationSequence = NewObject<UAnimSequence>( // Package, UAnimSequence::StaticClass(), *AnimNameOnly, // RF_Public | RF_Standalone); // // IAnimationDataController& Controller = AnimationSequence->GetController(); // // if (AnimationSequence) // { // // // AnimationSequence->SetSkeleton(AnimInstance->GetSkelMeshComponent()->SkeletalMesh->GetSkeleton()); // //AnimationSequence->SetSequenceLength(AnimationData.TimeSteps.Last().Timestamp); // Controller.SetPlayLength(AnimationData.TimeSteps.Last().Timestamp); // //rate = nr frames / length // Controller.SetFrameRate(FFrameRate(AnimationData.TimeSteps.Num(), AnimationData.TimeSteps.Last().Timestamp)); // // USkeleton* Skeleton = AnimationSequence->GetSkeleton(); // FFloatCurve* curve; // FString ContextString; // FSmartName MorphName; // // //Clear Curves // //AnimationSequence->RawCurveData.Empty(); // FRawCurveTracks RawCurveData; // RawCurveData = AnimationSequence->GetCurveData(); // RawCurveData.Empty(); // // //Add Morph Curves // for (int MorphTargetIndex = 0; MorphTargetIndex < AnimationData.BlendshapeNames.Num(); MorphTargetIndex++) // { // FString BlendshapeName = AnimationData.BlendshapeNames[MorphTargetIndex]; // Skeleton->AddSmartNameAndModify(USkeleton::AnimCurveMappingName, // *BlendshapeName, MorphName); // AnimationSequence->Modify(true); // RawCurveData.AddCurveData(MorphName); // RawCurveData.GetCurveData(MorphName.UID)-> // LastObservedName_DEPRECATED = *BlendshapeName; // // curve = StaticCast<FFloatCurve*>( // RawCurveData.GetCurveData( // MorphName.UID, ERawCurveTrackTypes::RCT_Float)); // // for (int FrameNr = 0; FrameNr < AnimationData.TimeSteps.Num(); FrameNr++) // { // FFrameData& Frame = AnimationData.TimeSteps[FrameNr]; // float Value = Frame.BlendshapeActivations[MorphTargetIndex]; // if (IsCalibrationPresent()) // { // Value -= CalibrationFrame.BlendshapeActivations[MorphTargetIndex]; // } // curve->FloatCurve.AddKey(Frame.Timestamp, Value); // } // } // // FAssetRegistryModule::AssetCreated(AnimationSequence); // const FString Filename = FString::Printf( // TEXT("%s%s"), *AnimationAbsolute, // *FPackageName::GetAssetPackageExtension()); // UPackage::SavePackage(Package, nullptr, RF_Public | RF_Standalone, // *Filename); // } // // // Add bone curves // TArray<FQuat4f> HeadRawTrack; // TArray<FQuat4f> REyeRawTrack; // TArray<FQuat4f> LEyeRawTrack; // TArray<FQuat4f> JawRawTrack; // for (int FrameNr = 0; FrameNr < AnimationData.TimeSteps.Num(); FrameNr++) { // FFrameData& Timestep = AnimationData.TimeSteps[FrameNr]; // HeadRawTrack.Add(FQuat4f(Timestep.HeadRotation.Quaternion())); // REyeRawTrack.Add(FQuat4f(Timestep.RightEyeRotation.Quaternion())); // LEyeRawTrack.Add(FQuat4f(Timestep.LeftEyeRotation.Quaternion())); // JawRawTrack.Add(FQuat4f(Timestep.JawRotation.Quaternion())); // } // // AVirtualHuman* owner = Cast<AVirtualHuman>(GetOwner()); // const BoneNames& bones = owner->GetBoneNames(); // // if (bUseHeadRotation) { // Controller.AddBoneTrack(bones.head); // Controller.SetBoneTrackKeys(bones.head, {}, HeadRawTrack, {}); // } // if (bUseEyeRotations) { // Controller.AddBoneTrack(bones.eye_r); // Controller.SetBoneTrackKeys(bones.eye_r, {}, REyeRawTrack, {}); // Controller.AddBoneTrack(bones.eye_l); // Controller.SetBoneTrackKeys(bones.eye_l, {}, LEyeRawTrack, {}); // } // Controller.AddBoneTrack(bones.jaw); // Controller.SetBoneTrackKeys(bones.jaw, {}, JawRawTrack, {}); // // //the metahuman jaw bone only lowers the chin and not the mouth and teeth // if (owner->GetBodyType() == EBodyType::MetaHuman) { // Controller.AddBoneTrack(bones.teeth_lower); // Controller.SetBoneTrackKeys(bones.teeth_lower, {}, JawRawTrack, {}); // Controller.AddBoneTrack(bones.mouth_lower); // Controller.SetBoneTrackKeys(bones.mouth_lower, {}, JawRawTrack, {}); // } // // } // return AnimationSequence; //#else // VH_ERROR("UVHFaceAnimation::SaveAsAnimSequence can only be used in Editor builds!\n"); // return nullptr; //#endif }