Files
UnrealEngine/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp
2025-05-18 13:04:45 +08:00

1222 lines
38 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimPreviewInstance.h"
#include "Animation/AnimMontage.h"
#include "Animation/DebugSkelMeshComponent.h"
#include "AnimationRuntime.h"
#include "Animation/AnimationSettings.h"
#include "Animation/AnimSequence.h"
#include "Animation/AnimSequenceHelpers.h"
#if WITH_EDITOR
#include "ScopedTransaction.h"
#endif
#define LOCTEXT_NAMESPACE "AnimPreviewInstance"
void FAnimPreviewInstanceProxy::Initialize(UAnimInstance* InAnimInstance)
{
FAnimSingleNodeInstanceProxy::Initialize(InAnimInstance);
bSetKey = false;
// link up our curve post-process mini-graph
PoseBlendNode.SourcePose.SetLinkNode(&CurveSource);
CurveSource.SourcePose.SetLinkNode(&SingleNode);
FAnimationInitializeContext InitContext(this);
PoseBlendNode.Initialize_AnyThread(InitContext);
CurveSource.Initialize_AnyThread(InitContext);
}
void FAnimPreviewInstanceProxy::ResetModifiedBone(bool bCurveController)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
Controllers.Empty();
}
FAnimNode_ModifyBone* FAnimPreviewInstanceProxy::FindModifiedBone(const FName& InBoneName, bool bCurveController)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
return Controllers.FindByPredicate(
[InBoneName](const FAnimNode_ModifyBone& InController) -> bool
{
return InController.BoneToModify.BoneName == InBoneName;
}
);
}
void FAnimPreviewInstanceProxy::SetAnimationAsset(UAnimationAsset* NewAsset, USkeletalMeshComponent* MeshComponent, bool bIsLooping, float InPlayRate)
{
// reinitialize pose blend node for pose assets
// this is necessary because sometimes in the editor, we add pose then list of pose changes, but
// this node continue use previous information
// @todo: should we initialize all nodes?
if (NewAsset && NewAsset->IsA(UPoseAsset::StaticClass()))
{
FAnimationInitializeContext Context(this);
PoseBlendNode.Initialize_AnyThread(Context);
}
FAnimSingleNodeInstanceProxy::SetAnimationAsset(NewAsset, MeshComponent, bIsLooping, InPlayRate);
}
FAnimNode_ModifyBone& FAnimPreviewInstanceProxy::ModifyBone(const FName& InBoneName, bool bCurveController)
{
FAnimNode_ModifyBone* SingleBoneController = FindModifiedBone(InBoneName, bCurveController);
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
if(SingleBoneController == nullptr)
{
int32 NewIndex = Controllers.Add(FAnimNode_ModifyBone());
SingleBoneController = &Controllers[NewIndex];
}
SingleBoneController->BoneToModify.BoneName = InBoneName;
if (bCurveController)
{
SingleBoneController->TranslationMode = BMM_Additive;
SingleBoneController->TranslationSpace = BCS_BoneSpace;
SingleBoneController->RotationMode = BMM_Additive;
SingleBoneController->RotationSpace = BCS_BoneSpace;
SingleBoneController->ScaleMode = BMM_Additive;
SingleBoneController->ScaleSpace = BCS_BoneSpace;
}
else
{
SingleBoneController->TranslationMode = BMM_Replace;
SingleBoneController->TranslationSpace = BCS_BoneSpace;
SingleBoneController->RotationMode = BMM_Replace;
SingleBoneController->RotationSpace = BCS_BoneSpace;
SingleBoneController->ScaleMode = BMM_Replace;
SingleBoneController->ScaleSpace = BCS_BoneSpace;
}
return *SingleBoneController;
}
void FAnimPreviewInstanceProxy::RemoveBoneModification(const FName& InBoneName, bool bCurveController)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
Controllers.RemoveAll(
[InBoneName](const FAnimNode_ModifyBone& InController)
{
return InController.BoneToModify.BoneName == InBoneName;
}
);
}
void FAnimPreviewInstanceProxy::Update(float DeltaSeconds)
{
// we cant update on a worker thread here because of the key delegate needing to be fired
check(IsInGameThread());
FAnimSingleNodeInstanceProxy::Update(DeltaSeconds);
}
void FAnimPreviewInstanceProxy::UpdateAnimationNode(const FAnimationUpdateContext& InContext)
{
if (CopyPoseNode.SourceMeshComponent.IsValid())
{
CopyPoseNode.Update_AnyThread(InContext);
}
else if (UPoseAsset* PoseAsset = Cast<UPoseAsset>(CurrentAsset))
{
PoseBlendNode.PoseAsset = PoseAsset;
PoseBlendNode.Update_AnyThread(InContext);
}
else
{
FAnimSingleNodeInstanceProxy::UpdateAnimationNode(InContext);
}
}
void FAnimPreviewInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds)
{
FAnimSingleNodeInstanceProxy::PreUpdate(InAnimInstance, DeltaSeconds);
if (CopyPoseNode.SourceMeshComponent.IsValid())
{
CopyPoseNode.PreUpdate(InAnimInstance);
}
CurveSource.PreUpdate(InAnimInstance);
}
bool FAnimPreviewInstanceProxy::Evaluate(FPoseContext& Output)
{
// we cant evaluate on a worker thread here because of the key delegate needing to be fired
check(IsInGameThread());
if (CopyPoseNode.SourceMeshComponent.IsValid())
{
CopyPoseNode.Evaluate_AnyThread(Output);
}
else
{
if (UPoseAsset* PoseAsset = Cast<UPoseAsset>(CurrentAsset))
{
PoseBlendNode.Evaluate_AnyThread(Output);
}
else
{
FAnimSingleNodeInstanceProxy::Evaluate(Output);
}
if (bEnableControllers)
{
UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent>(GetSkelMeshComponent());
if(Component)
{
// update curve controllers
UpdateCurveController();
// create bone controllers from
if(BoneControllers.Num() > 0 || CurveBoneControllers.Num() > 0)
{
FPoseContext PreController(Output), PostController(Output);
// if set key is true, we should save pre controller local space transform
// so that we can calculate the delta correctly
if(bSetKey)
{
PreController = Output;
}
FComponentSpacePoseContext ComponentSpacePoseContext(Output.AnimInstanceProxy);
ComponentSpacePoseContext.Pose.InitPose(Output.Pose);
// apply curve data first
ApplyBoneControllers(CurveBoneControllers, ComponentSpacePoseContext);
// and now apply bone controllers data
// it is possible they can be overlapping, but then bone controllers will overwrite
ApplyBoneControllers(BoneControllers, ComponentSpacePoseContext);
// convert back to local @todo check this
FCSPose<FCompactPose>::ConvertComponentPosesToLocalPoses(ComponentSpacePoseContext.Pose, Output.Pose);
if(bSetKey)
{
// now we have post controller, and calculate delta now
PostController = Output;
SetKeyImplementation(PreController.Pose, PostController.Pose);
}
}
// if any other bone is selected, still go for set key even if nothing changed
else if(Component->BonesOfInterest.Num() > 0)
{
if(bSetKey)
{
// in this case, pose is same
SetKeyImplementation(Output.Pose, Output.Pose);
}
}
}
// we should unset here, just in case somebody clicks the key when it's not valid
if(bSetKey)
{
bSetKey = false;
}
}
}
return true;
}
void FAnimPreviewInstanceProxy::RefreshCurveBoneControllers(UAnimationAsset* AssetToRefreshFrom)
{
// go through all curves and see if it has Transform Curve
// if so, find what bone that belong to and create BoneMOdifier for them
check(!CurrentAsset || CurrentAsset == AssetToRefreshFrom);
UAnimSequence* CurrentSequence = Cast<UAnimSequence>(AssetToRefreshFrom);
CurveBoneControllers.Empty();
// do not apply if BakedAnimation is on
if(CurrentSequence && CurrentSequence->IsDataModelValid())
{
// make sure if this needs source update
if (CurrentSequence->GetDataModel()->GetNumberOfTransformCurves() == 0)
{
return;
}
GetRequiredBones().SetUseSourceData(true);
const TArray<FTransformCurve>& Curves = CurrentSequence->GetDataModel()->GetCurveData().TransformCurves;
USkeleton* MySkeleton = CurrentSequence->GetSkeleton();
for (auto& Curve : Curves)
{
// skip if disabled
if (Curve.GetCurveTypeFlag(AACF_Disabled))
{
continue;
}
// add bone modifier
FName BoneName = Curve.GetName();
if (BoneName != NAME_None && MySkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName) != INDEX_NONE)
{
ModifyBone(BoneName, true);
}
}
}
}
void FAnimPreviewInstanceProxy::UpdateCurveController()
{
// evaluate the curve data first
UAnimSequenceBase* CurrentSequence = Cast<UAnimSequenceBase>(CurrentAsset);
USkeleton* PreviewSkeleton = (CurrentSequence) ? CurrentSequence->GetSkeleton() : nullptr;
if (CurrentSequence && PreviewSkeleton)
{
TMap<FName, FTransform> ActiveCurves;
UE::Anim::EvaluateTransformCurvesFromModel(CurrentSequence->GetDataModel(), ActiveCurves, GetCurrentTime(), 1.f);
// make sure those curves exists in the bone controller, otherwise problem
if ( ActiveCurves.Num() > 0 )
{
for(auto& SingleBoneController : CurveBoneControllers)
{
// make sure the curve exists
FName CurveName = SingleBoneController.BoneToModify.BoneName;
// we should add extra key to front and back whenever animation length changes or so.
// animation length change requires to bake down animation first
// this will make sure all the keys that were embedded at the start/end will automatically be backed to the data
const FTransform* Value = ActiveCurves.Find(CurveName);
if (Value)
{
// apply this change
SingleBoneController.Translation = Value->GetTranslation();
SingleBoneController.Scale = Value->GetScale3D();
// sasd we're converting twice
SingleBoneController.Rotation = Value->GetRotation().Rotator();
}
}
}
else
{
// should match
ensure (CurveBoneControllers.Num() == 0);
CurveBoneControllers.Empty();
}
}
}
void FAnimPreviewInstanceProxy::ApplyBoneControllers(TArray<FAnimNode_ModifyBone> &InBoneControllers, FComponentSpacePoseContext& ComponentSpacePoseContext)
{
if(USkeleton* LocalSkeleton = ComponentSpacePoseContext.AnimInstanceProxy->GetSkeleton())
{
for (auto& SingleBoneController : InBoneControllers)
{
TArray<FBoneTransform> BoneTransforms;
FAnimationCacheBonesContext Proxy(this);
SingleBoneController.CacheBones_AnyThread(Proxy);
if (SingleBoneController.IsValidToEvaluate(LocalSkeleton, ComponentSpacePoseContext.Pose.GetPose().GetBoneContainer()))
{
SingleBoneController.EvaluateSkeletalControl_AnyThread(ComponentSpacePoseContext, BoneTransforms);
if (BoneTransforms.Num() > 0)
{
ComponentSpacePoseContext.Pose.LocalBlendCSBoneTransforms(BoneTransforms, 1.0f);
}
}
}
}
}
void FAnimPreviewInstanceProxy::SetKeyImplementation(const FCompactPose& PreControllerInLocalSpace, const FCompactPose& PostControllerInLocalSpace)
{
#if WITH_EDITOR
// evaluate the curve data first
UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset);
UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent> (GetSkelMeshComponent());
USkeleton* PreviewSkeleton = (CurrentSequence) ? CurrentSequence->GetSkeleton() : nullptr;
if(CurrentSequence && PreviewSkeleton && Component && Component->GetSkeletalMeshAsset())
{
FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key"));
CurrentSequence->Modify(true);
GetAnimInstanceObject()->Modify();
IAnimationDataController& Controller = CurrentSequence->GetController();
Controller.OpenBracket(LOCTEXT("AnimPreviewInstanceProxy_SetKey", "Set Key"));
TArray<FName> BonesToModify;
// need to get component transform first. Depending on when this gets called, the transform is not up-to-date.
// first look at the bonecontrollers, and convert each bone controller to transform curve key
// and add new curvebonecontrollers with additive data type
// clear bone controller data
for(auto& SingleBoneController : BoneControllers)
{
// find bone name, and just get transform of the bone in local space
// and get the additive data
// find if this already exists, then just add curve data only
FName BoneName = SingleBoneController.BoneToModify.BoneName;
// now convert data
const FMeshPoseBoneIndex MeshBoneIndex(Component->GetBoneIndex(BoneName));
const FCompactPoseBoneIndex BoneIndex = GetRequiredBones().MakeCompactPoseIndex(MeshBoneIndex);
FTransform LocalTransform = PostControllerInLocalSpace[BoneIndex];
// now we have LocalTransform and get additive data
FTransform AdditiveTransform = LocalTransform.GetRelativeTransform(PreControllerInLocalSpace[BoneIndex]);
AddKeyToSequence(CurrentSequence, GetCurrentTime(), BoneName, AdditiveTransform);
BonesToModify.Add(BoneName);
}
// see if the bone is selected right now and if that is added - if bone is selected, we should add identity key to it.
if ( Component->BonesOfInterest.Num() > 0 )
{
// if they're selected, we should add to the modifyBone list even if they're not modified, so that they can key that point.
// first make sure those are added
// if not added, make sure to set the key for them
for (const auto& BoneIndex : Component->BonesOfInterest)
{
FName BoneName = Component->GetBoneName(BoneIndex);
// if it's not on BonesToModify, add identity here.
if (!BonesToModify.Contains(BoneName))
{
AddKeyToSequence(CurrentSequence, GetCurrentTime(), BoneName, FTransform::Identity);
}
}
}
Controller.CloseBracket();
ResetModifiedBone(false);
OnSetKeyCompleteDelegate.Broadcast();
}
#endif
}
void FAnimPreviewInstanceProxy::AddKeyToSequence(UAnimSequence* Sequence, float Time, const FName& BoneName, const FTransform& AdditiveTransform)
{
Sequence->AddKeyToSequence(Time, BoneName, AdditiveTransform);
// now add to the controller
// find if it exists in CurveBoneController
// make sure you add it there
ModifyBone(BoneName, true);
GetRequiredBones().SetUseSourceData(true);
}
void FAnimPreviewInstanceProxy::SetDebugSkeletalMeshComponent(USkeletalMeshComponent* InSkeletalMeshComponent)
{
CopyPoseNode.SourceMeshComponent = InSkeletalMeshComponent;
CopyPoseNode.Initialize_AnyThread(FAnimationInitializeContext(this));
}
USkeletalMeshComponent* FAnimPreviewInstanceProxy::GetDebugSkeletalMeshComponent() const
{
return CopyPoseNode.SourceMeshComponent.Get();
}
UAnimPreviewInstance::UAnimPreviewInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
RootMotionMode = ERootMotionMode::RootMotionFromEverything;
bUseMultiThreadedAnimationUpdate = false;
}
static FArchive& operator<<(FArchive& Ar, FAnimNode_ModifyBone& ModifyBone)
{
FAnimNode_ModifyBone::StaticStruct()->SerializeItem(Ar, &ModifyBone, nullptr);
return Ar;
}
void UAnimPreviewInstance::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
if(Ar.IsTransacting())
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Ar << Proxy.GetBoneControllers();
Ar << Proxy.GetCurveBoneControllers();
}
}
void UAnimPreviewInstance::NativeInitializeAnimation()
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
// Cache our play state from the previous animation otherwise set to play
bool bCachedIsPlaying = (CurrentAsset != nullptr) ? Proxy.IsPlaying() : true;
Super::NativeInitializeAnimation();
Proxy.SetPlaying(bCachedIsPlaying);
Proxy.RefreshCurveBoneControllers(CurrentAsset);
}
void UAnimPreviewInstance::Montage_Advance(float DeltaTime)
{
/*
We're running in the Animation Editor.
Call 'EditorOnly_PreAdvance' on montage instances.
So they can do editor specific updates.
*/
for (int32 InstanceIndex = 0; InstanceIndex < MontageInstances.Num(); InstanceIndex++)
{
FAnimMontageInstance* const MontageInstance = MontageInstances[InstanceIndex];
if (MontageInstance && MontageInstance->IsValid())
{
MontageInstance->EditorOnly_PreAdvance();
}
}
Super::Montage_Advance(DeltaTime);
}
FAnimNode_ModifyBone* UAnimPreviewInstance::FindModifiedBone(const FName& InBoneName, bool bCurveController/*=false*/)
{
return GetProxyOnGameThread<FAnimPreviewInstanceProxy>().FindModifiedBone(InBoneName, bCurveController);
}
FAnimNode_ModifyBone& UAnimPreviewInstance::ModifyBone(const FName& InBoneName, bool bCurveController/*=false*/)
{
return GetProxyOnGameThread<FAnimPreviewInstanceProxy>().ModifyBone(InBoneName, bCurveController);
}
void UAnimPreviewInstance::RemoveBoneModification(const FName& InBoneName, bool bCurveController/*=false*/)
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().RemoveBoneModification(InBoneName, bCurveController);
}
void UAnimPreviewInstance::ResetModifiedBone(bool bCurveController/*=false*/)
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().ResetModifiedBone(bCurveController);
}
const TArray<FAnimNode_ModifyBone>& UAnimPreviewInstance::GetBoneControllers()
{
return GetProxyOnGameThread<FAnimPreviewInstanceProxy>().GetBoneControllers();
}
#if WITH_EDITOR
void UAnimPreviewInstance::SetKey()
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().SetKey();
}
FDelegateHandle UAnimPreviewInstance::AddKeyCompleteDelegate(FSimpleMulticastDelegate::FDelegate InOnSetKeyCompleteDelegate)
{
return GetProxyOnGameThread<FAnimPreviewInstanceProxy>().AddKeyCompleteDelegate(InOnSetKeyCompleteDelegate);
}
void UAnimPreviewInstance::RemoveKeyCompleteDelegate(FDelegateHandle InDelegateHandle)
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().RemoveKeyCompleteDelegate(InDelegateHandle);
}
#endif
void UAnimPreviewInstance::RefreshCurveBoneControllers()
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().RefreshCurveBoneControllers(CurrentAsset);
}
/** Set SkeletalControl Alpha**/
void UAnimPreviewInstance::SetSkeletalControlAlpha(float InSkeletalControlAlpha)
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().SetSkeletalControlAlpha(InSkeletalControlAlpha);
}
UAnimSequence* UAnimPreviewInstance::GetAnimSequence()
{
return Cast<UAnimSequence>(CurrentAsset);
}
void UAnimPreviewInstance::RestartMontage(UAnimMontage* Montage, FName FromSection)
{
if (Montage == CurrentAsset)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
MontagePreviewType = EMPT_Normal;
// since this is preview, we would like not to blend in
// just hard stop here
Montage_Stop(0.0f, Montage);
Montage_Play(Montage, Proxy.GetPlayRate());
MontagePreview_RemoveBlendOut();
if (FromSection != NAME_None)
{
Montage_JumpToSection(FromSection);
}
MontagePreview_SetLoopNormal(Proxy.IsLooping(), Montage->GetSectionIndex(FromSection));
}
}
void UAnimPreviewInstance::SetAnimationAsset(UAnimationAsset* NewAsset, bool bIsLooping, float InPlayRate)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
// make sure to turn that off before setting new asset
Proxy.GetRequiredBones().SetUseSourceData(false);
Super::SetAnimationAsset(NewAsset, bIsLooping, InPlayRate);
RootMotionMode = Cast<UAnimMontage>(CurrentAsset) != nullptr ? ERootMotionMode::RootMotionFromMontagesOnly : ERootMotionMode::RootMotionFromEverything;
// disable playing for single frame assets
UAnimSequence* AnimSequence = GetAnimSequence();
bool bSingleFrameSequence = (AnimSequence != nullptr) ? AnimSequence->GetNumberOfSampledKeys() <= 1 : false;
if (bSingleFrameSequence && Proxy.IsPlaying())
{
Proxy.SetPlaying(false);
}
// should re sync up curve bone controllers from new asset
Proxy.RefreshCurveBoneControllers(CurrentAsset);
}
void UAnimPreviewInstance::MontagePreview_SetLooping(bool bIsLooping)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Proxy.SetLooping(bIsLooping);
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
switch (MontagePreviewType)
{
case EMPT_AllSections:
MontagePreview_SetLoopAllSections(Proxy.IsLooping());
break;
case EMPT_Normal:
default:
MontagePreview_SetLoopNormal(Proxy.IsLooping());
break;
}
}
}
void UAnimPreviewInstance::MontagePreview_SetPlaying(bool bIsPlaying)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Proxy.SetPlaying(bIsPlaying);
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
CurMontageInstance->bPlaying = Proxy.IsPlaying();
}
else if (Proxy.IsPlaying())
{
UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
if (Montage)
{
switch (MontagePreviewType)
{
case EMPT_AllSections:
MontagePreview_PreviewAllSections();
break;
case EMPT_Normal:
default:
MontagePreview_PreviewNormal();
break;
}
}
}
}
void UAnimPreviewInstance::MontagePreview_SetReverse(bool bInReverse)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Super::SetReverse(bInReverse);
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
// copy the current playrate
CurMontageInstance->SetPlayRate(Proxy.GetPlayRate());
}
}
void UAnimPreviewInstance::MontagePreview_Restart()
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
switch (MontagePreviewType)
{
case EMPT_AllSections:
MontagePreview_PreviewAllSections();
break;
case EMPT_Normal:
default:
MontagePreview_PreviewNormal();
break;
}
}
}
void UAnimPreviewInstance::MontagePreview_StepForward()
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
bool bWasPlaying = IsPlayingMontage() && (Proxy.IsLooping() || Proxy.IsPlaying()); // we need to handle non-looped case separately, even if paused during playthrough
MontagePreview_SetReverse(false);
if (! bWasPlaying)
{
if (! Proxy.IsLooping())
{
float StoppedAt = Proxy.GetCurrentTime();
if (! bWasPlaying)
{
// play montage but at last known location
MontagePreview_Restart();
SetPosition(StoppedAt, false);
}
int32 LastPreviewSectionIdx = MontagePreview_FindLastSection(MontagePreviewStartSectionIdx);
if (FMath::Abs(Proxy.GetCurrentTime() - (Montage->CompositeSections[LastPreviewSectionIdx].GetTime() + Montage->GetSectionLength(LastPreviewSectionIdx))) <= MontagePreview_CalculateStepLength())
{
// we're at the end, jump right to the end
Montage_JumpToSectionsEnd(Montage->GetSectionName(LastPreviewSectionIdx));
if (! bWasPlaying)
{
MontagePreview_SetPlaying(false);
}
return; // can't go further than beginning of this
}
}
else
{
MontagePreview_Restart();
}
}
MontagePreview_SetPlaying(true);
// Advance a single frame, leaving it paused afterwards
const int32 NumKeys = Montage->GetNumberOfSampledKeys();
// Add DELTA to prefer next frame when we're close to the boundary
const float CurrentFraction = Proxy.GetCurrentTime() / Montage->GetPlayLength() + DELTA;
const float NextFrame = FMath::Clamp<float>(FMath::FloorToFloat(CurrentFraction * NumKeys) + 1.0f, 0, NumKeys);
const float NewTime = Montage->GetPlayLength() * (NextFrame / (NumKeys-1));
GetSkelMeshComponent()->GlobalAnimRateScale = 1.0f;
GetSkelMeshComponent()->TickAnimation(NewTime - Proxy.GetCurrentTime(), false);
MontagePreview_SetPlaying(false);
}
}
void UAnimPreviewInstance::MontagePreview_StepBackward()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
bool bWasPlaying = IsPlayingMontage() && (Proxy.IsLooping() || Proxy.IsPlaying()); // we need to handle non-looped case separately, even if paused during playthrough
MontagePreview_SetReverse(true);
if (! bWasPlaying)
{
if (! Proxy.IsLooping())
{
float StoppedAt = Proxy.GetCurrentTime();
if (! bWasPlaying)
{
// play montage but at last known location
MontagePreview_Restart();
SetPosition(StoppedAt, false);
}
int32 LastPreviewSectionIdx = MontagePreview_FindLastSection(MontagePreviewStartSectionIdx);
if (FMath::Abs(Proxy.GetCurrentTime() - (Montage->CompositeSections[LastPreviewSectionIdx].GetTime() + Montage->GetSectionLength(LastPreviewSectionIdx))) <= MontagePreview_CalculateStepLength())
{
// special case as we could stop at the end of our last section which is also beginning of following section - we don't want to get stuck there, but be inside of our starting section
Montage_JumpToSection(Montage->GetSectionName(LastPreviewSectionIdx));
}
else if (FMath::Abs(Proxy.GetCurrentTime() - Montage->CompositeSections[MontagePreviewStartSectionIdx].GetTime()) <= MontagePreview_CalculateStepLength())
{
// we're at the end of playing backward, jump right to the end
Montage_JumpToSectionsEnd(Montage->GetSectionName(MontagePreviewStartSectionIdx));
if (! bWasPlaying)
{
MontagePreview_SetPlaying(false);
}
return; // can't go further than beginning of first section
}
}
else
{
MontagePreview_Restart();
}
}
MontagePreview_SetPlaying(true);
// Advance a single frame, leaving it paused afterwards
const int32 NumKeys = Montage->GetNumberOfSampledKeys();
// Add DELTA to prefer next frame when we're close to the boundary
const float CurrentFraction = Proxy.GetCurrentTime() / Montage->GetPlayLength() + DELTA;
const float NextFrame = FMath::Clamp<float>(FMath::FloorToFloat(CurrentFraction * NumKeys) - 1.0f, 0, NumKeys);
const float NewTime = Montage->GetPlayLength() * (NextFrame / (NumKeys-1));
GetSkelMeshComponent()->GlobalAnimRateScale = 1.0f;
GetSkelMeshComponent()->TickAnimation(FMath::Abs(NewTime - Proxy.GetCurrentTime()), false);
MontagePreview_SetPlaying(false);
}
}
float UAnimPreviewInstance::MontagePreview_CalculateStepLength()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
return static_cast<float>(Montage->GetSamplingFrameRate().AsInterval());
}
return static_cast<float>(UAnimationSettings::Get()->GetDefaultFrameRate().AsInterval());
}
void UAnimPreviewInstance::MontagePreview_JumpToStart()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
int32 SectionIdx = 0;
if (MontagePreviewType == EMPT_Normal)
{
SectionIdx = MontagePreviewStartSectionIdx;
}
// TODO hack - Montage_JumpToSection requires montage being played
bool bWasPlaying = IsPlayingMontage();
if (! bWasPlaying)
{
MontagePreview_Restart();
}
if (Proxy.GetPlayRate() < 0.f)
{
Montage_JumpToSectionsEnd(Montage->GetSectionName(SectionIdx));
}
else
{
Montage_JumpToSection(Montage->GetSectionName(SectionIdx));
}
if (! bWasPlaying)
{
MontagePreview_SetPlaying(false);
}
}
}
void UAnimPreviewInstance::MontagePreview_JumpToEnd()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
int32 SectionIdx = 0;
if (MontagePreviewType == EMPT_Normal)
{
SectionIdx = MontagePreviewStartSectionIdx;
}
// TODO hack - Montage_JumpToSectionsEnd requires montage being played
bool bWasPlaying = IsPlayingMontage();
if (! bWasPlaying)
{
MontagePreview_Restart();
}
if (Proxy.GetPlayRate() < 0.f)
{
Montage_JumpToSection(Montage->GetSectionName(MontagePreview_FindLastSection(SectionIdx)));
}
else
{
Montage_JumpToSectionsEnd(Montage->GetSectionName(MontagePreview_FindLastSection(SectionIdx)));
}
if (! bWasPlaying)
{
MontagePreview_SetPlaying(false);
}
}
}
void UAnimPreviewInstance::MontagePreview_JumpToPreviewStart()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
int32 SectionIdx = 0;
if (MontagePreviewType == EMPT_Normal)
{
SectionIdx = MontagePreviewStartSectionIdx;
}
// TODO hack - Montage_JumpToSectionsEnd requires montage being played
bool bWasPlaying = IsPlayingMontage();
if (! bWasPlaying)
{
MontagePreview_Restart();
}
Montage_JumpToSection(Montage->GetSectionName(Proxy.GetPlayRate() > 0.f? SectionIdx : MontagePreview_FindLastSection(SectionIdx)));
if (! bWasPlaying)
{
MontagePreview_SetPlaying(false);
}
}
}
void UAnimPreviewInstance::MontagePreview_JumpToPosition(float NewPosition)
{
SetPosition(NewPosition, false);
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
// this section will be first
int32 NewMontagePreviewStartSectionIdx = MontagePreview_FindFirstSectionAsInMontage(Montage->GetSectionIndexFromPosition(NewPosition));
if (MontagePreviewStartSectionIdx != NewMontagePreviewStartSectionIdx &&
MontagePreviewType == EMPT_Normal)
{
MontagePreviewStartSectionIdx = NewMontagePreviewStartSectionIdx;
}
// setup looping to match normal playback
MontagePreview_SetLooping(Proxy.IsLooping());
}
}
void UAnimPreviewInstance::MontagePreview_RemoveBlendOut()
{
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
CurMontageInstance->DefaultBlendTimeMultiplier = 0.0f;
}
}
void UAnimPreviewInstance::MontagePreview_PreviewNormal(int32 FromSectionIdx, bool bPlay)
{
UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
if (Montage && Montage->GetPlayLength() > 0.0f)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
int32 PreviewFromSection = FromSectionIdx;
if (FromSectionIdx != INDEX_NONE)
{
MontagePreviewStartSectionIdx = MontagePreview_FindFirstSectionAsInMontage(FromSectionIdx);
}
else
{
FromSectionIdx = MontagePreviewStartSectionIdx;
PreviewFromSection = MontagePreviewStartSectionIdx;
}
MontagePreviewType = EMPT_Normal;
// since this is preview, we would like not to blend in
// just hard stop here
Montage_Stop(0.0f, Montage);
Montage_Play(Montage, Proxy.GetPlayRate());
MontagePreview_SetLoopNormal(Proxy.IsLooping(), FromSectionIdx);
Montage_JumpToSection(Montage->GetSectionName(PreviewFromSection));
MontagePreview_RemoveBlendOut();
Proxy.SetPlaying(bPlay);
FAnimMontageInstance* MontageInstance = GetActiveMontageInstance();
if (MontageInstance)
{
MontageInstance->SetWeight(1.0f);
MontageInstance->bPlaying = Proxy.IsPlaying();
}
}
}
void UAnimPreviewInstance::MontagePreview_PreviewAllSections(bool bPlay)
{
UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
if (Montage && Montage->GetPlayLength() > 0.0f)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
MontagePreviewType = EMPT_AllSections;
// since this is preview, we would like not to blend in
// just hard stop here
Montage_Stop(0.0f, Montage);
Montage_Play(Montage, Proxy.GetPlayRate());
MontagePreview_SetLoopAllSections(Proxy.IsLooping());
MontagePreview_JumpToPreviewStart();
MontagePreview_RemoveBlendOut();
Proxy.SetPlaying(bPlay);
FAnimMontageInstance* MontageInstance = GetActiveMontageInstance();
if (MontageInstance)
{
MontageInstance->SetWeight(1.0f);
MontageInstance->bPlaying = Proxy.IsPlaying();
}
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopNormal(bool bIsLooping, int32 PreferSectionIdx)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
MontagePreview_ResetSectionsOrder();
if (PreferSectionIdx == INDEX_NONE)
{
PreferSectionIdx = Montage->GetSectionIndexFromPosition(Proxy.GetCurrentTime());
}
int32 TotalSection = Montage->CompositeSections.Num();
if (TotalSection > 0)
{
int PreferedInChain = TotalSection;
TArray<bool> AlreadyUsed;
AlreadyUsed.AddZeroed(TotalSection);
while (true)
{
// find first not already used section
int32 NotUsedIdx = 0;
while (NotUsedIdx < TotalSection)
{
if (! AlreadyUsed[NotUsedIdx])
{
break;
}
++ NotUsedIdx;
}
if (NotUsedIdx >= TotalSection)
{
break;
}
// find if this is one we're looking for closest to starting one
int32 CurSectionIdx = NotUsedIdx;
int32 InChain = 0;
while (true)
{
// find first that contains this
if (CurSectionIdx == PreferSectionIdx &&
InChain < PreferedInChain)
{
PreferedInChain = InChain;
PreferSectionIdx = NotUsedIdx;
}
AlreadyUsed[CurSectionIdx] = true;
FName NextSection = Montage->CompositeSections[CurSectionIdx].NextSectionName;
CurSectionIdx = Montage->GetSectionIndex(NextSection);
if (CurSectionIdx == INDEX_NONE || AlreadyUsed[CurSectionIdx]) // break loops
{
break;
}
++ InChain;
}
// loop this section
SetMontageLoop(Montage, Proxy.IsLooping(), Montage->CompositeSections[NotUsedIdx].SectionName);
}
if (Montage->CompositeSections.IsValidIndex(PreferSectionIdx))
{
SetMontageLoop(Montage, Proxy.IsLooping(), Montage->CompositeSections[PreferSectionIdx].SectionName);
}
}
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopAllSetupSections(bool bIsLooping)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
MontagePreview_ResetSectionsOrder();
int32 TotalSection = Montage->CompositeSections.Num();
if (TotalSection > 0)
{
FName FirstSection = Montage->CompositeSections[0].SectionName;
FName PreviousSection = FirstSection;
TArray<bool> AlreadyUsed;
AlreadyUsed.AddZeroed(TotalSection);
while (true)
{
// find first not already used section
int32 NotUsedIdx = 0;
while (NotUsedIdx < TotalSection)
{
if (! AlreadyUsed[NotUsedIdx])
{
break;
}
++ NotUsedIdx;
}
if (NotUsedIdx >= TotalSection)
{
break;
}
// go through all connected to join them into one big chain
int CurSectionIdx = NotUsedIdx;
while (true)
{
AlreadyUsed[CurSectionIdx] = true;
FName CurrentSection = Montage->CompositeSections[CurSectionIdx].SectionName;
Montage_SetNextSection(PreviousSection, CurrentSection);
PreviousSection = CurrentSection;
FName NextSection = Montage->CompositeSections[CurSectionIdx].NextSectionName;
CurSectionIdx = Montage->GetSectionIndex(NextSection);
if (CurSectionIdx == INDEX_NONE || AlreadyUsed[CurSectionIdx]) // break loops
{
break;
}
}
}
if (Proxy.IsLooping())
{
// and loop all
Montage_SetNextSection(PreviousSection, FirstSection);
}
}
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopAllSections(bool bIsLooping)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
int32 TotalSection = Montage->CompositeSections.Num();
if (TotalSection > 0)
{
if (Proxy.IsLooping())
{
for (int i = 0; i < TotalSection; ++ i)
{
Montage_SetNextSection(Montage->CompositeSections[i].SectionName, Montage->CompositeSections[(i+1) % TotalSection].SectionName);
}
}
else
{
for (int i = 0; i < TotalSection - 1; ++ i)
{
Montage_SetNextSection(Montage->CompositeSections[i].SectionName, Montage->CompositeSections[i+1].SectionName);
}
Montage_SetNextSection(Montage->CompositeSections[TotalSection - 1].SectionName, NAME_None);
}
}
}
}
void UAnimPreviewInstance::MontagePreview_ResetSectionsOrder()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
int32 TotalSection = Montage->CompositeSections.Num();
// restore to default
for (int i = 0; i < TotalSection; ++ i)
{
Montage_SetNextSection(Montage->CompositeSections[i].SectionName, Montage->CompositeSections[i].NextSectionName);
}
}
}
int32 UAnimPreviewInstance::MontagePreview_FindFirstSectionAsInMontage(int32 ForSectionIdx)
{
int32 ResultIdx = ForSectionIdx;
// Montage does not have looping set up, so it should be valid and it gets
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
TArray<bool> AlreadyVisited;
AlreadyVisited.AddZeroed(Montage->CompositeSections.Num());
bool bFoundResult = false;
while (! bFoundResult)
{
int32 UnusedSectionIdx = INDEX_NONE;
for (int32 Idx = 0; Idx < Montage->CompositeSections.Num(); ++ Idx)
{
if (! AlreadyVisited[Idx])
{
UnusedSectionIdx = Idx;
break;
}
}
if (UnusedSectionIdx == INDEX_NONE)
{
break;
}
// check if this has ForSectionIdx
int32 CurrentSectionIdx = UnusedSectionIdx;
while (CurrentSectionIdx != INDEX_NONE && ! AlreadyVisited[CurrentSectionIdx])
{
if (CurrentSectionIdx == ForSectionIdx)
{
ResultIdx = UnusedSectionIdx;
bFoundResult = true;
break;
}
AlreadyVisited[CurrentSectionIdx] = true;
FName NextSection = Montage->CompositeSections[CurrentSectionIdx].NextSectionName;
CurrentSectionIdx = Montage->GetSectionIndex(NextSection);
}
}
}
return ResultIdx;
}
int32 UAnimPreviewInstance::MontagePreview_FindLastSection(int32 StartSectionIdx)
{
int32 ResultIdx = StartSectionIdx;
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
int32 TotalSection = Montage->CompositeSections.Num();
if (TotalSection > 0)
{
TArray<bool> AlreadyVisited;
AlreadyVisited.AddZeroed(TotalSection);
int32 CurrentSectionIdx = StartSectionIdx;
while (CurrentSectionIdx != INDEX_NONE && ! AlreadyVisited[CurrentSectionIdx])
{
AlreadyVisited[CurrentSectionIdx] = true;
ResultIdx = CurrentSectionIdx;
CurrentSectionIdx = CurMontageInstance->GetNextSectionID(CurrentSectionIdx);
}
}
}
}
return ResultIdx;
}
void UAnimPreviewInstance::EnableControllers(bool bEnable)
{
GetProxyOnGameThread<FAnimPreviewInstanceProxy>().EnableControllers(bEnable);
}
FAnimInstanceProxy* UAnimPreviewInstance::CreateAnimInstanceProxy()
{
return new FAnimPreviewInstanceProxy(this);
}
void UAnimPreviewInstance::AddImpulseAtLocation(FVector Impulse, FVector Location, FName BoneName)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Proxy.AddImpulseAtLocation(Impulse, Location, BoneName);
}
void UAnimPreviewInstance::SetDebugSkeletalMeshComponent(USkeletalMeshComponent* InSkeletalMeshComponent)
{
FAnimPreviewInstanceProxy& Proxy = GetProxyOnGameThread<FAnimPreviewInstanceProxy>();
Proxy.InitializeObjects(this);
Proxy.SetDebugSkeletalMeshComponent(InSkeletalMeshComponent);
Proxy.ClearObjects();
}
USkeletalMeshComponent* UAnimPreviewInstance::GetDebugSkeletalMeshComponent() const
{
return GetProxyOnGameThread<FAnimPreviewInstanceProxy>().GetDebugSkeletalMeshComponent();
}
#undef LOCTEXT_NAMESPACE