Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimPreviewInstance.cpp
Martin Wilson ae71a8cca5 Added FCompactPose, contains only the bone transforms needed by RequiredBones. Allows us to do animation evaluation without having to refer to required bones array all the time
API Breaking Changes

Removed Functions:
FA2CSPose::SafeSetCSBoneTransforms
FA2CSPose::LocalBlendCSBoneTransforms

Member Type Changes:
- int32 -> FCompactPoseBoneIndex
--- FBoneTransform::BoneIndex
--- FABRIKChainLink::BoneIndex
- FA2CSPose -> FCSPose<FCompactPose>
--- FAnimNode_SkeletalControlBase::ForwardedPose
--- FComponentSpacePoseContext::Pose
- FA2Pose -> FCompactPose
--- FSlotEvaluationPose::Pose
--- FAnimNode_TransitionPoseEvaluator::CachedPose
--- FAnimNode_SaveCachedPose::CachedPose
--- FPoseContext::Pose

Paramater Changes:
- FA2Pose& -> FCompactPose&
--- UAnimSingleNodeInstance::InternalBlendSpaceEvaluatePose (FA2Pose& Pose -> FCompactPose&)
--- UAnimInstance::SequenceEvaluatePose
--- UAnimInstance::BlendSequences
--- UAnimInstance::CopyPose
--- UAnimInstance::ApplyAdditiveSequence
--- UAnimInstance::BlendSpaceEvaluatePose
--- UAnimInstance::BlendRotationOffset
--- UAnimInstance::GetSlotWeight
--- UAnimInstance::SlotEvaluatePose
- FA2CSPose& -> FCSPose<FCompactPose>&
--- FAnimNode_SkeletalControlBase::EvaluateBoneTransforms
--- UAnimGraphNode_SkeletalControlBase::ConvertCSVectorToBoneSpace
--- UAnimGraphNode_SkeletalControlBase::ConvertCSRotationToBoneSpace
--- UAnimGraphNode_SkeletalControlBase::ConvertWidgetLocation
--- UAnimPreviewInstance::ApplyBoneControllers
- TArray<FTransform> -> FCompactPose
--- UAnimPreviewInstance::SetKeyImplementation
--- UAnimSequence::GetAnimationPose
--- UAnimSequence::GetBonePose
--- UAnimSequence::GetBonePose_Additive
--- UAnimSequence::GetAdditiveBasePose
--- UAnimSequence::GetBonePose_AdditiveMeshRotationOnly
--- FAnimationRuntime::BlendPosesTogether ( + Removed NumPoses and RequiredBones )
--- FAnimationRuntime::BlendPosesTogetherPerBone  ( + Removed NumPoses and RequiredBones )
--- FAnimationRuntime::BlendPosesTogetherPerBoneInMeshSpace ( + Removed NumPoses and RequiredBones )
--- FAnimationRuntime::BlendPosesPerBoneFilter ( + Removed Skeleton and RequiredBones )
--- FAnimationRuntime::GetPoseFromSequence ( + Removed  RequiredBones )
--- FAnimationRuntime::GetPoseFromAnimTrack ( + Removed  RequiredBones )
--- FAnimationRuntime::FillWithRetargetBaseRefPose ( + Removed  RequiredBones )
--- FAnimationRuntime::ConvertPoseToAdditive ( + Removed  RequiredBones )
--- FAnimationRuntime::ConvertPoseToMeshRotation ( + Removed  RequiredBones )
--- FAnimationRuntime::BlendPosesAccumulate ( + Removed  RequiredBones )
--- FAnimationRuntime::BlendAdditivePose ( + Removed  RequiredBones )
--- FAnimationRuntime::GetPoseFromBlendSpace ( + Removed  RequiredBones )
--- FAnimationRuntime::ConvertCSTransformToBoneSpace
--- FAnimationRuntime::ConvertBoneSpaceTransformToCS
--- FAnimationRuntime::BlendMeshPosesPerBoneWeights ( + Removed Skeleton and RequiredBones )
--- FAnimationRuntime::BlendLocalPosesPerBoneWeights ( + Removed Skeleton and RequiredBones )
- TArray<FTransform> -> FTransform
--- UAnimSequence::ResetRootBoneForRootMotion
- int32 -> FCompactPoseBoneIndex
--- UAnimSequence::RetargetBoneTransform

