Files
UnrealEngineUWP/Engine/Source/Runtime/MovieScene/Private/MovieSceneSection.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

798 lines
25 KiB
C++

// 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<FFrameNumber>(0);
UMovieSceneBuiltInEasingFunction* DefaultEaseIn = ObjectInitializer.CreateDefaultSubobject<UMovieSceneBuiltInEasingFunction>(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<UMovieSceneBuiltInEasingFunction>(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<FFrameNumber>::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<FFrameNumber>(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<FFrameNumber> 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<FFrameNumber>::FlipInclusion(SectionRange.Value.GetUpperBound()));
}
}
}
void UMovieSceneSection::SetEndFrame(TRangeBound<FFrameNumber> 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<FFrameNumber>::FlipInclusion(SectionRange.Value.GetLowerBound()));
}
}
}
FMovieSceneChannelProxy& UMovieSceneSection::GetChannelProxy() const
{
if (!ChannelProxy.IsValid())
{
ChannelProxyType = const_cast<UMovieSceneSection*>(this)->CacheChannelProxy();
}
FMovieSceneChannelProxy* Proxy = ChannelProxy.Get();
check(Proxy);
return *Proxy;
}
EMovieSceneChannelProxyType UMovieSceneSection::CacheChannelProxy()
{
ChannelProxy = MakeShared<FMovieSceneChannelProxy>();
return EMovieSceneChannelProxyType::Static;
}
TSharedPtr<FStructOnScope> UMovieSceneSection::GetKeyStruct(TArrayView<const FKeyHandle> KeyHandles)
{
return nullptr;
}
void UMovieSceneSection::MoveSection(FFrameNumber DeltaFrame)
{
if (TryModify())
{
TRange<FFrameNumber> 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<FFrameNumber> UMovieSceneSection::ComputeEffectiveRange() const
{
if (!SectionRange.Value.GetLowerBound().IsOpen() && !SectionRange.Value.GetUpperBound().IsOpen())
{
return GetRange();
}
TRange<FFrameNumber> EffectiveRange = TRange<FFrameNumber>::Empty();
for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries())
{
for (const FMovieSceneChannel* Channel : Entry.GetChannels())
{
EffectiveRange = TRange<FFrameNumber>::Hull(EffectiveRange, Channel->ComputeEffectiveRange());
}
}
return TRange<FFrameNumber>::Intersection(EffectiveRange, SectionRange.Value);
}
TOptional<TRange<FFrameNumber> > UMovieSceneSection::GetAutoSizeRange() const
{
TRange<FFrameNumber> EffectiveRange = TRange<FFrameNumber>::Empty();
for (const FMovieSceneChannelEntry& Entry : GetChannelProxy().GetAllEntries())
{
for (const FMovieSceneChannel* Channel : Entry.GetChannels())
{
EffectiveRange = TRange<FFrameNumber>::Hull(EffectiveRange, Channel->ComputeEffectiveRange());
}
}
if (!EffectiveRange.IsEmpty())
{
return EffectiveRange;
}
return TOptional<TRange<FFrameNumber> >();
}
FMovieSceneBlendTypeField UMovieSceneSection::GetSupportedBlendTypes() const
{
UMovieSceneTrack* Track = GetTypedOuter<UMovieSceneTrack>();
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<FEasingComponentData> EasingComponentID = Components->Easing;
FComponentTypeID RestoreStateTag = Components->Tags.RestoreState;
const bool bHasForcedTime = Params.EntityMetaData && Params.EntityMetaData->ForcedTime != TNumericLimits<int32>::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<UMovieScene>();
const TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
const TRange<FFrameNumber> 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<UMovieScene>())
{
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<UMovieSceneSection*>& OutSections, bool bSameRow, bool bIncludeThis)
{
UMovieSceneTrack* Track = GetTypedOuter<UMovieSceneTrack>();
if (!Track)
{
return;
}
TRange<FFrameNumber> 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<UMovieSceneSection*>& 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<FFrameNumber> NewSectionRange;
// if (SectionRange.GetLowerBound().IsClosed())
// {
// NewSectionRange = TRange<FFrameNumber>(
// TRangeBound<FFrameNumber>::Exclusive(SectionRange.GetLowerBoundValue() + TimeDelta),
// NewSectionRange.GetUpperBound()
// );
// }
// if (SectionRange.GetUpperBound().IsClosed())
// {
// NewSectionRange = TRange<FFrameNumber>(
// NewSectionRange.GetLowerBound(),
// TRangeBound<FFrameNumber>::Exclusive(SectionRange.GetUpperBoundValue() + TimeDelta)
// );
// }
TRange<FFrameNumber> ThisRange = SectionRange.Value;
for (const auto Section : Sections)
{
check(Section);
if ((this != Section) && (Section->GetRowIndex() == NewTrackIndex))
{
//TRange<float> ExclusiveSectionRange = TRange<float>(TRange<float>::BoundsType::Exclusive(Section->GetRange().GetLowerBoundValue()), TRange<float>::BoundsType::Exclusive(Section->GetRange().GetUpperBoundValue()));
if (ThisRange.Overlaps(Section->GetRange()))
{
return Section;
}
}
}
return nullptr;
}
void UMovieSceneSection::InitialPlacement(const TArray<UMovieSceneSection*>& Sections, FFrameNumber InStartTime, int32 Duration, bool bAllowMultipleRows)
{
check(Duration >= 0);
// Inclusive lower, exclusive upper bounds
SectionRange = TRange<FFrameNumber>(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<FFrameNumber> OtherRange = OverlappedSection->GetRange();
if (OtherRange.GetUpperBound().IsClosed())
{
MoveSection(OtherRange.GetUpperBoundValue() - InStartTime);
}
else
{
++OverlapPriority;
break;
}
}
}
UMovieSceneTrack* Track = GetTypedOuter<UMovieSceneTrack>();
if (Track)
{
Track->UpdateEasing();
}
}
void UMovieSceneSection::InitialPlacementOnRow(const TArray<UMovieSceneSection*>& Sections, FFrameNumber InStartTime, int32 Duration, int32 InRowIndex)
{
check(Duration >= 0);
// Inclusive lower, exclusive upper bounds
SectionRange = TRange<FFrameNumber>(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<UMovieSceneTrack>();
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<UMovieSceneTrack>(GetOuter());
Track->Modify();
UMovieSceneSection* NewSection = DuplicateObject<UMovieSceneSection>(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<UMovieSceneTrack>())
{
if (UMovieScene* MovieScene = Track->GetTypedOuter<UMovieScene>())
{
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<FFrameNumber>::Inclusive(TrimTime.Time.GetFrame()));
}
else
{
SectionRange.Value.SetUpperBound(TRangeBound<FFrameNumber>::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<float>& OutEaseInValue, TOptional<float>& 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<FFrameNumber> UMovieSceneSection::GetEaseInRange() const
{
if (HasStartFrame() && Easing.GetEaseInDuration() > 0)
{
TRangeBound<FFrameNumber> LowerBound = TRangeBound<FFrameNumber>::Inclusive(GetInclusiveStartFrame());
TRangeBound<FFrameNumber> UpperBound = TRangeBound<FFrameNumber>::Exclusive(GetInclusiveStartFrame() + Easing.GetEaseInDuration());
UpperBound = TRangeBound<FFrameNumber>::MinUpper(UpperBound, SectionRange.Value.GetUpperBound());
return TRange<FFrameNumber>(LowerBound, UpperBound);
}
return TRange<FFrameNumber>::Empty();
}
TRange<FFrameNumber> UMovieSceneSection::GetEaseOutRange() const
{
if (HasEndFrame() && Easing.GetEaseOutDuration() > 0)
{
TRangeBound<FFrameNumber> UpperBound = TRangeBound<FFrameNumber>::Exclusive(GetExclusiveEndFrame());
TRangeBound<FFrameNumber> LowerBound = TRangeBound<FFrameNumber>::Inclusive(GetExclusiveEndFrame() - Easing.GetEaseOutDuration());
LowerBound = TRangeBound<FFrameNumber>::MaxLower(LowerBound, SectionRange.Value.GetLowerBound());
return TRange<FFrameNumber>(LowerBound, UpperBound);
}
return TRange<FFrameNumber>::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<UMovieSceneTrack>())
{
Track->UpdateEasing();
}
}
}
ECookOptimizationFlags UMovieSceneSection::GetCookOptimizationFlags() const
{
UMovieSceneTrack* Track = GetTypedOuter<UMovieSceneTrack>();
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