// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerKeyParams.h" #include "IKeyArea.h" #include "ISequencer.h" #include "ISequencerSection.h" #include "MovieSceneTrack.h" #include "Algo/IndexOf.h" namespace UE { namespace Sequencer { void FKeyOperation::IterateOperations(TFunctionRef Callback) const { for (const TTuple& Pair : CandidatesByTrack) { UMovieSceneTrack* Track = Pair.Key; TArrayView Operations = Pair.Value.Operations; Callback(Track, Operations); } } void FKeyOperation::InitializeOperation(FFrameNumber InKeyTime) { for (auto It = CandidatesByTrack.CreateIterator(); It; ++It) { UMovieSceneTrack* Track = It.Key(); FSectionCandidates& Candidates = It.Value(); Candidates.FilterOperations(Track, InKeyTime); if (Candidates.Operations.Num() == 0) { It.RemoveCurrent(); continue; } for (const FKeySectionOperation& Operation : Candidates.Operations) { UMovieSceneSection* SectionObject = Operation.Section->GetSectionObject(); SectionObject->Modify(); SectionObject->ExpandToFrame(InKeyTime); } } } void FKeyOperation::ApplyDefault(FFrameNumber InKeyTime, ISequencer& InSequencer) const { auto Iterator = [InKeyTime, &InSequencer](UMovieSceneTrack* Track, TArrayView Operations) { ApplyOperations(InKeyTime, Operations, Track->FindObjectBindingGuid(), InSequencer); }; IterateOperations(Iterator); } void FKeyOperation::ApplyOperations(FFrameNumber InKeyTime, TArrayView InOperations, const FGuid& ObjectBindingID, ISequencer& InSequencer) { // @todo: Do we even want to support multiple??? for (const FKeySectionOperation& Operation : InOperations) { for (TSharedPtr KeyArea : Operation.KeyAreas) { KeyArea->AddOrUpdateKey(InKeyTime, ObjectBindingID, InSequencer); } } } void FKeyOperation::Populate(UMovieSceneTrack* InTrack, TSharedPtr InSection, TSharedPtr InKeyArea) { UMovieSceneSection* SectionObject = InSection->GetSectionObject(); if (SectionObject->IsReadOnly()) { return; } FSectionCandidates& Candidates = CandidatesByTrack.FindOrAdd(InTrack); FKeySectionOperation* ExistingOperation = Algo::FindBy(Candidates.Operations, InSection, &FKeySectionOperation::Section); if (ExistingOperation) { ExistingOperation->KeyAreas.Add(InKeyArea); } else { Candidates.Operations.Add(FKeySectionOperation{ InSection, { InKeyArea } }); } } void FKeyOperation::FSectionCandidates::FilterOperations(UMovieSceneTrack* Track, FFrameNumber KeyTime) { checkf(Operations.Num() != 0, TEXT("FSectionCandidates should never have been constructed with empty sections")); UMovieSceneSection* SectionToKey = Track->GetSectionToKey(); if (SectionToKey) { // If we have a section to key, and it is one of the prospective sections, it should always receive the full weighting const int32 SectionToKeyIndex = Algo::IndexOfBy(Operations, SectionToKey, [](const FKeySectionOperation& In) { return In.Section->GetSectionObject(); }); if (SectionToKeyIndex != INDEX_NONE) { FKeySectionOperation Op = MoveTemp(Operations[SectionToKeyIndex]); Operations.Empty(); Operations.Add(MoveTemp(Op)); return; } } if (Operations.Num() > 1) { TArray> OldOperations; Swap(OldOperations, Operations); TArray> Sections; for (const FKeySectionOperation& Operation : OldOperations) { UMovieSceneSection* Section = Operation.Section->GetSectionObject(); Sections.Add(Section); } // Sort by row index so we can choose one per row Algo::SortBy(Sections, &UMovieSceneSection::GetRowIndex); bool bFoundOverlap = false; // Find a sections that overlaps the current time for (int32 Index = 0; Index < Sections.Num(); ) { const int32 StartIndex = Index; const int32 RowIndex = Sections[Index]->GetRowIndex(); UMovieSceneSection* ThisSectionToKey = nullptr; // Increment over all the sections on the current row for (; Index < Sections.Num() && Sections[Index]->GetRowIndex() == RowIndex; ++Index) { if (Sections[Index]->GetRange().Contains(KeyTime)) { if (!ThisSectionToKey || ThisSectionToKey->GetOverlapPriority() > Sections[Index]->GetOverlapPriority()) { ThisSectionToKey = Sections[Index]; } } } if (ThisSectionToKey) { bFoundOverlap = true; const int32 ThisSectionToKeyIndex = Algo::IndexOfBy(OldOperations, ThisSectionToKey, [](const FKeySectionOperation& In) { return In.Section->GetSectionObject(); }); Operations.Add(MoveTemp(OldOperations[ThisSectionToKeyIndex])); OldOperations.RemoveAt(ThisSectionToKeyIndex, 1, false); } } // If nothing was overlapping, we find the nearest section on each row, and expand those if (!bFoundOverlap) { for (int32 Index = 0; Index < Sections.Num(); ) { const int32 StartIndex = Index; const int32 RowIndex = Sections[Index]->GetRowIndex(); // Increment over all the sections on the current row for (; Index < Sections.Num() && Sections[Index]->GetRowIndex() == RowIndex; ++Index) {} TArrayView Row = MakeArrayView(Sections.GetData() + StartIndex, Index - StartIndex); UMovieSceneSection* ThisSectionToKey = MovieSceneHelpers::FindNearestSectionAtTime(Row, KeyTime); if (ThisSectionToKey) { const int32 ThisSectionToKeyIndex = Algo::IndexOfBy(OldOperations, ThisSectionToKey, [](const FKeySectionOperation& In) { return In.Section->GetSectionObject(); }); Operations.Add(MoveTemp(OldOperations[ThisSectionToKeyIndex])); OldOperations.RemoveAt(ThisSectionToKeyIndex, 1, false); } } } } } } // namespace Sequencer } // namespace UE