FAnimationRuntime: Many changes to animation operation functions, removal of NumPoses and RequiredBones, conversion of TArray<FTransform> to FCompactPose

Removed USTRUCT markup from FBoneTransform
Removed UPROPERTY markup from FAnimNode_TransitionPoseEvaluator::CachedPose

[CL 2556671 by Martin Wilson in Main branch]
2015-05-19 06:19:22 -04:00

1001 lines
29 KiB
C++

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "AnimGraphPrivatePCH.h"
#include "AnimPreviewInstance.h"
#if WITH_EDITOR
#include "ScopedTransaction.h"
#endif
#define LOCTEXT_NAMESPACE "AnimPreviewInstance"
UAnimPreviewInstance::UAnimPreviewInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, SkeletalControlAlpha(1.0f)
#if WITH_EDITORONLY_DATA
, bForceRetargetBasePose(false)
#endif
, bSetKey(false)
{
RootMotionMode = ERootMotionMode::RootMotionFromEverything;
bEnableControllers = true;
}
void UAnimPreviewInstance::NativeInitializeAnimation()
{
// Cache our play state from the previous animation otherwise set to play
bool bCachedIsPlaying = (CurrentAsset != NULL) ? bPlaying : true;
bSetKey = false;
Super::NativeInitializeAnimation();
SetPlaying(bCachedIsPlaying);
RefreshCurveBoneControllers();
}
void UAnimPreviewInstance::ResetModifiedBone(bool bCurveController/*=false*/)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
Controllers.Empty();
}
FAnimNode_ModifyBone* UAnimPreviewInstance::FindModifiedBone(const FName& InBoneName, bool bCurveController/*=false*/)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
return Controllers.FindByPredicate(
[InBoneName](const FAnimNode_ModifyBone& InController) -> bool
{
return InController.BoneToModify.BoneName == InBoneName;
}
);
}
FAnimNode_ModifyBone& UAnimPreviewInstance::ModifyBone(const FName& InBoneName, bool bCurveController/*=false*/)
{
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 UAnimPreviewInstance::RemoveBoneModification(const FName& InBoneName, bool bCurveController/*=false*/)
{
TArray<FAnimNode_ModifyBone>& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers;
Controllers.RemoveAll(
[InBoneName](const FAnimNode_ModifyBone& InController)
{
return InController.BoneToModify.BoneName == InBoneName;
}
);
}
void UAnimPreviewInstance::NativeUpdateAnimation(float DeltaTimeX)
{
#if WITH_EDITORONLY_DATA
if(bForceRetargetBasePose)
{
// nothing to be done here
return;
}
#endif // #if WITH_EDITORONLY_DATA
Super::NativeUpdateAnimation(DeltaTimeX);
}
bool UAnimPreviewInstance::NativeEvaluateAnimation(FPoseContext& Output)
{
#if WITH_EDITORONLY_DATA
if(bForceRetargetBasePose)
{
USkeletalMeshComponent* MeshComponent = GetSkelMeshComponent();
if(MeshComponent && MeshComponent->SkeletalMesh)
{
FAnimationRuntime::FillWithRetargetBaseRefPose(Output.Pose, GetSkelMeshComponent()->SkeletalMesh);
}
else
{
// ideally we'll return just ref pose, but not sure if this will work with LODs
Output.Pose.ResetToRefPose();
}
}
else
#endif // #if WITH_EDITORONLY_DATA
{
Super::NativeEvaluateAnimation(Output);
}
if (bEnableControllers)
{
UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent>(GetSkelMeshComponent());
if(Component && CurrentSkeleton)
{
// update curve controllers
UpdateCurveController();
// create bone controllers from
if(BoneControllers.Num() > 0 || CurveBoneControllers.Num() > 0)
{
FCompactPose PreController, PostController;
// 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.Pose;
}
FCSPose<FCompactPose> OutMeshPose;
OutMeshPose.InitPose(&RequiredBones);
// apply curve data first
ApplyBoneControllers(Component, CurveBoneControllers, OutMeshPose);
// and now apply bone controllers data
// it is possible they can be overlapping, but then bone controllers will overwrite
ApplyBoneControllers(Component, BoneControllers, OutMeshPose);
// convert back to local @todo check this
OutMeshPose.ConvertToLocalPoses(Output.Pose);
if(bSetKey)
{
// now we have post controller, and calculate delta now
PostController = Output.Pose;
SetKeyImplementation(PreController, PostController);
}
}
// 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 UAnimPreviewInstance::ApplyBoneControllers(USkeletalMeshComponent* Component, TArray<FAnimNode_ModifyBone> &InBoneControllers, FCSPose<FCompactPose>& OutMeshPose)
{
for(auto& SingleBoneController : InBoneControllers)
{
SingleBoneController.BoneToModify.BoneIndex = RequiredBones.GetPoseBoneIndexForBoneName(SingleBoneController.BoneToModify.BoneName);
if(SingleBoneController.BoneToModify.BoneIndex != INDEX_NONE)
{
TArray<FBoneTransform> BoneTransforms;
SingleBoneController.EvaluateBoneTransforms(Component, OutMeshPose, BoneTransforms);
if(BoneTransforms.Num() > 0)
{
OutMeshPose.LocalBlendCSBoneTransforms(BoneTransforms, 1.0f);
}
}
}
}
void UAnimPreviewInstance::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());
if(CurrentSequence && CurrentSkeleton && Component && Component->SkeletalMesh)
{
FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key"));
CurrentSequence->Modify(true);
Modify();
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 = RequiredBones.MakeCompactPoseIndex(MeshBoneIndex);
FTransform LocalTransform = PostControllerInLocalSpace[BoneIndex];
// now we have LocalTransform and get additive data
FTransform AdditiveTransform = LocalTransform.GetRelativeTransform(PreControllerInLocalSpace[BoneIndex]);
AddKeyToSequence(CurrentSequence, CurrentTime, 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, CurrentTime, BoneName, FTransform::Identity);
}
}
}
ResetModifiedBone(false);
OnSetKeyCompleteDelegate.ExecuteIfBound();
}
#endif
}
void UAnimPreviewInstance::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);
RequiredBones.SetUseSourceData(true);
}
void UAnimPreviewInstance::SetKey(FSimpleDelegate InOnSetKeyCompleteDelegate)
{
#if WITH_EDITOR
bSetKey = true;
OnSetKeyCompleteDelegate = InOnSetKeyCompleteDelegate;
#endif
}
void UAnimPreviewInstance::RefreshCurveBoneControllers()
{
// go through all curves and see if it has Transform Curve
// if so, find what bone that belong to and create BoneMOdifier for them
UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset);
CurveBoneControllers.Empty();
// do not apply if BakedAnimation is on
if(CurrentSequence)
{
// make sure if this needs source update
if ( !CurrentSequence->DoesContainTransformCurves() )
{
return;
}
RequiredBones.SetUseSourceData(true);
TArray<FTransformCurve>& Curves = CurrentSequence->RawCurveData.TransformCurves;
FSmartNameMapping* NameMapping = CurrentSkeleton->SmartNames.GetContainer(USkeleton::AnimTrackCurveMappingName);
for (auto& Curve : Curves)
{
// skip if disabled
if (Curve.GetCurveTypeFlag(ACF_Disabled))
{
continue;
}
// add bone modifier
FName CurveName;
NameMapping->GetName(Curve.CurveUid, CurveName);
// @TODO: this is going to be issue. If they don't save skeleton with it, we don't have name at all?
if (CurveName == NAME_None)
{
FSmartNameMapping::UID NewUID;
NameMapping->AddOrFindName(Curve.LastObservedName, NewUID);
Curve.CurveUid = NewUID;
CurveName = Curve.LastObservedName;
}
FName BoneName = CurveName;
if (BoneName != NAME_None && CurrentSkeleton->GetReferenceSkeleton().FindBoneIndex(BoneName) != INDEX_NONE)
{
ModifyBone(BoneName, true);
}
}
}
}
void UAnimPreviewInstance::UpdateCurveController()
{
// evaluate the curve data first
UAnimSequenceBase* CurrentSequence = Cast<UAnimSequenceBase>(CurrentAsset);
if (CurrentSequence && CurrentSkeleton)
{
TMap<FName, FTransform> ActiveCurves;
CurrentSequence->RawCurveData.EvaluateTransformCurveData(CurrentSkeleton, ActiveCurves, CurrentTime, 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();
}
}
}
/** Set SkeletalControl Alpha**/
void UAnimPreviewInstance::SetSkeletalControlAlpha(float InSkeletalControlAlpha)
{
SkeletalControlAlpha = FMath::Clamp<float>(InSkeletalControlAlpha, 0.f, 1.f);
}
UAnimSequence* UAnimPreviewInstance::GetAnimSequence()
{
return Cast<UAnimSequence>(CurrentAsset);
}
void UAnimPreviewInstance::RestartMontage(UAnimMontage* Montage, FName FromSection)
{
if (Montage == CurrentAsset)
{
MontagePreviewType = EMPT_Normal;
Montage_Play(Montage, PlayRate);
if (FromSection != NAME_None)
{
Montage_JumpToSection(FromSection);
}
MontagePreview_SetLoopNormal(bLooping, Montage->GetSectionIndex(FromSection));
}
}
void UAnimPreviewInstance::SetAnimationAsset(UAnimationAsset* NewAsset, bool bIsLooping, float InPlayRate)
{
// make sure to turn that off before setting new asset
RequiredBones.SetUseSourceData(false);
Super::SetAnimationAsset(NewAsset, bIsLooping, InPlayRate);
RootMotionMode = Cast<UAnimMontage>(CurrentAsset) != NULL ? ERootMotionMode::RootMotionFromMontagesOnly : ERootMotionMode::RootMotionFromEverything;
// should re sync up curve bone controllers from new asset
RefreshCurveBoneControllers();
}
void UAnimPreviewInstance::MontagePreview_SetLooping(bool bIsLooping)
{
bLooping = bIsLooping;
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
switch (MontagePreviewType)
{
case EMPT_AllSections:
MontagePreview_SetLoopAllSections(bLooping);
break;
case EMPT_Normal:
default:
MontagePreview_SetLoopNormal(bLooping);
break;
}
}
}
void UAnimPreviewInstance::MontagePreview_SetPlaying(bool bIsPlaying)
{
bPlaying = bIsPlaying;
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
CurMontageInstance->bPlaying = bIsPlaying;
}
else if (bPlaying)
{
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)
{
Super::SetReverse(bInReverse);
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
// copy the current playrate
CurMontageInstance->SetPlayRate(PlayRate);
}
}
void UAnimPreviewInstance::MontagePreview_Restart()
{
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()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
bool bWasPlaying = IsPlayingMontage() && (bLooping || bPlaying); // we need to handle non-looped case separately, even if paused during playthrough
MontagePreview_SetReverse(false);
if (! bWasPlaying)
{
if (! bLooping)
{
float StoppedAt = CurrentTime;
if (! bWasPlaying)
{
// play montage but at last known location
MontagePreview_Restart();
SetPosition(StoppedAt, false);
}
int32 LastPreviewSectionIdx = MontagePreview_FindLastSection(MontagePreviewStartSectionIdx);
if (FMath::Abs(CurrentTime - (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
int32 NumFrames = Montage->GetNumberOfFrames();
// Add DELTA to prefer next frame when we're close to the boundary
float CurrentFraction = CurrentTime / Montage->SequenceLength + DELTA;
float NextFrame = FMath::Clamp<float>(FMath::FloorToFloat(CurrentFraction * NumFrames) + 1.0f, 0, NumFrames);
float NewTime = Montage->SequenceLength * (NextFrame / NumFrames);
GetSkelMeshComponent()->GlobalAnimRateScale = 1.0f;
GetSkelMeshComponent()->TickAnimation(NewTime - CurrentTime);
MontagePreview_SetPlaying(false);
}
}
void UAnimPreviewInstance::MontagePreview_StepBackward()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
bool bWasPlaying = IsPlayingMontage() && (bLooping || bPlaying); // we need to handle non-looped case separately, even if paused during playthrough
MontagePreview_SetReverse(true);
if (! bWasPlaying)
{
if (! bLooping)
{
float StoppedAt = CurrentTime;
if (! bWasPlaying)
{
// play montage but at last known location
MontagePreview_Restart();
SetPosition(StoppedAt, false);
}
int32 LastPreviewSectionIdx = MontagePreview_FindLastSection(MontagePreviewStartSectionIdx);
if (FMath::Abs(CurrentTime - (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(CurrentTime - 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
int32 NumFrames = Montage->GetNumberOfFrames();
// Add DELTA to prefer next frame when we're close to the boundary
float CurrentFraction = CurrentTime / Montage->SequenceLength + DELTA;
float NextFrame = FMath::Clamp<float>(FMath::FloorToFloat(CurrentFraction * NumFrames) - 1.0f, 0, NumFrames);
float NewTime = Montage->SequenceLength * (NextFrame / NumFrames);
GetSkelMeshComponent()->GlobalAnimRateScale = 1.0f;
GetSkelMeshComponent()->TickAnimation(FMath::Abs(NewTime - CurrentTime));
MontagePreview_SetPlaying(false);
}
}
float UAnimPreviewInstance::MontagePreview_CalculateStepLength()
{
return 1.0f / 30.0f;
}
void UAnimPreviewInstance::MontagePreview_JumpToStart()
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
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 (PlayRate < 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))
{
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 (PlayRate < 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))
{
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(PlayRate > 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))
{
// 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(bLooping);
}
}
void UAnimPreviewInstance::MontagePreview_RemoveBlendOut()
{
if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance())
{
CurMontageInstance->DefaultBlendTimeMultiplier = 0.0f;
}
}
void UAnimPreviewInstance::MontagePreview_PreviewNormal(int32 FromSectionIdx)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
int32 PreviewFromSection = FromSectionIdx;
if (FromSectionIdx != INDEX_NONE)
{
MontagePreviewStartSectionIdx = MontagePreview_FindFirstSectionAsInMontage(FromSectionIdx);
}
else
{
FromSectionIdx = MontagePreviewStartSectionIdx;
PreviewFromSection = MontagePreviewStartSectionIdx;
}
MontagePreviewType = EMPT_Normal;
Montage_Play(Montage, PlayRate);
MontagePreview_SetLoopNormal(bLooping, FromSectionIdx);
Montage_JumpToSection(Montage->GetSectionName(PreviewFromSection));
MontagePreview_RemoveBlendOut();
bPlaying = true;
}
}
void UAnimPreviewInstance::MontagePreview_PreviewAllSections()
{
UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
if (Montage && Montage->SequenceLength > 0.f)
{
MontagePreviewType = EMPT_AllSections;
Montage_Play(Montage, PlayRate);
MontagePreview_SetLoopAllSections(bLooping);
MontagePreview_JumpToPreviewStart();
MontagePreview_RemoveBlendOut();
bPlaying = true;
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopNormal(bool bIsLooping, int32 PreferSectionIdx)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
MontagePreview_ResetSectionsOrder();
if (PreferSectionIdx == INDEX_NONE)
{
PreferSectionIdx = Montage->GetSectionIndexFromPosition(CurrentTime);
}
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, bIsLooping, Montage->CompositeSections[NotUsedIdx].SectionName);
}
if (Montage->CompositeSections.IsValidIndex(PreferSectionIdx))
{
SetMontageLoop(Montage, bIsLooping, Montage->CompositeSections[PreferSectionIdx].SectionName);
}
}
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopAllSetupSections(bool bIsLooping)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
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 (bIsLooping)
{
// and loop all
Montage_SetNextSection(PreviousSection, FirstSection);
}
}
}
}
void UAnimPreviewInstance::MontagePreview_SetLoopAllSections(bool bIsLooping)
{
if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
{
int32 TotalSection = Montage->CompositeSections.Num();
if (TotalSection > 0)
{
if (bIsLooping)
{
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;
if (CurMontageInstance->NextSections.IsValidIndex(CurrentSectionIdx))
{
CurrentSectionIdx = CurMontageInstance->NextSections[CurrentSectionIdx];
}
}
}
}
}
return ResultIdx;
}
void UAnimPreviewInstance::BakeAnimation()
{
if (UAnimSequence* Sequence = Cast<UAnimSequence>(CurrentAsset))
{
FScopedTransaction ScopedTransaction(LOCTEXT("BakeAnimation", "Bake Animation"));
Sequence->Modify(true);
Sequence->BakeTrackCurvesToRawAnimation();
}
}
void UAnimPreviewInstance::EnableControllers(bool bEnable)
{
bEnableControllers = bEnable;
}
#undef LOCTEXT_NAMESPACE