// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneSection.h" #include "MovieScene.h" #include "MovieSceneTrack.h" #include "MovieSceneSequence.h" #include "MovieSceneCommonHelpers.h" #include "MovieSceneTimeHelpers.h" #include "Evaluation/MovieSceneEvalTemplate.h" #include "EntitySystem/MovieSceneEntityManager.h" #include "Generators/MovieSceneEasingCurves.h" #include "Channels/MovieSceneChannelProxy.h" #include "EntitySystem/IMovieSceneEntityProvider.h" #include "EntitySystem/BuiltInComponentTypes.h" #include "EntitySystem/MovieSceneEntitySystemTask.h" #include "EntitySystem/MovieSceneEntitySystemLinker.h" #include "Containers/ArrayView.h" #include "Channels/MovieSceneChannel.h" #include "UObject/SequencerObjectVersion.h" #include "Misc/FeedbackContext.h" UMovieSceneSection::UMovieSceneSection(const FObjectInitializer& ObjectInitializer) : Super( ObjectInitializer ) , PreRollFrames(0) , PostRollFrames(0) , RowIndex(0) , OverlapPriority(0) , bIsActive(true) , bIsLocked(false) , StartTime_DEPRECATED(0.f) , EndTime_DEPRECATED(0.f) , PreRollTime_DEPRECATED(0.f) , PostRollTime_DEPRECATED(0.f) , bIsInfinite_DEPRECATED(0) , bSupportsInfiniteRange(false) { SectionRange.Value = TRange(0); UMovieSceneBuiltInEasingFunction* DefaultEaseIn = ObjectInitializer.CreateDefaultSubobject(this, "EaseInFunction"); DefaultEaseIn->SetFlags(RF_Public); //@todo Need to be marked public. GLEO occurs when transform sections are added to actor sequence blueprints. Are these not being duplicated properly? DefaultEaseIn->Type = EMovieSceneBuiltInEasing::CubicInOut; Easing.EaseIn = DefaultEaseIn; UMovieSceneBuiltInEasingFunction* DefaultEaseOut = ObjectInitializer.CreateDefaultSubobject(this, "EaseOutFunction"); DefaultEaseOut->SetFlags(RF_Public); //@todo Need to be marked public. GLEO occurs when transform sections are added to actor sequence blueprints. Are these not being duplicated properly? DefaultEaseOut->Type = EMovieSceneBuiltInEasing::CubicInOut; Easing.EaseOut = DefaultEaseOut; ChannelProxyType = EMovieSceneChannelProxyType::Static; } void UMovieSceneSection::PostInitProperties() { SetFlags(RF_Transactional); // Propagate sub object flags from our outer (track) to ourselves. This is required for sections 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(); } bool UMovieSceneSection::IsPostLoadThreadSafe() const { return true; } void UMovieSceneSection::PostEditImport() { if (ChannelProxyType == EMovieSceneChannelProxyType::Dynamic) { ChannelProxy = nullptr; } Super::PostEditImport(); } void UMovieSceneSection::Serialize(FArchive& Ar) { using namespace UE::MovieScene; if (Ar.IsLoading() && ChannelProxyType == EMovieSceneChannelProxyType::Dynamic) { ChannelProxy = nullptr; } Super::Serialize(Ar); Ar.UsingCustomVersion(FSequencerObjectVersion::GUID); if (Ar.CustomVer(FSequencerObjectVersion::GUID) < FSequencerObjectVersion::FloatToIntConversion) { const FFrameRate LegacyFrameRate = GetLegacyConversionFrameRate(); if (bIsInfinite_DEPRECATED && bSupportsInfiniteRange) { SectionRange = TRange::All(); } else { FFrameNumber StartFrame = UpgradeLegacyMovieSceneTime(this, LegacyFrameRate, StartTime_DEPRECATED); FFrameNumber LastFrame = UpgradeLegacyMovieSceneTime(this, LegacyFrameRate, EndTime_DEPRECATED); // Exclusive upper bound so we want the upper bound to be exclusively the next frame after LastFrame SectionRange = TRange(StartFrame, LastFrame + 1); } // All these times are offsets from the start/end time so it's highly unlikely that they'll be out-of bounds PreRollFrames = LegacyFrameRate.AsFrameNumber(PreRollTime_DEPRECATED).Value; PostRollFrames = LegacyFrameRate.AsFrameNumber(PostRollTime_DEPRECATED).Value; #if WITH_EDITORONLY_DATA Easing.AutoEaseInDuration = (Easing.AutoEaseInTime_DEPRECATED * LegacyFrameRate).RoundToFrame().Value; Easing.AutoEaseOutDuration = (Easing.AutoEaseOutTime_DEPRECATED * LegacyFrameRate).RoundToFrame().Value; Easing.ManualEaseInDuration = (Easing.ManualEaseInTime_DEPRECATED * LegacyFrameRate).RoundToFrame().Value; Easing.ManualEaseOutDuration = (Easing.ManualEaseOutTime_DEPRECATED * LegacyFrameRate).RoundToFrame().Value; #endif } } void UMovieSceneSection::PostDuplicate(bool bDuplicateForPIE) { if (ChannelProxyType == EMovieSceneChannelProxyType::Dynamic) { ChannelProxy = nullptr; } Super::PostDuplicate(bDuplicateForPIE); } void UMovieSceneSection::PostRename(UObject* OldOuter, const FName OldName) { if (ChannelProxyType == EMovieSceneChannelProxyType::Dynamic) { ChannelProxy = nullptr; } Super::PostRename(OldOuter, OldName); } void UMovieSceneSection::SetStartFrame(TRangeBound NewStartFrame) { if (TryModify()) { bool bIsValidStartFrame = ensureMsgf(SectionRange.Value.GetUpperBound().IsOpen() || NewStartFrame.IsOpen() || SectionRange.Value.GetUpperBound().GetValue() >= NewStartFrame.GetValue(), TEXT("Invalid start frame specified; will be clamped to current end frame.")); if (bIsValidStartFrame) { SectionRange.Value.SetLowerBound(NewStartFrame); } else { SectionRange.Value.SetLowerBound(TRangeBound::FlipInclusion(SectionRange.Value.GetUpperBound())); } } } void UMovieSceneSection::SetEndFrame(TRangeBound NewEndFrame) { if (TryModify()) { bool bIsValidEndFrame = ensureMsgf(SectionRange.Value.GetLowerBound().IsOpen() || NewEndFrame.IsOpen() || SectionRange.Value.GetLowerBound().GetValue() <= NewEndFrame.GetValue(), TEXT("Invalid end frame specified; will be clamped to current start frame.")); if (bIsValidEndFrame) { SectionRange.Value.SetUpperBound(NewEndFrame); } else { SectionRange.Value.SetUpperBound(TRangeBound::FlipInclusion(SectionRange.Value.GetLowerBound())); } } } FMovieSceneChannelProxy& UMovieSceneSection::GetChannelProxy() const { if (!ChannelProxy.IsValid()) { ChannelProxyType = const_cast(this)->CacheChannelProxy(); } FMovieSceneChannelProxy* Proxy = ChannelProxy.Get(); check(Proxy); return *Proxy; } EMovieSceneChannelProxyType UMovieSceneSection::CacheChannelProxy() { ChannelProxy = MakeShared(); return EMovieSceneChannelProxyType::Static; } TSharedPtr UMovieSceneSection::GetKeyStruct(TArrayView KeyHandles) { return nullptr; } void UMovieSceneSection::MoveSection(FFrameNumber DeltaFrame) { if (TryModify()) { TRange NewRange = SectionRange.Value; if (SectionRange.Value.GetLowerBound().IsClosed()) { SectionRange.Value.SetLowerBoundValue(SectionRange.Value.GetLowerBoundValue() + DeltaFrame); } if (SectionRange.Value.GetUpperBound().IsClosed()) { SectionRange.Value.SetUpperBoundValue(SectionRange.Value.GetUpperBoundValue() + DeltaFrame); } for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries()) { for (FMovieSceneChannel* Channel : Entry.GetChannels()) { Channel->Offset(DeltaFrame); } } } } TRange UMovieSceneSection::ComputeEffectiveRange() const { if (!SectionRange.Value.GetLowerBound().IsOpen() && !SectionRange.Value.GetUpperBound().IsOpen()) { return GetRange(); } TRange EffectiveRange = TRange::Empty(); for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries()) { for (const FMovieSceneChannel* Channel : Entry.GetChannels()) { EffectiveRange = TRange::Hull(EffectiveRange, Channel->ComputeEffectiveRange()); } } return TRange::Intersection(EffectiveRange, SectionRange.Value); } TOptional > UMovieSceneSection::GetAutoSizeRange() const { TRange EffectiveRange = TRange::Empty(); for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries()) { for (const FMovieSceneChannel* Channel : Entry.GetChannels()) { EffectiveRange = TRange::Hull(EffectiveRange, Channel->ComputeEffectiveRange()); } } if (!EffectiveRange.IsEmpty()) { return EffectiveRange; } return TOptional >(); } FMovieSceneBlendTypeField UMovieSceneSection::GetSupportedBlendTypes() const { UMovieSceneTrack* Track = GetTypedOuter(); return Track ? Track->GetSupportedBlendTypes() : FMovieSceneBlendTypeField::None(); } void UMovieSceneSection::BuildDefaultComponents(UMovieSceneEntitySystemLinker* EntityLinker, const UE::MovieScene::FEntityImportParams& Params, UE::MovieScene::FImportedEntity* OutImportedEntity) { using namespace UE::MovieScene; FBuiltInComponentTypes* Components = FBuiltInComponentTypes::Get(); FComponentTypeID BlendTag; if (BlendType.IsValid()) { if (BlendType.Get() == EMovieSceneBlendType::Absolute) { BlendTag = Components->Tags.AbsoluteBlend; } else if (BlendType.Get() == EMovieSceneBlendType::Relative) { BlendTag = Components->Tags.RelativeBlend; } else if (BlendType.Get() == EMovieSceneBlendType::Additive) { BlendTag = Components->Tags.AdditiveBlend; } else if (BlendType.Get() == EMovieSceneBlendType::AdditiveFromBase) { BlendTag = Components->Tags.AdditiveFromBaseBlend; } } const bool bHasEasing = (Easing.GetEaseInDuration() > 0 || Easing.GetEaseOutDuration() > 0); const bool bShouldRestoreState = (EvalOptions.CompletionMode == EMovieSceneCompletionMode::RestoreState) || ( EvalOptions.CompletionMode == EMovieSceneCompletionMode::ProjectDefault && Params.Sequence.DefaultCompletionMode == EMovieSceneCompletionMode::RestoreState); TComponentTypeID EasingComponentID = Components->Easing; FComponentTypeID RestoreStateTag = Components->Tags.RestoreState; const bool bHasForcedTime = Params.EntityMetaData && Params.EntityMetaData->ForcedTime != TNumericLimits::Lowest(); const bool bHasSectionPreRoll = Params.EntityMetaData && EnumHasAnyFlags(Params.EntityMetaData->Flags, ESectionEvaluationFlags::PreRoll | ESectionEvaluationFlags::PostRoll); const bool bHasSequencePreRoll = Params.Sequence.bPreRoll || Params.Sequence.bPostRoll; OutImportedEntity->AddBuilder( FEntityBuilder() .AddConditional(Components->Easing, FEasingComponentData{ decltype(FEasingComponentData::Section)(this) }, bHasEasing) .AddConditional(Components->HierarchicalEasingChannel, uint16(-1), Params.Sequence.bHasHierarchicalEasing) .AddConditional(Components->HierarchicalBias, Params.Sequence.HierarchicalBias, Params.Sequence.HierarchicalBias != 0) .AddConditional(Components->Interrogation.InputKey, Params.InterrogationKey, Params.InterrogationKey.IsValid()) .AddConditional(Components->EvalTime, Params.EntityMetaData ? Params.EntityMetaData->ForcedTime : 0, bHasForcedTime) .AddTagConditional(Components->Tags.RestoreState, bShouldRestoreState) .AddTagConditional(Components->Tags.FixedTime, bHasForcedTime) .AddTagConditional(Components->Tags.SectionPreRoll, bHasSectionPreRoll) .AddTagConditional(Components->Tags.PreRoll, bHasSequencePreRoll || bHasSectionPreRoll) .AddTagConditional(BlendTag, BlendTag != FComponentTypeID::Invalid()) ); if (BlendTag == Components->Tags.AdditiveFromBaseBlend) { const UMovieScene* MovieScene = GetTypedOuter(); const TRange PlaybackRange = MovieScene->GetPlaybackRange(); const TRange TrueRange = GetTrueRange(); const FFrameNumber BaseValueEvalTime = TrueRange.HasLowerBound() ? TrueRange.GetLowerBoundValue() : (PlaybackRange.HasLowerBound() ? PlaybackRange.GetLowerBoundValue() : FFrameNumber(0)); OutImportedEntity->AddBuilder( FEntityBuilder().Add(Components->BaseValueEvalTime, BaseValueEvalTime) ); } } bool UMovieSceneSection::TryModify(bool bAlwaysMarkDirty) { if (IsReadOnly()) { return false; } Modify(bAlwaysMarkDirty); return true; } bool UMovieSceneSection::IsReadOnly() const { if (IsLocked()) { return true; } #if WITH_EDITORONLY_DATA if (UMovieScene* OuterScene = GetTypedOuter()) { if (OuterScene->IsReadOnly()) { return true; } } #endif return false; } void UMovieSceneSection::SetRowIndex(int32 NewRowIndex) { const int32 OldRowIndex = RowIndex; RowIndex = NewRowIndex; if (OldRowIndex != RowIndex) { EventHandlers.Trigger(&UE::MovieScene::ISectionEventHandler::OnRowChanged, this); } } void UMovieSceneSection::GetOverlappingSections(TArray& OutSections, bool bSameRow, bool bIncludeThis) { UMovieSceneTrack* Track = GetTypedOuter(); if (!Track) { return; } TRange ThisRange = GetRange(); for (UMovieSceneSection* Section : Track->GetAllSections()) { if (!Section || (!bIncludeThis && Section == this)) { continue; } if (bSameRow && Section->GetRowIndex() != GetRowIndex()) { continue; } if (Section->GetRange().Overlaps(ThisRange)) { OutSections.Add(Section); } } } const UMovieSceneSection* UMovieSceneSection::OverlapsWithSections(const TArray& Sections, int32 TrackDelta, int32 TimeDelta) const { // Check overlaps with exclusive ranges so that sections can butt up against each other int32 NewTrackIndex = RowIndex + TrackDelta; // @todo: sequencer-timecode: is this correct? it seems like we should just use the section's ranges directly rather than fiddling with the bounds // TRange NewSectionRange; // if (SectionRange.GetLowerBound().IsClosed()) // { // NewSectionRange = TRange( // TRangeBound::Exclusive(SectionRange.GetLowerBoundValue() + TimeDelta), // NewSectionRange.GetUpperBound() // ); // } // if (SectionRange.GetUpperBound().IsClosed()) // { // NewSectionRange = TRange( // NewSectionRange.GetLowerBound(), // TRangeBound::Exclusive(SectionRange.GetUpperBoundValue() + TimeDelta) // ); // } TRange ThisRange = SectionRange.Value; for (const auto Section : Sections) { check(Section); if ((this != Section) && (Section->GetRowIndex() == NewTrackIndex)) { //TRange ExclusiveSectionRange = TRange(TRange::BoundsType::Exclusive(Section->GetRange().GetLowerBoundValue()), TRange::BoundsType::Exclusive(Section->GetRange().GetUpperBoundValue())); if (ThisRange.Overlaps(Section->GetRange())) { return Section; } } } return nullptr; } void UMovieSceneSection::InitialPlacement(const TArray& Sections, FFrameNumber InStartTime, int32 Duration, bool bAllowMultipleRows) { check(Duration >= 0); // Inclusive lower, exclusive upper bounds SectionRange = TRange(InStartTime, InStartTime + Duration); RowIndex = 0; for (UMovieSceneSection* OtherSection : Sections) { OverlapPriority = FMath::Max(OtherSection->GetOverlapPriority()+1, OverlapPriority); } if (bAllowMultipleRows) { while (OverlapsWithSections(Sections) != nullptr) { ++RowIndex; } } else { for (;;) { const UMovieSceneSection* OverlappedSection = OverlapsWithSections(Sections); if (OverlappedSection == nullptr) { break; } TRange OtherRange = OverlappedSection->GetRange(); if (OtherRange.GetUpperBound().IsClosed()) { MoveSection(OtherRange.GetUpperBoundValue() - InStartTime); } else { ++OverlapPriority; break; } } } UMovieSceneTrack* Track = GetTypedOuter(); if (Track) { Track->UpdateEasing(); } } void UMovieSceneSection::InitialPlacementOnRow(const TArray& Sections, FFrameNumber InStartTime, int32 Duration, int32 InRowIndex) { check(Duration >= 0); // Inclusive lower, exclusive upper bounds SectionRange = TRange(InStartTime, InStartTime + Duration); RowIndex = InRowIndex; // If no given row index, put it on the next available row if (RowIndex == INDEX_NONE) { RowIndex = 0; while (OverlapsWithSections(Sections) != nullptr) { ++RowIndex; } } for (UMovieSceneSection* OtherSection : Sections) { OverlapPriority = FMath::Max(OtherSection->GetOverlapPriority()+1, OverlapPriority); } // If this overlaps with any sections, move out all the sections that are beyond this row if (OverlapsWithSections(Sections)) { for (UMovieSceneSection* OtherSection : Sections) { if (OtherSection != nullptr && OtherSection != this && OtherSection->GetRowIndex() >= RowIndex) { OtherSection->SetRowIndex(OtherSection->GetRowIndex()+1); } } } UMovieSceneTrack* Track = GetTypedOuter(); if (Track) { Track->UpdateEasing(); } } UMovieSceneSection* UMovieSceneSection::SplitSection(FQualifiedFrameTime SplitTime, bool bDeleteKeys) { if (!SectionRange.Value.Contains(SplitTime.Time.GetFrame())) { return nullptr; } SetFlags(RF_Transactional); if (TryModify()) { // Duplicate the current section to be the section on the right side of the trim point UMovieSceneTrack* Track = CastChecked(GetOuter()); Track->Modify(); UMovieSceneSection* NewSection = DuplicateObject(this, Track); check(NewSection); Track->AddSection(*NewSection); TrimSection(SplitTime, false, bDeleteKeys); Easing.AutoEaseOutDuration = 0; Easing.bManualEaseOut = false; Easing.ManualEaseOutDuration = 0; NewSection->TrimSection(SplitTime, true, bDeleteKeys); NewSection->Easing.AutoEaseInDuration = 0; NewSection->Easing.bManualEaseIn = false; NewSection->Easing.ManualEaseInDuration = 0; return NewSection; } return nullptr; } UObject* UMovieSceneSection::GetImplicitObjectOwner() { if (UMovieSceneTrack* Track = GetTypedOuter()) { if (UMovieScene* MovieScene = Track->GetTypedOuter()) { FGuid Guid; if (MovieScene->FindTrackBinding(*Track, Guid)) { if (FMovieSceneSpawnable* MovieSceneSpanwable = MovieScene->FindSpawnable(Guid)) { return MovieSceneSpanwable->GetObjectTemplate(); } else if (FMovieScenePossessable* MovieScenePossessable = MovieScene->FindPossessable(Guid)) { #if WITH_EDITORONLY_DATA if (MovieScenePossessable->GetPossessedObjectClass() && MovieScenePossessable->GetPossessedObjectClass()->GetDefaultObject()) { return MovieScenePossessable->GetPossessedObjectClass()->GetDefaultObject(); } #endif } } } } return nullptr; } void UMovieSceneSection::TrimSection(FQualifiedFrameTime TrimTime, bool bTrimLeft, bool bDeleteKeys) { if (SectionRange.Value.Contains(TrimTime.Time.GetFrame())) { SetFlags(RF_Transactional); if (TryModify()) { if (bTrimLeft) { SectionRange.Value.SetLowerBound(TRangeBound::Inclusive(TrimTime.Time.GetFrame())); } else { SectionRange.Value.SetUpperBound(TRangeBound::Exclusive(TrimTime.Time.GetFrame())); } if (bDeleteKeys) { for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries()) { for (FMovieSceneChannel* Channel : Entry.GetChannels()) { Channel->DeleteKeysFrom(TrimTime.Time.GetFrame(), bTrimLeft); } } } } } } float UMovieSceneSection::EvaluateEasing(FFrameTime InTime) const { float EaseInValue = 1.f; float EaseOutValue = 1.f; if (HasStartFrame() && Easing.GetEaseInDuration() > 0 && Easing.EaseIn.GetObject()) { const int32 EaseFrame = (InTime.FrameNumber - GetInclusiveStartFrame()).Value; const double EaseInInterp = (double(EaseFrame) + InTime.GetSubFrame()) / Easing.GetEaseInDuration(); if (EaseInInterp <= 0.0) { EaseInValue = 0.0; } else if (EaseInInterp >= 1.0) { EaseInValue = 1.0; } else { EaseInValue = IMovieSceneEasingFunction::EvaluateWith(Easing.EaseIn, EaseInInterp); } } if (HasEndFrame() && Easing.GetEaseOutDuration() > 0 && Easing.EaseOut.GetObject()) { const int32 EaseFrame = (InTime.FrameNumber - GetExclusiveEndFrame() + Easing.GetEaseOutDuration()).Value; const double EaseOutInterp = (double(EaseFrame) + InTime.GetSubFrame()) / Easing.GetEaseOutDuration(); if (EaseOutInterp <= 0.0) { EaseOutValue = 1.0; } else if (EaseOutInterp >= 1.0) { EaseOutValue = 0.0; } else { EaseOutValue = 1.f - IMovieSceneEasingFunction::EvaluateWith(Easing.EaseOut, EaseOutInterp); } } return EaseInValue * EaseOutValue; } void UMovieSceneSection::EvaluateEasing(FFrameTime InTime, TOptional& OutEaseInValue, TOptional& OutEaseOutValue, float* OutEaseInInterp, float* OutEaseOutInterp) const { if (HasStartFrame() && Easing.EaseIn.GetObject() && GetEaseInRange().Contains(InTime.FrameNumber)) { const int32 EaseFrame = (InTime.FrameNumber - GetInclusiveStartFrame()).Value; const double EaseInInterp = (double(EaseFrame) + InTime.GetSubFrame()) / Easing.GetEaseInDuration(); OutEaseInValue = IMovieSceneEasingFunction::EvaluateWith(Easing.EaseIn, EaseInInterp); if (OutEaseInInterp) { *OutEaseInInterp = EaseInInterp; } } if (HasEndFrame() && Easing.EaseOut.GetObject() && GetEaseOutRange().Contains(InTime.FrameNumber)) { const int32 EaseFrame = (InTime.FrameNumber - GetExclusiveEndFrame() + Easing.GetEaseOutDuration()).Value; const double EaseOutInterp = (double(EaseFrame) + InTime.GetSubFrame()) / Easing.GetEaseOutDuration(); OutEaseOutValue = 1.f - IMovieSceneEasingFunction::EvaluateWith(Easing.EaseOut, EaseOutInterp); if (OutEaseOutInterp) { *OutEaseOutInterp = EaseOutInterp; } } } TRange UMovieSceneSection::GetEaseInRange() const { if (HasStartFrame() && Easing.GetEaseInDuration() > 0) { TRangeBound LowerBound = TRangeBound::Inclusive(GetInclusiveStartFrame()); TRangeBound UpperBound = TRangeBound::Exclusive(GetInclusiveStartFrame() + Easing.GetEaseInDuration()); UpperBound = TRangeBound::MinUpper(UpperBound, SectionRange.Value.GetUpperBound()); return TRange(LowerBound, UpperBound); } return TRange::Empty(); } TRange UMovieSceneSection::GetEaseOutRange() const { if (HasEndFrame() && Easing.GetEaseOutDuration() > 0) { TRangeBound UpperBound = TRangeBound::Exclusive(GetExclusiveEndFrame()); TRangeBound LowerBound = TRangeBound::Inclusive(GetExclusiveEndFrame() - Easing.GetEaseOutDuration()); LowerBound = TRangeBound::MaxLower(LowerBound, SectionRange.Value.GetLowerBound()); return TRange(LowerBound, UpperBound); } return TRange::Empty(); } bool UMovieSceneSection::ShouldUpgradeEntityData(FArchive& Ar, FMovieSceneEvaluationCustomVersion::Type UpgradeVersion) const { return Ar.IsLoading() && !Ar.HasAnyPortFlags(PPF_Duplicate | PPF_DuplicateForPIE) && GetLinkerCustomVersion(FMovieSceneEvaluationCustomVersion::GUID) < UpgradeVersion; } #if WITH_EDITOR void UMovieSceneSection::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { static const FName NAME_SectionRange = GET_MEMBER_NAME_CHECKED(UMovieSceneSection, SectionRange); Super::PostEditChangeProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.Property->GetFName() == NAME_SectionRange) { if (UMovieSceneTrack* Track = GetTypedOuter()) { Track->UpdateEasing(); } } } ECookOptimizationFlags UMovieSceneSection::GetCookOptimizationFlags() const { UMovieSceneTrack* Track = GetTypedOuter(); if (UMovieSceneTrack::RemoveMutedTracksOnCook() && Track && Track->IsRowEvalDisabled(GetRowIndex())) { return ECookOptimizationFlags::RemoveSection; } return ECookOptimizationFlags::None; } void UMovieSceneSection::RemoveForCook() { Modify(); for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries()) { for (FMovieSceneChannel* Channel : Entry.GetChannels()) { if (Channel) { Channel->Reset(); } } } } #endif