// 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& Controllers = (bCurveController)?CurveBoneControllers : BoneControllers; Controllers.Empty(); } FAnimNode_ModifyBone* UAnimPreviewInstance::FindModifiedBone(const FName& InBoneName, bool bCurveController/*=false*/) { TArray& 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& 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& 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.Bones, GetSkelMeshComponent()->SkeletalMesh, RequiredBones); } else { // ideally we'll return just ref pose, but not sure if this will work with LODs FAnimationRuntime::FillWithRefPose(Output.Pose.Bones, RequiredBones); } } else #endif // #if WITH_EDITORONLY_DATA { Super::NativeEvaluateAnimation(Output); } if (bEnableControllers) { UDebugSkelMeshComponent* Component = Cast(GetSkelMeshComponent()); if(Component && CurrentSkeleton) { // update curve controllers UpdateCurveController(); // create bone controllers from if(BoneControllers.Num() > 0 || CurveBoneControllers.Num() > 0) { TArray 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.Bones; } FA2CSPose OutMeshPose; OutMeshPose.AllocateLocalPoses(RequiredBones, Output.Pose); // 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.Bones; 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.Bones, Output.Pose.Bones); } } } // 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 &InBoneControllers, FA2CSPose& OutMeshPose) { for(auto& SingleBoneController : InBoneControllers) { SingleBoneController.BoneToModify.BoneIndex = RequiredBones.GetPoseBoneIndexForBoneName(SingleBoneController.BoneToModify.BoneName); if(SingleBoneController.BoneToModify.BoneIndex != INDEX_NONE) { TArray BoneTransforms; SingleBoneController.EvaluateBoneTransforms(Component, RequiredBones, OutMeshPose, BoneTransforms); if(BoneTransforms.Num() > 0) { OutMeshPose.LocalBlendCSBoneTransforms(BoneTransforms, 1.0f); } } } } void UAnimPreviewInstance::SetKeyImplementation(const TArray& PreControllerInLocalSpace, const TArray& PostControllerInLocalSpace) { #if WITH_EDITOR // evaluate the curve data first UAnimSequence* CurrentSequence = Cast(CurrentAsset); UDebugSkelMeshComponent* Component = Cast (GetSkelMeshComponent()); if(CurrentSequence && CurrentSkeleton && Component && Component->SkeletalMesh) { FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key")); CurrentSequence->Modify(true); Modify(); TArray 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 int32 BoneIndex = Component->GetBoneIndex(BoneName); 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(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& 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(CurrentAsset); if (CurrentSequence && CurrentSkeleton) { TMap 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(InSkeletalControlAlpha, 0.f, 1.f); } UAnimSequence* UAnimPreviewInstance::GetAnimSequence() { return Cast(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(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(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(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(CurrentAsset)) { switch (MontagePreviewType) { case EMPT_AllSections: MontagePreview_PreviewAllSections(); break; case EMPT_Normal: default: MontagePreview_PreviewNormal(); break; } } } void UAnimPreviewInstance::MontagePreview_StepForward() { if (UAnimMontage* Montage = Cast(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(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(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(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(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(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(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(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(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(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(CurrentAsset)) { MontagePreview_ResetSectionsOrder(); if (PreferSectionIdx == INDEX_NONE) { PreferSectionIdx = Montage->GetSectionIndexFromPosition(CurrentTime); } int32 TotalSection = Montage->CompositeSections.Num(); if (TotalSection > 0) { int PreferedInChain = TotalSection; TArray 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(CurrentAsset)) { MontagePreview_ResetSectionsOrder(); int32 TotalSection = Montage->CompositeSections.Num(); if (TotalSection > 0) { FName FirstSection = Montage->CompositeSections[0].SectionName; FName PreviousSection = FirstSection; TArray 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(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(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(CurrentAsset)) { TArray 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(CurrentAsset)) { if (FAnimMontageInstance* CurMontageInstance = GetActiveMontageInstance()) { int32 TotalSection = Montage->CompositeSections.Num(); if (TotalSection > 0) { TArray 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(CurrentAsset)) { FScopedTransaction ScopedTransaction(LOCTEXT("BakeAnimation", "Bake Animation")); Sequence->Modify(true); Sequence->BakeTrackCurvesToRawAnimation(); } } void UAnimPreviewInstance::EnableControllers(bool bEnable) { bEnableControllers = bEnable; } #undef LOCTEXT_NAMESPACE