// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimTimeline/AnimModel.h" #include "IPersonaPreviewScene.h" #include "Animation/DebugSkelMeshComponent.h" #include "AnimPreviewInstance.h" #include "Preferences/PersonaOptions.h" #include "Animation/EditorAnimBaseObj.h" #include "AnimTimeline/AnimTimelineTrack.h" #include "Animation/AnimSequence.h" #define LOCTEXT_NAMESPACE "FAnimModel" const FAnimModel::FSnapType FAnimModel::FSnapType::Frames("Frames", LOCTEXT("FramesSnapName", "Frames"), [](const FAnimModel& InModel, double InTime) { // Round to nearest frame FFrameRate FrameRate = InModel.GetFrameRate(); if(FrameRate.IsValid()) { return (double)FrameRate.AsFrameNumber(InTime).Value; } return InTime; }); const FAnimModel::FSnapType FAnimModel::FSnapType::Notifies("Notifies", LOCTEXT("NotifiesSnapName", "Notifies")); const FAnimModel::FSnapType FAnimModel::FSnapType::CompositeSegment("CompositeSegment", LOCTEXT("CompositeSegmentSnapName", "Composite Segments")); const FAnimModel::FSnapType FAnimModel::FSnapType::MontageSection("MontageSection", LOCTEXT("MontageSectionSnapName", "Montage Sections")); FAnimModel::FAnimModel(const TSharedRef& InPreviewScene, const TSharedRef& InEditableSkeleton, const TSharedRef& InCommandList) : WeakPreviewScene(InPreviewScene) , WeakEditableSkeleton(InEditableSkeleton) , WeakCommandList(InCommandList) , bIsSelecting(false) { } void FAnimModel::Initialize() { } FAnimatedRange FAnimModel::GetViewRange() const { return ViewRange; } FAnimatedRange FAnimModel::GetWorkingRange() const { return WorkingRange; } FFrameRate FAnimModel::GetFrameRate() const { return GetAnimSequenceBase()->GetSamplingFrameRate(); } int32 FAnimModel::GetTickResolution() const { return FMath::RoundToInt32((double)GetDefault()->TimelineScrubSnapValue * GetFrameRate().AsDecimal()); } TRange FAnimModel::GetPlaybackRange() const { const int32 Resolution = GetTickResolution(); return TRange(FFrameNumber(FMath::RoundToInt32(PlaybackRange.GetLowerBoundValue() * (double)Resolution)), FFrameNumber(FMath::RoundToInt32(PlaybackRange.GetUpperBoundValue() * (double)Resolution))); } FFrameNumber FAnimModel::GetScrubPosition() const { if(WeakPreviewScene.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = WeakPreviewScene.Pin()->GetPreviewMeshComponent(); if(PreviewMeshComponent && PreviewMeshComponent->IsPreviewOn()) { return FFrameNumber(FMath::RoundToInt32(PreviewMeshComponent->PreviewInstance->GetCurrentTime() * (double)GetTickResolution())); } } return FFrameNumber(0); } float FAnimModel::GetScrubTime() const { if(WeakPreviewScene.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = WeakPreviewScene.Pin()->GetPreviewMeshComponent(); if(PreviewMeshComponent && PreviewMeshComponent->IsPreviewOn()) { return PreviewMeshComponent->PreviewInstance->GetCurrentTime(); } } return 0.0f; } void FAnimModel::SetScrubPosition(FFrameTime NewScrubPostion) const { if(WeakPreviewScene.IsValid()) { UDebugSkelMeshComponent* PreviewMeshComponent = WeakPreviewScene.Pin()->GetPreviewMeshComponent(); if(PreviewMeshComponent && PreviewMeshComponent->IsPreviewOn()) { if(PreviewMeshComponent->PreviewInstance->IsPlaying()) { PreviewMeshComponent->PreviewInstance->SetPlaying(false); } PreviewMeshComponent->PreviewInstance->SetPosition(static_cast(NewScrubPostion.AsDecimal() / static_cast(GetTickResolution()))); } } } void FAnimModel::HandleViewRangeChanged(TRange InRange, EViewRangeInterpolation InInterpolation) { SetViewRange(InRange); } void FAnimModel::SetViewRange(TRange InRange) { ViewRange = InRange; if(WorkingRange.HasLowerBound() && WorkingRange.HasUpperBound()) { WorkingRange = TRange::Hull(WorkingRange, ViewRange); } else { WorkingRange = ViewRange; } } void FAnimModel::HandleWorkingRangeChanged(TRange InRange) { WorkingRange = InRange; } bool FAnimModel::IsTrackSelected(const TSharedRef& InTrack) const { return SelectedTracks.Find(InTrack) != nullptr; } void FAnimModel::ClearTrackSelection() { SelectedTracks.Empty(); } void FAnimModel::SetTrackSelected(const TSharedRef& InTrack, bool bIsSelected) { if(bIsSelected) { SelectedTracks.Add(InTrack); } else { SelectedTracks.Remove(InTrack); } } void FAnimModel::AddReferencedObjects(FReferenceCollector& Collector) { EditorObjectTracker.AddReferencedObjects(Collector); } void FAnimModel::SelectObjects(const TArray& Objects) { if(!bIsSelecting) { TGuardValue GuardValue(bIsSelecting, true); OnSelectObjects.ExecuteIfBound(Objects); OnHandleObjectsSelectedDelegate.Broadcast(Objects); } } UObject* FAnimModel::ShowInDetailsView(UClass* EdClass) { UObject* Obj = EditorObjectTracker.GetEditorObjectForClass(EdClass); if(Obj != nullptr) { if(Obj->IsA(UEditorAnimBaseObj::StaticClass())) { if(!bIsSelecting) { TGuardValue GuardValue(bIsSelecting, true); ClearTrackSelection(); UEditorAnimBaseObj *EdObj = Cast(Obj); InitDetailsViewEditorObject(EdObj); TArray Objects; Objects.Add(EdObj); OnSelectObjects.ExecuteIfBound(Objects); OnHandleObjectsSelectedDelegate.Broadcast(Objects); } } } return Obj; } void FAnimModel::ClearDetailsView() { if(!bIsSelecting) { TGuardValue GuardValue(bIsSelecting, true); TArray Objects; OnSelectObjects.ExecuteIfBound(Objects); OnHandleObjectsSelectedDelegate.Broadcast(Objects); } } float FAnimModel::CalculateSequenceLengthOfEditorObject() const { if(UAnimSequenceBase* AnimSequenceBase = GetAnimSequenceBase()) { return AnimSequenceBase->GetPlayLength(); } return 0.0f; } void FAnimModel::RecalculateSequenceLength() { if(UAnimSequenceBase* AnimSequenceBase = GetAnimSequenceBase()) { AnimSequenceBase->ClampNotifiesAtEndOfSequence(); } } void FAnimModel::SetEditableTime(int32 TimeIndex, double Time, bool bIsDragging) { EditableTimes[TimeIndex] = FMath::Clamp(Time, 0.0, (double)CalculateSequenceLengthOfEditorObject()); OnSetEditableTime(TimeIndex, EditableTimes[TimeIndex], bIsDragging); } bool FAnimModel::Snap(float& InOutTime, float InSnapMargin, TArrayView InSkippedSnapTypes, FName& OutSnapType) const { double DoubleTime = InOutTime; bool bResult = Snap(DoubleTime, (double)InSnapMargin, InSkippedSnapTypes, OutSnapType); InOutTime = static_cast(DoubleTime); return bResult; } bool FAnimModel::Snap(double& InOutTime, double InSnapMargin, TArrayView InSkippedSnapTypes, FName& OutSnapType) const { InSnapMargin = FMath::Max(InSnapMargin, (double)KINDA_SMALL_NUMBER); double ClosestDelta = DBL_MAX; double ClosestSnapTime = DBL_MAX; FName ClosestSnapType = NAME_None; // Check for enabled snap functions first for(const TPair& SnapTypePair : SnapTypes) { if(SnapTypePair.Value.SnapFunction != nullptr) { if(IsSnapChecked(SnapTypePair.Value.Type)) { if(!InSkippedSnapTypes.Contains(SnapTypePair.Value.Type)) { double SnappedTime = SnapTypePair.Value.SnapFunction(*this, InOutTime); if(SnappedTime != InOutTime) { double Delta = FMath::Abs(SnappedTime - InOutTime); if(Delta < InSnapMargin && Delta < ClosestDelta) { ClosestSnapType = SnapTypePair.Value.Type; ClosestDelta = Delta; ClosestSnapTime = SnappedTime; } } } } } } // Find the closest in-range enabled snap time for(const FSnapTime& SnapTime : SnapTimes) { double Delta = FMath::Abs(SnapTime.Time - InOutTime); if(Delta < InSnapMargin && Delta < ClosestDelta) { if(!InSkippedSnapTypes.Contains(SnapTime.Type)) { if(const FSnapType* SnapType = SnapTypes.Find(SnapTime.Type)) { if(IsSnapChecked(SnapTime.Type)) { ClosestSnapType = SnapTime.Type; ClosestDelta = Delta; ClosestSnapTime = SnapTime.Time; } } } } } if(ClosestDelta != DBL_MAX) { OutSnapType = ClosestSnapType; InOutTime = ClosestSnapTime; return true; } return false; } void FAnimModel::ToggleSnap(FName InSnapName) { if(IsSnapChecked(InSnapName)) { GetMutableDefault()->TimelineEnabledSnaps.Remove(InSnapName); } else { GetMutableDefault()->TimelineEnabledSnaps.AddUnique(InSnapName); } } bool FAnimModel::IsSnapChecked(FName InSnapName) const { return GetDefault()->TimelineEnabledSnaps.Contains(InSnapName); } bool FAnimModel::IsSnapAvailable(FName InSnapName) const { return SnapTypes.Find(InSnapName) != nullptr; } void FAnimModel::BuildContextMenu(FMenuBuilder& InMenuBuilder) { // Let each selected item contribute to the context menu TSet ExistingMenuTypes; for(const TSharedRef& SelectedItem : SelectedTracks) { SelectedItem->AddToContextMenu(InMenuBuilder, ExistingMenuTypes); } } void FAnimModel::AddRootTrack(TSharedRef InTrack) { if (GetMutableDefault()->GetAllowedAnimationEditorTracks().PassesFilter(InTrack->GetTypeName())) { PRAGMA_DISABLE_DEPRECATION_WARNINGS RootTracks.Add(InTrack); PRAGMA_ENABLE_DEPRECATION_WARNINGS } } void FAnimModel::ClearRootTracks() { PRAGMA_DISABLE_DEPRECATION_WARNINGS RootTracks.Empty(); PRAGMA_ENABLE_DEPRECATION_WARNINGS } void FAnimModel::ForEachRootTrack(TFunctionRef InFunction) { PRAGMA_DISABLE_DEPRECATION_WARNINGS for (TSharedRef& Track : RootTracks) { InFunction(Track.Get()); } PRAGMA_ENABLE_DEPRECATION_WARNINGS } #undef LOCTEXT_NAMESPACE