// Copyright Epic Games, Inc. All Rights Reserved. #include "SAnimationScrubPanel.h" #include "Widgets/SBoxPanel.h" #include "Animation/AnimSequenceBase.h" #include "Animation/AnimSequence.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/AnimBlueprint.h" #include "AnimPreviewInstance.h" #include "SScrubControlPanel.h" #include "ScopedTransaction.h" #include "Animation/BlendSpace.h" #include "AnimationEditorPreviewScene.h" #include "Animation/AnimData/IAnimationDataModel.h" #include "Animation/AnimSequenceHelpers.h" #include "ViewportToolbar/AnimationEditorMenus.h" #include "ViewportToolbar/AnimViewportContext.h" #include "ToolMenus.h" #define LOCTEXT_NAMESPACE "AnimationScrubPanel" void SAnimationScrubPanel::Construct( const SAnimationScrubPanel::FArguments& InArgs, const TSharedRef& InPreviewScene) { bSliderBeingDragged = false; LockedSequence = InArgs._LockedSequence; OnSetInputViewRange = InArgs._OnSetInputViewRange; TimelineDelegates = InArgs._TimelineDelegates; ViewInputMinAttribute = InArgs._ViewInputMin; ViewInputMaxAttribute = InArgs._ViewInputMax; PreviewScenePtr = InPreviewScene; TSharedPtr HorizontalBox; this->ChildSlot [ SAssignNew(HorizontalBox, SHorizontalBox) .AddMetaData(TEXT("AnimScrub.Scrub")) +SHorizontalBox::Slot() .HAlign(HAlign_Fill) .VAlign(VAlign_Center) .FillWidth(1) .Padding(0.0f) [ SAssignNew(ScrubControlPanel, SScrubControlPanel) .IsEnabled(true)//this, &SAnimationScrubPanel::DoesSyncViewport) .Value(this, &SAnimationScrubPanel::GetScrubValue) .NumOfKeys(this, &SAnimationScrubPanel::GetNumberOfKeys) .SequenceLength(this, &SAnimationScrubPanel::GetSequenceLength) .DisplayDrag(this, &SAnimationScrubPanel::GetDisplayDrag) .OnValueChanged(this, &SAnimationScrubPanel::OnValueChanged, true) .OnBeginSliderMovement(this, &SAnimationScrubPanel::OnBeginSliderMovement) .OnEndSliderMovement(this, &SAnimationScrubPanel::OnEndSliderMovement) .OnClickedForwardPlay(this, &SAnimationScrubPanel::OnClick_Forward) .OnClickedForwardStep(this, &SAnimationScrubPanel::OnClick_Forward_Step) .OnClickedForwardEnd(this, &SAnimationScrubPanel::OnClick_Forward_End) .OnClickedBackwardPlay(this, &SAnimationScrubPanel::OnClick_Backward) .OnClickedBackwardStep(this, &SAnimationScrubPanel::OnClick_Backward_Step) .OnClickedBackwardEnd(this, &SAnimationScrubPanel::OnClick_Backward_End) .OnClickedToggleLoop(this, &SAnimationScrubPanel::OnClick_ToggleLoop) .OnClickedRecord(this, &SAnimationScrubPanel::OnClick_Record) .OnGetRecordVisibility(this, &SAnimationScrubPanel::OnGetRecordVisibility) .OnGetLooping(this, &SAnimationScrubPanel::IsLoopStatusOn) .OnGetPlaybackMode(this, &SAnimationScrubPanel::GetPlaybackMode) .OnGetRecording(this, &SAnimationScrubPanel::IsRecording) .ViewInputMin(this, &SAnimationScrubPanel::GetViewInputMin) .ViewInputMax(this, &SAnimationScrubPanel::GetViewInputMax) .bDisplayAnimScrubBarEditing(InArgs._bDisplayAnimScrubBarEditing) .OnSetInputViewRange(InArgs._OnSetInputViewRange) .OnCropAnimSequence( this, &SAnimationScrubPanel::OnCropAnimSequence ) .OnAddAnimSequence( this, &SAnimationScrubPanel::OnInsertAnimSequence ) .OnAppendAnimSequence(this, &SAnimationScrubPanel::OnAppendAnimSequence ) .OnReZeroAnimSequence( this, &SAnimationScrubPanel::OnReZeroAnimSequence ) .bAllowZoom(InArgs._bAllowZoom) .IsRealtimeStreamingMode(this, &SAnimationScrubPanel::IsRealtimeStreamingMode) ] ]; { const FName MenuName = "AnimationEditor.Scrub.PlaybackMenu"; if (!UToolMenus::Get()->IsMenuRegistered(MenuName)) { UToolMenu* Menu = UToolMenus::Get()->RegisterMenu(MenuName); Menu->MenuType = EMultiBoxType::SlimHorizontalToolBar; FToolMenuSection& UnnamedSection = Menu->AddSection(NAME_None); UnnamedSection.AddEntry(UE::AnimationEditor::CreatePlaybackSubmenu()); } FToolMenuContext MenuContext; { MenuContext.AppendCommandList(InPreviewScene->GetCommandList()); UAnimViewportContext* const ContextObject = NewObject(); ContextObject->PersonaPreviewScene = InPreviewScene; MenuContext.AddObject(ContextObject); } // clang-format off HorizontalBox->AddSlot() .HAlign(HAlign_Right) .AutoWidth() [ UToolMenus::Get()->GenerateWidget(MenuName, MenuContext) ]; // clang-format on } } FReply SAnimationScrubPanel::OnClick_Forward_Step() { SetPlaybackMode(EPlaybackMode::Stopped); if(TimelineDelegates.StepForwardDelegate.IsBound()) { if(TimelineDelegates.StepForwardDelegate.Execute()) { return FReply::Handled(); } } UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent(); if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance()) { bool bShouldStepCloth = FMath::Abs(PreviewInstance->GetLength() - PreviewInstance->GetCurrentTime()) > SMALL_NUMBER; PreviewInstance->StepForward(); if(SMC && bShouldStepCloth) { SMC->bPerformSingleClothingTick = true; } } else if (SMC) { // BlendSpaces and Animation Blueprints combine animations so there's no such thing as a frame. However, 1/30 is a sensible/common rate. const float FixedFrameRate = 30.0f; // Advance a single frame, leaving it paused afterwards SMC->GlobalAnimRateScale = 1.0f; GetPreviewScene()->Tick(1.0f / FixedFrameRate); SMC->GlobalAnimRateScale = 0.0f; } return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Forward_End() { SetPlaybackMode(EPlaybackMode::Stopped); OnValueChanged(GetViewInputMax(), false); return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Backward_Step() { SetPlaybackMode(EPlaybackMode::Stopped); if(TimelineDelegates.StepBackwardDelegate.IsBound()) { if(TimelineDelegates.StepBackwardDelegate.Execute()) { return FReply::Handled(); } } UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent(); if (PreviewInstance) { bool bShouldStepCloth = PreviewInstance->GetCurrentTime() > SMALL_NUMBER; PreviewInstance->StepBackward(); if(SMC && bShouldStepCloth) { SMC->bPerformSingleClothingTick = true; } } return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Backward_End() { SetPlaybackMode(EPlaybackMode::Stopped); OnValueChanged(GetViewInputMin(), false); return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Forward() { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent(); const EPlaybackMode::Type PlaybackMode = GetPlaybackMode(); if(PlaybackMode == EPlaybackMode::PlayingForward) { SetPlaybackMode(EPlaybackMode::Stopped); if(SMC && SMC->bPauseClothingSimulationWithAnim) { SMC->SuspendClothingSimulation(); } } else { if ( GetScrubValue() >= GetSequenceLength() ) { OnValueChanged(0.0f, false); } SetPlaybackMode(EPlaybackMode::PlayingForward); if(SMC && SMC->bPauseClothingSimulationWithAnim) { SMC->ResumeClothingSimulation(); } } if(SMC && !PreviewInstance) { SMC->GlobalAnimRateScale = (SMC->GlobalAnimRateScale > 0.0f) ? 0.0f : 1.0f; } return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Backward() { const EPlaybackMode::Type PlaybackMode = GetPlaybackMode(); if(PlaybackMode == EPlaybackMode::PlayingForward) { SetPlaybackMode(EPlaybackMode::Stopped); } else { if ( GetScrubValue() >= GetSequenceLength() ) { OnValueChanged(0.0f, false); } SetPlaybackMode(EPlaybackMode::PlayingReverse); } return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_ToggleLoop() { if(TimelineDelegates.GetIsLoopingDelegate.IsBound() && TimelineDelegates.SetIsLoopingDelegate.IsBound()) { const TOptional PreviousState = TimelineDelegates.GetIsLoopingDelegate.Execute(); if(PreviousState.IsSet()) { if(TimelineDelegates.SetIsLoopingDelegate.Execute(!PreviousState.GetValue())) { return FReply::Handled(); } } } UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if (PreviewInstance) { bool bIsLooping = PreviewInstance->IsLooping(); PreviewInstance->SetLooping(!bIsLooping); } return FReply::Handled(); } FReply SAnimationScrubPanel::OnClick_Record() { StaticCastSharedRef(GetPreviewScene())->RecordAnimation(); return FReply::Handled(); } EVisibility SAnimationScrubPanel::OnGetRecordVisibility() const { if(TimelineDelegates.GetRecordingVisibilityDelegate.IsBound()) { const TOptional Visibility = TimelineDelegates.GetRecordingVisibilityDelegate.Execute(); if(Visibility.IsSet()) { return Visibility.GetValue(); } } return EVisibility::Visible; } bool SAnimationScrubPanel::IsLoopStatusOn() const { if(TimelineDelegates.GetIsLoopingDelegate.IsBound()) { const TOptional PreviousState = TimelineDelegates.GetIsLoopingDelegate.Execute(); if(PreviousState.IsSet()) { return PreviousState.GetValue(); } } UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); return (PreviewInstance && PreviewInstance->IsLooping()); } EPlaybackMode::Type SAnimationScrubPanel::GetPlaybackMode() const { if(TimelineDelegates.GetPlaybackModeDelegate.IsBound()) { const TOptional Mode = TimelineDelegates.GetPlaybackModeDelegate.Execute(); if(Mode.IsSet()) { return static_cast(Mode.GetValue()); } } if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance()) { if (PreviewInstance->IsPlaying()) { return PreviewInstance->IsReverse() ? EPlaybackMode::PlayingReverse : EPlaybackMode::PlayingForward; } return EPlaybackMode::Stopped; } else if (UDebugSkelMeshComponent* SMC = GetPreviewScene()->GetPreviewMeshComponent()) { return (SMC->GlobalAnimRateScale > 0.0f) ? EPlaybackMode::PlayingForward : EPlaybackMode::Stopped; } return EPlaybackMode::Stopped; } bool SAnimationScrubPanel::IsRecording() const { if(TimelineDelegates.IsRecordingActiveDelegate.IsBound()) { const TOptional State = TimelineDelegates.IsRecordingActiveDelegate.Execute(); if(State.IsSet()) { return State.GetValue(); } } return StaticCastSharedRef(GetPreviewScene())->IsRecording(); } bool SAnimationScrubPanel::IsRealtimeStreamingMode() const { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); return ( ! (PreviewInstance && PreviewInstance->GetCurrentAsset()) ); } float SAnimationScrubPanel::GetViewInputMin() const { if(TimelineDelegates.GetPlaybackTimeRangeDelegate.IsBound()) { const TOptional TimeRange = TimelineDelegates.GetPlaybackTimeRangeDelegate.Execute(); if(TimeRange.IsSet()) { return TimeRange.GetValue().X; } } return ViewInputMinAttribute.Get(); } float SAnimationScrubPanel::GetViewInputMax() const { if(TimelineDelegates.GetPlaybackTimeRangeDelegate.IsBound()) { const TOptional TimeRange = TimelineDelegates.GetPlaybackTimeRangeDelegate.Execute(); if(TimeRange.IsSet()) { return TimeRange.GetValue().Y; } } return ViewInputMaxAttribute.Get(); } void SAnimationScrubPanel::OnValueChanged(float NewValue, bool bFireNotifies) { if(TimelineDelegates.SetPlaybackTimeDelegate.IsBound()) { if(TimelineDelegates.SetPlaybackTimeDelegate.Execute(NewValue, true)) { return; } } if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance()) { PreviewInstance->SetPosition(NewValue, bFireNotifies); } else { UAnimInstance* Instance; FAnimBlueprintDebugData* DebugData; if (GetAnimBlueprintDebugData(/*out*/ Instance, /*out*/ DebugData)) { DebugData->SetSnapshotIndexByTime(Instance, NewValue); } } } // make sure viewport is freshes void SAnimationScrubPanel::OnBeginSliderMovement() { bSliderBeingDragged = true; if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance()) { SetPlaybackMode(EPlaybackMode::Stopped); } } void SAnimationScrubPanel::OnEndSliderMovement(float NewValue) { bSliderBeingDragged = false; } uint32 SAnimationScrubPanel::GetNumberOfKeys() const { if(TimelineDelegates.GetNumberOfKeysDelegate.IsBound()) { const TOptional NumberOfKeys = TimelineDelegates.GetNumberOfKeysDelegate.Execute(); if(NumberOfKeys.IsSet()) { return NumberOfKeys.GetValue(); } } if (DoesSyncViewport()) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); float Length = PreviewInstance->GetLength(); // if anim sequence, use correct num frames int32 NumKeys = (int32) (Length/0.0333f); if (PreviewInstance->GetCurrentAsset()) { if (PreviewInstance->GetCurrentAsset()->IsA(UAnimSequenceBase::StaticClass())) { NumKeys = CastChecked(PreviewInstance->GetCurrentAsset())->GetNumberOfSampledKeys(); } else if(PreviewInstance->GetCurrentAsset()->IsA(UBlendSpace::StaticClass())) { // Blendspaces dont display frame notches, so just return 0 here NumKeys = 0; } } return NumKeys; } else if (LockedSequence) { return LockedSequence->GetNumberOfSampledKeys(); } else { UAnimInstance* Instance; FAnimBlueprintDebugData* DebugData; if (GetAnimBlueprintDebugData(/*out*/ Instance, /*out*/ DebugData)) { return DebugData->GetSnapshotLengthInFrames(); } } return 1; } float SAnimationScrubPanel::GetSequenceLength() const { if(TimelineDelegates.GetPlaybackTimeRangeDelegate.IsBound()) { const TOptional TimeRange = TimelineDelegates.GetPlaybackTimeRangeDelegate.Execute(); if(TimeRange.IsSet()) { return TimeRange.GetValue().Y - TimeRange.GetValue().X; } } if (DoesSyncViewport()) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); return PreviewInstance->GetLength(); } else if (LockedSequence) { return LockedSequence->GetPlayLength(); } else { UAnimInstance* Instance; FAnimBlueprintDebugData* DebugData; if (GetAnimBlueprintDebugData(/*out*/ Instance, /*out*/ DebugData)) { return static_cast(Instance->LifeTimer); } } return 0.f; } bool SAnimationScrubPanel::DoesSyncViewport() const { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); return (( LockedSequence==nullptr && PreviewInstance ) || ( LockedSequence && PreviewInstance && PreviewInstance->GetCurrentAsset() == LockedSequence )); } void SAnimationScrubPanel::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) { if (bSliderBeingDragged) { GetPreviewScene()->InvalidateViews(); } } class UAnimSingleNodeInstance* SAnimationScrubPanel::GetPreviewInstance() const { UDebugSkelMeshComponent* PreviewMeshComponent = GetPreviewScene()->GetPreviewMeshComponent(); return PreviewMeshComponent && PreviewMeshComponent->IsPreviewOn()? PreviewMeshComponent->PreviewInstance : nullptr; } float SAnimationScrubPanel::GetScrubValue() const { if(TimelineDelegates.GetPlaybackTimeDelegate.IsBound()) { const TOptional Time = TimelineDelegates.GetPlaybackTimeDelegate.Execute(); if(Time.IsSet()) { return Time.GetValue(); } } if (DoesSyncViewport()) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if (PreviewInstance) { return PreviewInstance->GetCurrentTime(); } } else { UAnimInstance* Instance; FAnimBlueprintDebugData* DebugData; if (GetAnimBlueprintDebugData(/*out*/ Instance, /*out*/ DebugData)) { return static_cast(Instance->CurrentLifeTimerScrubPosition); } } return 0.f; } void SAnimationScrubPanel::ReplaceLockedSequence(class UAnimSequenceBase* NewLockedSequence) { LockedSequence = NewLockedSequence; } UAnimInstance* SAnimationScrubPanel::GetAnimInstanceWithBlueprint() const { if (UDebugSkelMeshComponent* DebugComponent = GetPreviewScene()->GetPreviewMeshComponent()) { UAnimInstance* Instance = DebugComponent->GetAnimInstance(); if ((Instance != nullptr) && (Instance->GetClass()->ClassGeneratedBy != nullptr)) { return Instance; } } return nullptr; } bool SAnimationScrubPanel::GetAnimBlueprintDebugData(UAnimInstance*& Instance, FAnimBlueprintDebugData*& DebugInfo) const { Instance = GetAnimInstanceWithBlueprint(); if (Instance != nullptr) { // Avoid updating the instance if we're replaying the past if (UAnimBlueprintGeneratedClass* AnimBlueprintClass = Cast(Instance->GetClass())) { if (UAnimBlueprint* Blueprint = Cast(AnimBlueprintClass->ClassGeneratedBy)) { if (Blueprint->GetObjectBeingDebugged() == Instance) { DebugInfo = &(AnimBlueprintClass->GetAnimBlueprintDebugData()); return true; } } } } return false; } void SAnimationScrubPanel::OnCropAnimSequence( bool bFromStart, float CurrentTime ) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if(PreviewInstance) { float Length = PreviewInstance->GetLength(); if (PreviewInstance->GetCurrentAsset()) { UAnimSequence* AnimSequence = Cast( PreviewInstance->GetCurrentAsset() ); if( AnimSequence ) { const FScopedTransaction Transaction( LOCTEXT("CropAnimSequence", "Crop Animation Sequence") ); //Call modify to restore slider position PreviewInstance->Modify(); //Call modify to restore anim sequence current state AnimSequence->Modify(); const FFrameTime AssetFrameTime = AnimSequence->GetSamplingFrameRate().AsFrameTime(CurrentTime); const FFrameNumber RoundedAssetFrame = AssetFrameTime.RoundToFrame(); const TRange TrimRange(TRangeBound::Inclusive(bFromStart ? 0 : RoundedAssetFrame), bFromStart ? TRangeBound::Exclusive(RoundedAssetFrame) : TRangeBound::Exclusive(AnimSequence->GetNumberOfSampledKeys())); // Trim off the user-selected part of the raw anim data. UE::Anim::AnimationData::Trim(AnimSequence, TrimRange); //Resetting slider position to the first frame PreviewInstance->SetPosition( 0.0f, false ); OnSetInputViewRange.ExecuteIfBound(0, AnimSequence->GetPlayLength()); } } } } void SAnimationScrubPanel::OnAppendAnimSequence( bool bFromStart, int32 NumOfFrames ) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if(PreviewInstance && PreviewInstance->GetCurrentAsset()) { UAnimSequence* AnimSequence = Cast(PreviewInstance->GetCurrentAsset()); if(AnimSequence) { const FScopedTransaction Transaction(LOCTEXT("InsertAnimSequence", "Insert Animation Sequence")); //Call modify to restore slider position PreviewInstance->Modify(); //Call modify to restore anim sequence current state AnimSequence->Modify(); // Crop the raw anim data. int32 StartFrame = (bFromStart)? 0 : AnimSequence->GetDataModel()->GetNumberOfFrames() - 1; int32 EndFrame = StartFrame + NumOfFrames; int32 CopyFrame = StartFrame; UE::Anim::AnimationData::DuplicateKeys(AnimSequence, StartFrame, NumOfFrames, CopyFrame); OnSetInputViewRange.ExecuteIfBound(0, AnimSequence->GetPlayLength()); } } } void SAnimationScrubPanel::OnInsertAnimSequence( bool bBefore, int32 CurrentFrame ) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if(PreviewInstance && PreviewInstance->GetCurrentAsset()) { UAnimSequence* AnimSequence = Cast(PreviewInstance->GetCurrentAsset()); if(AnimSequence) { const FScopedTransaction Transaction(LOCTEXT("InsertAnimSequence", "Insert Animation Sequence")); //Call modify to restore slider position PreviewInstance->Modify(); //Call modify to restore anim sequence current state AnimSequence->Modify(); // Duplicate specified key const int32 StartFrame = (bBefore)? CurrentFrame : CurrentFrame + 1; UE::Anim::AnimationData::DuplicateKeys(AnimSequence, StartFrame, 1, CurrentFrame); OnSetInputViewRange.ExecuteIfBound(0, AnimSequence->GetPlayLength()); } } } void SAnimationScrubPanel::OnReZeroAnimSequence(int32 FrameIndex) { UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if(PreviewInstance) { UDebugSkelMeshComponent* PreviewSkelComp = GetPreviewScene()->GetPreviewMeshComponent(); if (PreviewInstance->GetCurrentAsset() && PreviewSkelComp ) { if(UAnimSequence* AnimSequence = Cast( PreviewInstance->GetCurrentAsset())) { if (const USkeleton* Skeleton = AnimSequence->GetSkeleton()) { const FName RootBoneName = Skeleton->GetReferenceSkeleton().GetBoneName(0); if(AnimSequence->GetDataModel()->IsValidBoneTrackName(RootBoneName)) { TArray PosKeys; TArray RotKeys; TArray ScaleKeys; TArray BoneTransforms; AnimSequence->GetDataModel()->GetBoneTrackTransforms(RootBoneName, BoneTransforms); PosKeys.SetNum(BoneTransforms.Num()); RotKeys.SetNum(BoneTransforms.Num()); ScaleKeys.SetNum(BoneTransforms.Num()); // Find vector that would translate current root bone location onto origin. FVector FrameTransform = FVector::ZeroVector; if (FrameIndex == INDEX_NONE) { // Use current transform FrameTransform = PreviewSkelComp->GetComponentSpaceTransforms()[0].GetLocation(); } else if(BoneTransforms.IsValidIndex(FrameIndex)) { // Use transform at frame FrameTransform = BoneTransforms[FrameIndex].GetLocation(); } FVector ApplyTranslation = -1.f * FrameTransform; // Convert into world space const FVector WorldApplyTranslation = PreviewSkelComp->GetComponentTransform().TransformVector(ApplyTranslation); ApplyTranslation = PreviewSkelComp->GetComponentTransform().InverseTransformVector(WorldApplyTranslation); for(int32 KeyIndex = 0; KeyIndex < BoneTransforms.Num(); KeyIndex++) { PosKeys[KeyIndex] = FVector3f(BoneTransforms[KeyIndex].GetLocation() + ApplyTranslation); RotKeys[KeyIndex] = FQuat4f(BoneTransforms[KeyIndex].GetRotation()); ScaleKeys[KeyIndex] = FVector3f(BoneTransforms[KeyIndex].GetScale3D()); } IAnimationDataController& Controller = AnimSequence->GetController(); Controller.SetBoneTrackKeys(RootBoneName, PosKeys, RotKeys, ScaleKeys); } } } } } } bool SAnimationScrubPanel::GetDisplayDrag() const { if(TimelineDelegates.GetPlaybackTimeDelegate.IsBound()) { if(TimelineDelegates.GetPlaybackTimeDelegate.Execute().IsSet()) { return true; } } UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance(); if (PreviewInstance && PreviewInstance->GetCurrentAsset()) { return true; } return false; } void SAnimationScrubPanel::SetPlaybackMode(EPlaybackMode::Type InMode) { if(TimelineDelegates.SetPlaybackModeDelegate.IsBound()) { if(TimelineDelegates.SetPlaybackModeDelegate.Execute(InMode)) { return; } } if (UAnimSingleNodeInstance* PreviewInstance = GetPreviewInstance()) { switch(InMode) { case EPlaybackMode::Stopped: { PreviewInstance->SetPlaying(false); break; } case EPlaybackMode::PlayingForward: { PreviewInstance->SetReverse(false); PreviewInstance->SetPlaying(true); break; } case EPlaybackMode::PlayingReverse: { PreviewInstance->SetReverse(true); PreviewInstance->SetPlaying(true); break; } } } } #undef LOCTEXT_NAMESPACE