// Copyright Epic Games, Inc. All Rights Reserved. #include "Evaluation/MovieScenePlayback.h" #include "MovieSceneTransformTypes.h" #include "MovieScene.h" namespace { TRange CalculateEvaluationRange(FFrameTime CurrentTime, FFrameTime PreviousTime, bool bInclusivePreviousTime) { if (CurrentTime == PreviousTime) { return TRange(CurrentTime); } else if (CurrentTime < PreviousTime) { return TRange( TRangeBound::Inclusive(CurrentTime), bInclusivePreviousTime ? TRangeBound::Inclusive(PreviousTime) : TRangeBound::Exclusive(PreviousTime) ); } return TRange( bInclusivePreviousTime ? TRangeBound::Inclusive(PreviousTime) : TRangeBound::Exclusive(PreviousTime) , TRangeBound::Inclusive(CurrentTime) ); } } FMovieSceneEvaluationRange::FMovieSceneEvaluationRange(FFrameTime InTime, FFrameRate InFrameRate) : EvaluationRange(InTime) , CurrentFrameRate(InFrameRate) , Direction(EPlayDirection::Forwards) , TimeOverride(FFrameNumber(TNumericLimits::Lowest())) { } FMovieSceneEvaluationRange::FMovieSceneEvaluationRange(TRange InRange, FFrameRate InFrameRate, EPlayDirection InDirection) : EvaluationRange(InRange) , CurrentFrameRate(InFrameRate) , Direction(InDirection) , TimeOverride(FFrameNumber(TNumericLimits::Lowest())) { } FMovieSceneEvaluationRange::FMovieSceneEvaluationRange(FFrameTime InCurrentTime, FFrameTime InPreviousTime, FFrameRate InFrameRate, bool bInclusivePreviousTime, EPlayDirection PreferredDirection) : EvaluationRange(CalculateEvaluationRange(InCurrentTime, InPreviousTime, bInclusivePreviousTime)) , CurrentFrameRate(InFrameRate) , TimeOverride(TNumericLimits::Lowest()) { const FFrameTime ZeroTime; const FFrameTime RangeLength = (InCurrentTime - InPreviousTime); Direction = (RangeLength > ZeroTime) ? EPlayDirection::Forwards : ((RangeLength < ZeroTime) ? EPlayDirection::Backwards : PreferredDirection); } void FMovieSceneEvaluationRange::ResetRange(const TRange& NewRange) { ensureMsgf(TimeOverride == TNumericLimits::Lowest(), TEXT("Should reset time ranges on a range with a fixed time override. This should never happen because such ranges are internal to the movie scene compiler.")); EvaluationRange = NewRange; } TRange FMovieSceneEvaluationRange::GetTraversedFrameNumberRange() const { TRange FrameNumberRange; if (!EvaluationRange.GetLowerBound().IsOpen()) { FFrameNumber StartFrame = EvaluationRange.GetLowerBoundValue().FloorToFrame(); FrameNumberRange.SetLowerBound(TRangeBound::Inclusive(StartFrame)); } if (!EvaluationRange.GetUpperBound().IsOpen()) { FFrameNumber EndFrame = EvaluationRange.GetUpperBoundValue().FloorToFrame() + 1; FrameNumberRange.SetUpperBound(TRangeBound::Exclusive(EndFrame)); } return FrameNumberRange; } TRange FMovieSceneEvaluationRange::TimeRangeToNumberRange(const TRange& InFrameTimeRange) { TRange FrameNumberRange; TOptional UpperTime; if (!InFrameTimeRange.GetUpperBound().IsOpen()) { UpperTime = InFrameTimeRange.GetUpperBoundValue(); // Similar to adjusting the lower bound, if there's a subframe on the upper bound, the frame number needs incrementing in order to evaluate keys in the subframe if (UpperTime.GetValue().GetSubFrame() != 0.f || InFrameTimeRange.GetUpperBound().IsInclusive()) { UpperTime.GetValue().FrameNumber = UpperTime.GetValue().FrameNumber + 1; } FrameNumberRange.SetUpperBound(TRangeBound::Exclusive(UpperTime.GetValue().FrameNumber)); } if (!InFrameTimeRange.GetLowerBound().IsOpen()) { FFrameTime LowerTime = InFrameTimeRange.GetLowerBoundValue(); // If there is a sub frame on the start time, we're actually beyond that frame number, so it needs incrementing if (LowerTime.GetSubFrame() != 0.f || InFrameTimeRange.GetLowerBound().IsExclusive()) { LowerTime.FrameNumber = (!UpperTime.IsSet() || LowerTime.FrameNumber < UpperTime.GetValue().FrameNumber) ? LowerTime.FrameNumber + 1 : LowerTime.FrameNumber; } FrameNumberRange.SetLowerBound(TRangeBound::Inclusive(LowerTime.FrameNumber)); } return FrameNumberRange; } TRange FMovieSceneEvaluationRange::NumberRangeToTimeRange(const TRange& InFrameNumberRange) { TRange FrameTimeRange; if (!InFrameNumberRange.GetLowerBound().IsOpen()) { const FFrameNumber FrameNumber = InFrameNumberRange.GetLowerBoundValue(); FrameTimeRange.SetLowerBound( InFrameNumberRange.GetLowerBound().IsExclusive() ? TRangeBound::Exclusive(FrameNumber) : TRangeBound::Inclusive(FrameNumber) ); } if (!InFrameNumberRange.GetUpperBound().IsOpen()) { const FFrameNumber FrameNumber = InFrameNumberRange.GetUpperBoundValue(); FrameTimeRange.SetUpperBound( InFrameNumberRange.GetUpperBound().IsExclusive() ? TRangeBound::Exclusive(FrameNumber) : TRangeBound::Inclusive(FrameNumber) ); } return FrameTimeRange; } FMovieSceneContext FMovieSceneContext::Transform(const FMovieSceneSequenceTransform& InTransform, FFrameRate NewFrameRate) const { using namespace UE::MovieScene; FMovieSceneContext NewContext = *this; NewContext.RootToSequenceTransform = NewContext.RootToSequenceTransform * InTransform; NewContext.CurrentFrameRate = NewFrameRate; NewContext.EvaluationRange = InTransform.ComputeTraversedHull(EvaluationRange); if (NewContext.EvaluationRange.GetLowerBound().IsClosed() && NewContext.EvaluationRange.GetUpperBound().IsClosed() && NewContext.EvaluationRange.GetLowerBoundValue() > NewContext.EvaluationRange.GetUpperBoundValue()) { TRangeBound OldLower = NewContext.EvaluationRange.GetLowerBound(); TRangeBound OldUpper = NewContext.EvaluationRange.GetUpperBound(); NewContext.EvaluationRange.SetLowerBound(OldUpper); NewContext.EvaluationRange.SetUpperBound(OldLower); NewContext.Direction = EPlayDirection::Backwards; } // Transform the current time so we get an idea in what loop(s) we are relative to the root sequence. InTransform.TransformTime(GetTime(), FTransformTimeParams().AppendBreadcrumbs(NewContext.RootToSequenceWarpCounter)); return NewContext; } PRAGMA_DISABLE_DEPRECATION_WARNINGS FMovieSceneTimeTransform FMovieSceneContext::GetSequenceToRootTransform() const { return RootToSequenceTransform.Inverse().AsLegacyLinearTimeTransform(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS FMovieSceneInverseSequenceTransform FMovieSceneContext::GetSequenceToRootSequenceTransform() const { return RootToSequenceTransform.Inverse(); } void FMovieScenePlaybackPosition::CheckInvariants() const { checkf(InputRate.IsValid() && OutputRate.IsValid(), TEXT("Invalid input or output rate. SetTimeBase must be called before any use of this class.")) } void FMovieScenePlaybackPosition::SetTimeBase(FFrameRate NewInputRate, FFrameRate NewOutputRate, EMovieSceneEvaluationType NewEvaluationType) { // Move the current position if necessary if (InputRate.IsValid() && InputRate != NewInputRate) { FFrameTime NewPosition = ConvertFrameTime(CurrentPosition, InputRate, NewInputRate); if (NewEvaluationType == EMovieSceneEvaluationType::FrameLocked) { NewPosition = NewPosition.FloorToFrame(); } Reset(NewPosition); } InputRate = NewInputRate; OutputRate = NewOutputRate; EvaluationType = NewEvaluationType; } void FMovieScenePlaybackPosition::Reset(FFrameTime StartPos) { CurrentPosition = StartPos; PreviousPlayEvalPosition.Reset(); LastRange.Reset(); } FMovieSceneEvaluationRange FMovieScenePlaybackPosition::GetCurrentPositionAsRange() const { CheckInvariants(); FFrameTime OutputPosition = ConvertFrameTime(CurrentPosition, InputRate, OutputRate); return FMovieSceneEvaluationRange(OutputPosition, OutputRate); } FMovieSceneEvaluationRange FMovieScenePlaybackPosition::JumpTo(FFrameTime InputPosition, EPlayDirection PreferredDirection) { CheckInvariants(); PreviousPlayEvalPosition.Reset(); // Floor to the current frame number if running frame-locked if (EvaluationType == EMovieSceneEvaluationType::FrameLocked) { InputPosition = InputPosition.FloorToFrame(); } // Assign the cached input values CurrentPosition = InputPosition; // Convert to output time-base FFrameTime OutputPosition = ConvertFrameTime(InputPosition, InputRate, OutputRate); LastRange = FMovieSceneEvaluationRange(TRange(OutputPosition), OutputRate, PreferredDirection); return LastRange.GetValue(); } FMovieSceneEvaluationRange FMovieScenePlaybackPosition::PlayTo(FFrameTime InputPosition, EPlayDirection PreferredDirection) { CheckInvariants(); // Floor to the current frame number if running frame-locked if (EvaluationType == EMovieSceneEvaluationType::FrameLocked) { InputPosition = InputPosition.FloorToFrame(); } // Convert to output time-base FFrameTime InputEvalPositionFrom = PreviousPlayEvalPosition.Get(CurrentPosition); FFrameTime OutputEvalPositionFrom = ConvertFrameTime(InputEvalPositionFrom, InputRate, OutputRate); FFrameTime OutputEvalPositionTo = ConvertFrameTime(InputPosition, InputRate, OutputRate); LastRange = FMovieSceneEvaluationRange(OutputEvalPositionTo, OutputEvalPositionFrom, OutputRate, !PreviousPlayEvalPosition.IsSet(), PreferredDirection); // Assign the cached input values CurrentPosition = InputPosition; PreviousPlayEvalPosition = InputPosition; return LastRange.GetValue(); } TOptional FMovieScenePlaybackPosition::GetLastRange() const { return LastRange; }