Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/Tools/SequencerSnapField.cpp
Max Chen 0167d4ea0f Sequencer: MVVM2 branch and Layer Bars
Copying //Tasks/UE5/Dev-SequencerMVVM2 to Main (//UE5/Main) @20364093

#preflight 628866dfb94f739b152c1e29
#preflight 628866e4585e8f793ee80943
#rb ludovic.chabant, andrew.rodham
#fyi ludovic.chabant, andrew.rodham, andrew.porter
#jira UE-105322

[CL 20364493 by Max Chen in ue5-main branch]
2022-05-25 10:39:33 -04:00

230 lines
7.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Tools/SequencerSnapField.h"
#include "MovieScene.h"
#include "SSequencer.h"
#include "MovieSceneTimeHelpers.h"
#include "MovieSceneSequence.h"
#include "ISequencerSection.h"
#include "MVVM/ViewModels/ViewModel.h"
#include "MVVM/Views/SOutlinerView.h"
#include "MVVM/Extensions/ISnappableExtension.h"
struct FSnapGridVisitor : ISequencerEntityVisitor, UE::Sequencer::ISnapField
{
FSnapGridVisitor(UE::Sequencer::ISnapCandidate& InCandidate, uint32 EntityMask)
: ISequencerEntityVisitor(EntityMask)
, Candidate(InCandidate)
{}
virtual void VisitKey(FKeyHandle KeyHandle, FFrameNumber KeyTime, const UE::Sequencer::TViewModelPtr<UE::Sequencer::FChannelModel>& Channel, UMovieSceneSection* Section) const
{
using namespace UE::Sequencer;
if (Candidate.IsKeyApplicable(KeyHandle, Channel))
{
Snaps.Add(FSnapPoint{ FSnapPoint::Key, KeyTime });
}
}
virtual void VisitDataModel(UE::Sequencer::FViewModel* DataModel) const
{
using namespace UE::Sequencer;
if (ISnappableExtension* Snappable = DataModel->CastThis<ISnappableExtension>())
{
Snappable->AddToSnapField(Candidate, *const_cast<FSnapGridVisitor*>(this));
}
}
virtual void AddSnapPoint(const UE::Sequencer::FSnapPoint& SnapPoint) override
{
Snaps.Add(SnapPoint);
}
UE::Sequencer::ISnapCandidate& Candidate;
mutable TArray<UE::Sequencer::FSnapPoint> Snaps;
};
FSequencerSnapField::FSequencerSnapField(const FSequencer& InSequencer, UE::Sequencer::ISnapCandidate& Candidate, uint32 EntityMask)
{
Initialize(InSequencer, Candidate, EntityMask);
Finalize();
}
void FSequencerSnapField::AddExplicitSnap(UE::Sequencer::FSnapPoint InSnap)
{
SortedSnaps.Add(InSnap);
}
void FSequencerSnapField::Initialize(const FSequencer& InSequencer, UE::Sequencer::ISnapCandidate& Candidate, uint32 EntityMask)
{
using namespace UE::Sequencer;
TSharedPtr<SOutlinerView> TreeView = StaticCastSharedRef<SSequencer>(InSequencer.GetSequencerWidget())->GetTreeView();
TArray<TViewModelPtr<IOutlinerExtension>> VisibleItems;
TreeView->GetVisibleItems(VisibleItems);
TRange<double> ViewRange = InSequencer.GetViewRange();
FSequencerEntityWalker Walker(
FSequencerEntityRange(ViewRange, InSequencer.GetFocusedTickResolution()),
FVector2D(SequencerSectionConstants::KeySize));
// Traverse the visible space, collecting snapping times as we go
FSnapGridVisitor Visitor(Candidate, EntityMask);
for (const TViewModelPtr<IOutlinerExtension>& Item : VisibleItems)
{
Walker.Traverse(Visitor, Item);
}
// Add the playback range start/end bounds as potential snap candidates
TRange<FFrameNumber> PlaybackRange = InSequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetPlaybackRange();
if(UE::MovieScene::DiscreteSize(PlaybackRange) > 0)
{
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::PlaybackRange, UE::MovieScene::DiscreteInclusiveLower(PlaybackRange)});
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::PlaybackRange, UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange)});
}
// Add the current time as a potential snap candidate
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::CurrentTime, InSequencer.GetLocalTime().Time.FrameNumber });
// Add the selection range bounds as a potential snap candidate
TRange<FFrameNumber> SelectionRange = InSequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetSelectionRange();
if (UE::MovieScene::DiscreteSize(SelectionRange) > 0)
{
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::InOutRange, UE::MovieScene::DiscreteInclusiveLower(SelectionRange)});
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::InOutRange, UE::MovieScene::DiscreteExclusiveUpper(SelectionRange) - 1});
}
// Add in the marked frames
for (const FMovieSceneMarkedFrame& MarkedFrame : InSequencer.GetFocusedMovieSceneSequence()->GetMovieScene()->GetMarkedFrames())
{
Visitor.Snaps.Add( FSnapPoint{ FSnapPoint::Mark, MarkedFrame.FrameNumber } );
}
// Add in the global marked frames
for (const FMovieSceneMarkedFrame& MarkedFrame : InSequencer.GetGlobalMarkedFrames())
{
Visitor.Snaps.Add(FSnapPoint{ FSnapPoint::Mark, MarkedFrame.FrameNumber });
}
if (SortedSnaps.Num() == 0)
{
SortedSnaps = MoveTemp(Visitor.Snaps);
}
else
{
SortedSnaps.Append(Visitor.Snaps);
}
}
void FSequencerSnapField::Finalize()
{
using namespace UE::Sequencer;
// Sort
SortedSnaps.Sort([](const FSnapPoint& A, const FSnapPoint& B){
return A.Time < B.Time;
});
// Remove duplicates
for (int32 Index = 0; Index < SortedSnaps.Num(); ++Index)
{
const FFrameNumber CurrentTime = SortedSnaps[Index].Time;
int32 NumToMerge = 0;
for (int32 DuplIndex = Index + 1; DuplIndex < SortedSnaps.Num(); ++DuplIndex)
{
if (CurrentTime != SortedSnaps[DuplIndex].Time)
{
break;
}
++NumToMerge;
}
if (NumToMerge)
{
SortedSnaps.RemoveAt(Index + 1, NumToMerge, false);
}
}
}
TOptional<FFrameNumber> FSequencerSnapField::Snap(FFrameNumber InTime, int32 Threshold) const
{
int32 Min = 0;
int32 Max = SortedSnaps.Num();
// Binary search, then linearly search a range
for ( ; Min != Max ; )
{
int32 SearchIndex = Min + (Max - Min) / 2;
FFrameNumber ProspectiveSnapPos = SortedSnaps[SearchIndex].Time;
if (ProspectiveSnapPos > InTime + Threshold)
{
Max = SearchIndex;
}
else if (ProspectiveSnapPos < InTime - Threshold)
{
Min = SearchIndex + 1;
}
else
{
// Linearly search forwards and backwards to find the closest snap
FFrameNumber SnapDelta = ProspectiveSnapPos - InTime;
// Search forwards while we're in the threshold
for (int32 FwdIndex = SearchIndex+1; FwdIndex < Max-1 && SortedSnaps[FwdIndex].Time < InTime + Threshold; ++FwdIndex)
{
FFrameNumber ThisSnapDelta = InTime - SortedSnaps[FwdIndex].Time;
if (FMath::Abs(ThisSnapDelta) < FMath::Abs(SnapDelta))
{
SnapDelta = ThisSnapDelta;
ProspectiveSnapPos = SortedSnaps[FwdIndex].Time;
}
}
// Search backwards while we're in the threshold
for (int32 BckIndex = SearchIndex-1; BckIndex >= Min && SortedSnaps[BckIndex].Time > InTime + Threshold; --BckIndex)
{
FFrameNumber ThisSnapDelta = InTime - SortedSnaps[BckIndex].Time;
if (FMath::Abs(ThisSnapDelta) < FMath::Abs(SnapDelta))
{
SnapDelta = ThisSnapDelta;
ProspectiveSnapPos = SortedSnaps[BckIndex].Time;
}
}
return ProspectiveSnapPos;
}
}
return TOptional<FFrameNumber>();
}
TOptional<FSequencerSnapField::FSnapResult> FSequencerSnapField::Snap(const TArray<FFrameNumber>& InTimes, int32 Threshold) const
{
TOptional<FSnapResult> ProspectiveSnap;
FFrameNumber SnapDelta(0);
for (FFrameNumber Time : InTimes)
{
TOptional<FFrameNumber> ThisSnap = Snap(Time, Threshold);
if (!ThisSnap.IsSet())
{
continue;
}
FFrameNumber ThisSnapDelta = ThisSnap.GetValue() - Time;
if (!ProspectiveSnap.IsSet() || FMath::Abs(ThisSnapDelta) < FMath::Abs(SnapDelta))
{
ProspectiveSnap = FSnapResult{ Time, ThisSnap.GetValue() };
SnapDelta = ThisSnapDelta;
}
}
return ProspectiveSnap;
}