// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerKeyParams.h" #include "Algo/Find.h" #include "Algo/IndexOf.h" #include "Algo/Sort.h" #include "CoreTypes.h" #include "IKeyArea.h" #include "ISequencerSection.h" #include "Math/Range.h" #include "Misc/AssertionMacros.h" #include "Misc/FrameNumber.h" #include "MovieSceneCommonHelpers.h" #include "MovieSceneSection.h" #include "MovieSceneTrack.h" #include "Templates/Function.h" #include "Templates/Tuple.h" #include "Templates/UnrealTemplate.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(); for (TSharedPtr KeyArea : Operation.KeyAreas) { UObject* OwningObject = KeyArea->GetOwningObject(); if (OwningObject != SectionObject) { OwningObject->Modify(); } } SectionObject->ExpandToFrame(InKeyTime); } } } void FKeyOperation::ApplyDefault(FFrameNumber InKeyTime, ISequencer& InSequencer, TArray* OutResults) const { auto Iterator = [InKeyTime, &InSequencer, &OutResults](UMovieSceneTrack* Track, TArrayView Operations) { ApplyOperations(InKeyTime, Operations, Track->FindObjectBindingGuid(), InSequencer, OutResults); }; IterateOperations(Iterator); } void FKeyOperation::ApplyOperations(FFrameNumber InKeyTime, TArrayView InOperations, const FGuid& ObjectBindingID, ISequencer& InSequencer, TArray* OutResults) { // @todo: Do we even want to support multiple??? for (const FKeySectionOperation& Operation : InOperations) { for (TSharedPtr KeyArea : Operation.KeyAreas) { FKeyHandle KeyHandle = KeyArea->AddOrUpdateKey(InKeyTime, ObjectBindingID, InSequencer); if (OutResults) { FAddKeyResult Result; Result.KeyArea = KeyArea; Result.KeyHandle = KeyHandle; OutResults->Add(Result); } } } } void FKeyOperation::Populate(UMovieSceneTrack* InTrack, TSharedPtr InSection, TSharedPtr InKeyArea) { UMovieSceneSection* SectionObject = InSection->GetSectionObject(); if (SectionObject->IsReadOnly()) { return; } if (!MovieSceneHelpers::IsSectionKeyable(SectionObject)) { 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, EAllowShrinking::No); } } // 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, EAllowShrinking::No); } } } } } } // namespace Sequencer } // namespace UE