// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneTrack.h" #include "MovieScene.h" #include "MovieSceneSequence.h" #include "Containers/SortedMap.h" #include "MovieSceneTimeHelpers.h" #include "Evaluation/MovieSceneSegment.h" #include "Compilation/MovieSceneSegmentCompiler.h" #include "Compilation/MovieSceneCompilerRules.h" #include "Compilation/MovieSceneEvaluationTreePopulationRules.h" #include "Compilation/IMovieSceneTemplateGenerator.h" #include "Decorations/IMovieSceneTrackDecoration.h" #include "Decorations/IMovieSceneSectionDecoration.h" #include "Decorations/IMovieSceneLifetimeDecoration.h" #include "Evaluation/MovieSceneEvaluationTrack.h" #include "Evaluation/MovieSceneEvaluationTemplate.h" #include "Evaluation/MovieSceneEvaluationCustomVersion.h" #include "Channels/MovieSceneChannelProxy.h" #include "Channels/MovieSceneChannel.h" #include "Compilation/IMovieSceneTrackTemplateProducer.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneTrack) int32 GMovieSceneRemoveMutedTracksOnCook = 0; static FAutoConsoleVariableRef CVarMovieSceneRemoveMutedTracksOnCook( TEXT("MovieScene.RemoveMutedTracksOnCook"), GMovieSceneRemoveMutedTracksOnCook, TEXT("If 1 remove muted tracks on cook, otherwise leave as is."), ECVF_Default); UMovieSceneTrack::UMovieSceneTrack(const FObjectInitializer& InInitializer) : Super(InInitializer) { #if WITH_EDITORONLY_DATA TrackTint = FColor(127, 127, 127, 0); SortingOrder = -1; bSupportsDefaultSections = true; bSupportsConditions = true; #endif BuiltInTreePopulationMode = ETreePopulationMode::HighPassPerRow; } void UMovieSceneTrack::PostInitProperties() { SetFlags(RF_Transactional); // Propagate sub object flags from our outer (movie scene) to ourselves. This is required for tracks that are stored on blueprints (archetypes) so that they can be referenced in worlds. if (GetOuter()->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject)) { SetFlags(GetOuter()->GetMaskedFlags(RF_PropagateToSubObjects)); } Super::PostInitProperties(); } void UMovieSceneTrack::PostLoad() { Super::PostLoad(); if (GetLinkerCustomVersion(FMovieSceneEvaluationCustomVersion::GUID) < FMovieSceneEvaluationCustomVersion::ChangeEvaluateNearestSectionDefault) { EvalOptions.bEvalNearestSection = EvalOptions.bEvaluateNearestSection_DEPRECATED; } // Remove any null sections for (int32 SectionIndex = 0; SectionIndex < GetAllSections().Num(); ) { UMovieSceneSection* Section = GetAllSections()[SectionIndex]; if (Section == nullptr) { #if WITH_EDITOR UE_LOG(LogMovieScene, Warning, TEXT("Removing null section from %s:%s"), *GetPathName(), *GetDisplayName().ToString()); #endif RemoveSectionAt(SectionIndex); } else if (Section->GetRange().IsEmpty()) { #if WITH_EDITOR //UE_LOG(LogMovieScene, Warning, TEXT("Removing section %s:%s with empty range"), *GetPathName(), *GetDisplayName().ToString()); #endif RemoveSectionAt(SectionIndex); } else { ++SectionIndex; } } } void UMovieSceneTrack::AddSection(FSectionParameter Section) { CallAddSection(*Section.Section); Section.Section->OnAddedToTrack(this); OnSectionAddedImpl(Section.Section); } void UMovieSceneTrack::RemoveSection(FSectionParameter Section) { CallRemoveSection(*Section.Section); Section.Section->OnRemovedFromTrack(); OnSectionRemovedImpl(Section.Section); } void UMovieSceneTrack::RemoveSectionAt(FSectionIndexParameter SectionIndex) { const TArray& Sections = GetAllSections(); if (Sections.IsValidIndex(SectionIndex.SectionIndex)) { UMovieSceneSection* Section = Sections[SectionIndex.SectionIndex]; CallRemoveSectionAt(SectionIndex.SectionIndex); if (Section) { Section->OnRemovedFromTrack(); } } } void UMovieSceneTrack::OnAddedToMovieScene(UMovieScene* MovieScene) { OnAddedToMovieSceneImpl(MovieScene); // If this is being renamed into a movie scene structure, simulate re-addition of the // decorations to ensure any external references within the MovieScene are updated. for (UObject* Decoration : GetDecorations()) { if (IMovieSceneLifetimeDecoration* Lifetime = Cast(Decoration)) { Lifetime->OnReconstruct(MovieScene); } } for (UMovieSceneSection* Section : GetAllSections()) { for (UObject* Decoration : Section->GetDecorations()) { if (IMovieSceneLifetimeDecoration* Lifetime = Cast(Decoration)) { Lifetime->OnReconstruct(MovieScene); } } } } void UMovieSceneTrack::OnRemovedFromMovieScene() { OnRemovedFromMovieSceneImpl(); if (UMovieScene* MovieScene = GetTypedOuter()) { for (UObject* Decoration : GetDecorations()) { if (IMovieSceneLifetimeDecoration* Lifetime = Cast(Decoration)) { Lifetime->OnDestroy(MovieScene); } } for (UMovieSceneSection* Section : GetAllSections()) { for (UObject* Decoration : Section->GetDecorations()) { if (IMovieSceneLifetimeDecoration* Lifetime = Cast(Decoration)) { Lifetime->OnDestroy(MovieScene); } } } } } void UMovieSceneTrack::OnDecorationAdded(UObject* NewDecoration) { if (IMovieSceneTrackDecoration* DecorationInterface = Cast(NewDecoration)) { DecorationInterface->OnDecorationAdded(this); } } void UMovieSceneTrack::OnDecorationRemoved(UObject* Decoration) { if (IMovieSceneTrackDecoration* DecorationInterface = Cast(Decoration)) { DecorationInterface->OnDecorationRemoved(); } } bool UMovieSceneTrack::IsPostLoadThreadSafe() const { return true; } void UMovieSceneTrack::UpdateEasing() { int32 MaxRows = GetMaxRowIndex(); TArray RowSections; for (int32 RowIndex = 0; RowIndex <= MaxRows; ++RowIndex) { RowSections.Reset(); for (UMovieSceneSection* Section : GetAllSections()) { if (Section && Section->GetRowIndex() == RowIndex) { RowSections.Add(Section); } } for (int32 Index = 0; Index < RowSections.Num(); ++Index) { UMovieSceneSection* CurrentSection = RowSections[Index]; FMovieSceneSupportsEasingParams SupportsEasingParams(CurrentSection); EMovieSceneTrackEasingSupportFlags EasingFlags = SupportsEasing(SupportsEasingParams); // Auto-deactivate manual easing if we lost the ability to use it. if (!EnumHasAllFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseIn)) { CurrentSection->Easing.bManualEaseIn = false; } if (!EnumHasAllFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::ManualEaseOut)) { CurrentSection->Easing.bManualEaseOut = false; } if (!EnumHasAllFlags(EasingFlags, EMovieSceneTrackEasingSupportFlags::AutomaticEasing)) { if (CurrentSection->Easing.AutoEaseInDuration != 0 || CurrentSection->Easing.AutoEaseOutDuration != 0) { CurrentSection->Modify(); CurrentSection->Easing.AutoEaseInDuration = 0; CurrentSection->Easing.AutoEaseOutDuration = 0; } continue; } // Check overlaps with exclusive ranges so that sections can butt up against each other UMovieSceneTrack* OuterTrack = CurrentSection->GetTypedOuter(); int32 MaxEaseIn = 0; int32 MaxEaseOut = 0; bool bIsEntirelyUnderlapped = false; TRange CurrentSectionRange = CurrentSection->GetRange(); for (int32 OtherIndex = 0; OtherIndex < RowSections.Num(); ++OtherIndex) { if (OtherIndex == Index) { continue; } UMovieSceneSection* Other = RowSections[OtherIndex]; TRange OtherSectionRange = Other->GetRange(); if (!OtherSectionRange.HasLowerBound() && !OtherSectionRange.HasUpperBound()) { // If we're testing against an infinite range we want to use the PlayRange of the sequence // instead so that blends stop at the end of a clip instead of a quarter of the length. UMovieScene* OuterScene = OuterTrack->GetTypedOuter(); OtherSectionRange = OuterScene->GetPlaybackRange(); } bIsEntirelyUnderlapped = bIsEntirelyUnderlapped || OtherSectionRange.Contains(CurrentSectionRange); // Check the lower bound of the current section against the other section's upper bound const bool bSectionRangeContainsOtherUpperBound = !OtherSectionRange.GetUpperBound().IsOpen() && !CurrentSectionRange.GetLowerBound().IsOpen() && CurrentSectionRange.Contains(OtherSectionRange.GetUpperBoundValue()); const bool bSectionRangeContainsOtherLowerBound = !OtherSectionRange.GetLowerBound().IsOpen() && !CurrentSectionRange.GetUpperBound().IsOpen() && CurrentSectionRange.Contains(OtherSectionRange.GetLowerBoundValue()); if (bSectionRangeContainsOtherUpperBound && !bSectionRangeContainsOtherLowerBound) { const int32 Difference = UE::MovieScene::DiscreteSize(TRange(CurrentSectionRange.GetLowerBound(), OtherSectionRange.GetUpperBound())); MaxEaseIn = FMath::Max(MaxEaseIn, Difference); } if (bSectionRangeContainsOtherLowerBound &&!bSectionRangeContainsOtherUpperBound) { const int32 Difference = UE::MovieScene::DiscreteSize(TRange(OtherSectionRange.GetLowerBound(), CurrentSectionRange.GetUpperBound())); MaxEaseOut = FMath::Max(MaxEaseOut, Difference); } } const bool bIsFinite = CurrentSectionRange.HasLowerBound() && CurrentSectionRange.HasUpperBound(); const int32 MaxSize = bIsFinite ? UE::MovieScene::DiscreteSize(CurrentSectionRange) : TNumericLimits::Max(); if (MaxEaseOut == 0 && MaxEaseIn == 0 && bIsEntirelyUnderlapped) { MaxEaseOut = MaxEaseIn = MaxSize / 4; } // Only modify the section if the ease in or out times have actually changed MaxEaseIn = FMath::Clamp(MaxEaseIn, 0, MaxSize); MaxEaseOut = FMath::Clamp(MaxEaseOut, 0, MaxSize); if (CurrentSection->Easing.AutoEaseInDuration != MaxEaseIn || CurrentSection->Easing.AutoEaseOutDuration != MaxEaseOut) { CurrentSection->Modify(); CurrentSection->Easing.AutoEaseInDuration = MaxEaseIn; CurrentSection->Easing.AutoEaseOutDuration = MaxEaseOut; } } } } FMovieSceneTrackRowSegmentBlenderPtr UMovieSceneTrack::GetRowSegmentBlender() const { return FDefaultTrackRowSegmentBlender(); } FMovieSceneTrackSegmentBlenderPtr UMovieSceneTrack::GetTrackSegmentBlender() const { if (EvalOptions.bCanEvaluateNearestSection && EvalOptions.bEvalNearestSection) { return FEvaluateNearestSegmentBlender(); } else { return FMovieSceneTrackSegmentBlenderPtr(); } } int32 UMovieSceneTrack::GetMaxRowIndex() const { int32 MaxRowIndex = 0; for (UMovieSceneSection* Section : GetAllSections()) { MaxRowIndex = FMath::Max(MaxRowIndex, Section->GetRowIndex()); } return MaxRowIndex; } bool UMovieSceneTrack::FixRowIndices() { TMap NewToOldRowIndices; bool bFixesMade = false; TArray Sections = GetAllSections(); if (SupportsMultipleRows()) { // remove any empty track rows by waterfalling down sections to be as compact as possible TArray> RowIndexToSectionsMap; RowIndexToSectionsMap.AddZeroed(GetMaxRowIndex() + 1); for (UMovieSceneSection* Section : Sections) { RowIndexToSectionsMap[Section->GetRowIndex()].Add(Section); } int32 NewIndex = 0; for (const TArray& SectionsForIndex : RowIndexToSectionsMap) { if (SectionsForIndex.Num() > 0) { for (UMovieSceneSection* SectionForIndex : SectionsForIndex) { if (SectionForIndex->GetRowIndex() != NewIndex) { int32 OldIndex = SectionForIndex->GetRowIndex(); SectionForIndex->Modify(); SectionForIndex->SetRowIndex(NewIndex); NewToOldRowIndices.FindOrAdd(NewIndex, OldIndex); bFixesMade = true; } } ++NewIndex; } } // If there aren't multiple rows (ie. max row is 0), there shouldn't be any disabled rows either if (GetMaxRowIndex() == 0 && !RowsDisabled.IsEmpty()) { Modify(); RowsDisabled.Empty(); } } else { for (int32 i = 0; i < Sections.Num(); ++i) { if (Sections[i]->GetRowIndex() != 0) { Sections[i]->Modify(); Sections[i]->SetRowIndex(0); bFixesMade = true; } } } if (NewToOldRowIndices.Num()) { OnRowIndicesChanged(NewToOldRowIndices); } return bFixesMade; } void UMovieSceneTrack::OnRowIndicesChanged(const TMap& NewToOldRowIndices) { // Patch track row metadata TMap NewTrackRowMetadata; for (int32 NewRowIndex = 0; NewRowIndex <= GetMaxRowIndex(); ++NewRowIndex) { int32 IndexToCopy = NewRowIndex; if (const int32* OldRowIndex = NewToOldRowIndices.Find(NewRowIndex)) { IndexToCopy = *OldRowIndex; } if (const FMovieSceneTrackRowMetadata* Metadata = TrackRowMetadata.Find(IndexToCopy)) { NewTrackRowMetadata.Add(NewRowIndex, *Metadata); } } TrackRowMetadata = NewTrackRowMetadata; } #if WITH_EDITOR ECookOptimizationFlags UMovieSceneTrack::GetCookOptimizationFlags() const { if (RemoveMutedTracksOnCook() && IsEvalDisabled()) { return ECookOptimizationFlags::RemoveTrack; } return ECookOptimizationFlags::None; } void UMovieSceneTrack::RemoveForCook() { for (UMovieSceneSection* Section : GetAllSections()) { if (Section) { Section->RemoveForCook(); } } RemoveAllAnimationData(); } bool UMovieSceneTrack::RemoveMutedTracksOnCook() { return CVarMovieSceneRemoveMutedTracksOnCook->GetInt() != 0; } TArray UMovieSceneTrack::GetAllConditions() { TArray Conditions; if (ConditionContainer.Condition) { Conditions.Add(ConditionContainer.Condition); } for (TPair& TrackRowMetadataPair : TrackRowMetadata) { if (TrackRowMetadataPair.Value.ConditionContainer.Condition) { Conditions.Add(TrackRowMetadataPair.Value.ConditionContainer.Condition); } } for (UMovieSceneSection* Section : GetAllSections()) { if (Section->ConditionContainer.Condition) { Conditions.Add(Section->ConditionContainer.Condition); } } return Conditions; } #endif bool UMovieSceneTrack::IsEvalDisabled(const bool bInCheckLocal) const { return bIsEvalDisabled #if WITH_EDITORONLY_DATA || (bInCheckLocal && bIsLocalEvalDisabled) #endif ; } bool UMovieSceneTrack::IsRowEvalDisabled(const int32 InRowIndex, const bool bInCheckLocal) const { return RowsDisabled.Contains(InRowIndex) #if WITH_EDITORONLY_DATA || (bInCheckLocal && LocalRowsDisabled.Contains(InRowIndex)) #endif ; } void UMovieSceneTrack::SetRowEvalDisabled(const bool bInEvalDisabled, const int32 InRowIndex) { if (bInEvalDisabled) { RowsDisabled.AddUnique(InRowIndex); } else { RowsDisabled.Remove(InRowIndex); } } #if WITH_EDITOR bool UMovieSceneTrack::IsLocalRowEvalDisabled(const int32 InRowIndex) const { return LocalRowsDisabled.Contains(InRowIndex); } void UMovieSceneTrack::SetLocalRowEvalDisabled(const bool bInEvalDisabled, const int32 InRowIndex) { if (bInEvalDisabled) { LocalRowsDisabled.AddUnique(InRowIndex); } else { LocalRowsDisabled.Remove(InRowIndex); } } #endif const FMovieSceneTrackRowMetadata* UMovieSceneTrack::FindTrackRowMetadata(int32 RowIndex) const { return TrackRowMetadata.Find(RowIndex); } FMovieSceneTrackRowMetadata* UMovieSceneTrack::FindTrackRowMetadata(int32 RowIndex) { return TrackRowMetadata.Find(RowIndex); } FMovieSceneTrackRowMetadata& UMovieSceneTrack::FindOrAddTrackRowMetadata(int32 RowIndex) { return TrackRowMetadata.FindOrAdd(RowIndex); } FGuid UMovieSceneTrack::FindObjectBindingGuid() const { const UMovieScene* MovieScene = GetTypedOuter(); if (MovieScene) { for (const FMovieSceneBinding& Binding : MovieScene->GetBindings()) { if (Binding.GetTracks().Contains(this)) { return Binding.GetObjectGuid(); } } } return FGuid(); } void UMovieSceneTrack::AddSectionRangesToTree(TArrayView Sections, TMovieSceneEvaluationTree& OutTree) { if (PopulateEvaluationTree(OutTree)) { return; } ETreePopulationMode ModeToUse = BuiltInTreePopulationMode; if (!ensureMsgf(ModeToUse != ETreePopulationMode::None, TEXT("No default tree population mode specified, and no PopulateEvaluationTree implemented - falling back to high-pass-per-row population."))) { ModeToUse = ETreePopulationMode::HighPassPerRow; } switch (ModeToUse) { case ETreePopulationMode::Blended: UE::MovieScene::FEvaluationTreePopulationRules::Blended(Sections, OutTree); break; case ETreePopulationMode::HighPass: UE::MovieScene::FEvaluationTreePopulationRules::HighPass(Sections, OutTree); break; case ETreePopulationMode::HighPassPerRow: UE::MovieScene::FEvaluationTreePopulationRules::HighPassPerRow(Sections, OutTree); break; } } void UMovieSceneTrack::AddSectionPrePostRollRangesToTree(TArrayView Sections, TMovieSceneEvaluationTree& OutTree) { // Always add pre and postroll ranges, regardless for (UMovieSceneSection* Section : Sections) { if (Section && Section->IsActive()) { const TRange SectionRange = Section->GetRange(); if (!SectionRange.IsEmpty()) { if (!SectionRange.GetLowerBound().IsOpen() && Section->GetPreRollFrames() > 0) { TRange PreRollRange = UE::MovieScene::MakeDiscreteRangeFromUpper(TRangeBound::FlipInclusion(SectionRange.GetLowerBoundValue()), Section->GetPreRollFrames()); OutTree.Add(PreRollRange, FMovieSceneTrackEvaluationData::FromSection(Section).SetFlags(ESectionEvaluationFlags::PreRoll)); } if (!SectionRange.GetUpperBound().IsOpen() && Section->GetPostRollFrames() > 0) { TRange PostRollRange = UE::MovieScene::MakeDiscreteRangeFromLower(TRangeBound(SectionRange.GetUpperBoundValue()), Section->GetPostRollFrames()); OutTree.Add(PostRollRange, FMovieSceneTrackEvaluationData::FromSection(Section).SetFlags(ESectionEvaluationFlags::PostRoll)); } } } } } void UMovieSceneTrack::PreCompile(FMovieSceneTrackPreCompileResult& OutPreCompileResult) { PreCompileImpl(OutPreCompileResult); } const FMovieSceneTrackEvaluationField& UMovieSceneTrack::GetEvaluationField() { if (EvaluationFieldGuid != GetSignature() #if WITH_EDITORONLY_DATA || EvaluationFieldVersion != GetEvaluationFieldVersion() #endif ) { UpdateEvaluationTree(); } return EvaluationField; } void UMovieSceneTrack::ForceUpdateEvaluationTree() { UpdateEvaluationTree(); } void UMovieSceneTrack::UpdateEvaluationTree() { TMovieSceneEvaluationTree EvaluationTree; TArray Sections = GetAllSections(); AddSectionRangesToTree(Sections, EvaluationTree); if (EvalOptions.bCanEvaluateNearestSection && EvalOptions.bEvalNearestSection) { UE::MovieScene::FEvaluationTreePopulationRules::PopulateNearestSection(Sections, EvaluationTree); } AddSectionPrePostRollRangesToTree(Sections, EvaluationTree); EvaluationField.Reset(); TMap> SectionToEntry; for (FMovieSceneEvaluationTreeRangeIterator It(EvaluationTree); It; ++It) { TRange Range = It.Range(); TMovieSceneEvaluationTreeDataIterator TrackDataIt = EvaluationTree.GetAllData(It.Node()); if (TrackDataIt) { for (const FMovieSceneTrackEvaluationData& TrackData : TrackDataIt) { UMovieSceneSection* Section = TrackData.Section.Get(); SectionToEntry.FindOrAdd(Section).Add(FMovieSceneTrackEvaluationFieldEntry{decltype(FMovieSceneTrackEvaluationFieldEntry::Section)(Section), Range, TrackData.ForcedTime, TrackData.Flags, TrackData.SortOrder }); } } else { // Add an eplicit entry for null, signifying the track itself, even though there are no sections at this time //SectionToEntry.FindOrAdd(nullptr).Add(FMovieSceneTrackEvaluationFieldEntry{ Range, ESectionEvaluationFlags::None, 0 }); } } for (TTuple>& Pair : SectionToEntry) { int32 NumEntries = Pair.Value.Num(); for (int32 Index = 0; Index < NumEntries; ++Index) { FMovieSceneTrackEvaluationFieldEntry* PredicateEntry = &Pair.Value[Index]; int32 StartIndex = Index; while (Index < NumEntries-1) { const FMovieSceneTrackEvaluationFieldEntry& SubsequentEntry = Pair.Value[Index+1]; if (SubsequentEntry.Range.Adjoins(PredicateEntry->Range) && SubsequentEntry.Flags == PredicateEntry->Flags && SubsequentEntry.ForcedTime == PredicateEntry->ForcedTime) { PredicateEntry->Range.SetUpperBound(SubsequentEntry.Range.GetUpperBound()); ++Index; continue; } break; } int32 NumToConsolidate = Index - StartIndex; if (NumToConsolidate > 0) { Pair.Value.RemoveAt(StartIndex + 1, NumToConsolidate, EAllowShrinking::No); NumEntries -= NumToConsolidate; } } // @todo: Do we need to handle with empty track segments? if (Pair.Value.Num() > 0) { EvaluationField.Entries.Append(Pair.Value); } } EvaluationFieldGuid = GetSignature(); #if WITH_EDITORONLY_DATA EvaluationFieldVersion = GetEvaluationFieldVersion(); #endif }