515 lines
18 KiB
C++
515 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Evaluation/MovieScenePlaybackManager.h"
|
|
|
|
#include "Channels/MovieSceneTimeWarpChannel.h"
|
|
#include "Misc/CoreMiscDefines.h"
|
|
#include "MovieScene.h"
|
|
#include "MovieSceneFwd.h"
|
|
#include "MovieSceneSequence.h"
|
|
|
|
FMovieScenePlaybackManager::FMovieScenePlaybackManager()
|
|
{
|
|
}
|
|
|
|
FMovieScenePlaybackManager::FMovieScenePlaybackManager(UMovieSceneSequence* InSequence)
|
|
{
|
|
Initialize(InSequence);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::Initialize(UMovieSceneSequence* InSequence)
|
|
{
|
|
if (!ensure(InSequence))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UMovieScene* MovieScene = InSequence->GetMovieScene();
|
|
const EMovieSceneEvaluationType EvaluationType = MovieScene->GetEvaluationType();
|
|
|
|
DisplayRate = MovieScene->GetDisplayRate();
|
|
|
|
// Make our playback position work *only* in ticks. We will handle conversion to/from frames ourselves.
|
|
const FFrameRate TickResolution = MovieScene->GetTickResolution();
|
|
PlaybackPosition.SetTimeBase(TickResolution, TickResolution, EvaluationType);
|
|
|
|
const TRange<FFrameNumber> PlaybackRange = MovieScene->GetPlaybackRange();
|
|
SequenceStartTick = UE::MovieScene::DiscreteInclusiveLower(PlaybackRange);
|
|
SequenceEndTick = UE::MovieScene::DiscreteExclusiveUpper(PlaybackRange);
|
|
|
|
ResetPlaybackSettings();
|
|
|
|
PlaybackPosition.Reset(SequenceStartTick);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::ResetPlaybackSettings()
|
|
{
|
|
StartOffsetTicks = EndOffsetTicks = FFrameNumber(0);
|
|
NumLoopsToPlay = 1;
|
|
NumLoopsCompleted = 0;
|
|
PlayRate = 1.0;
|
|
PlayDirection = EPlayDirection::Forwards;
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::Update(float InDeltaSeconds, FContexts& OutContexts)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
if (PlaybackStatus != EMovieScenePlayerStatus::Playing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the new time, advanced by InDeltaSeconds.
|
|
const FFrameTime PreviousTick = PlaybackPosition.GetCurrentPosition();
|
|
const FFrameTime DeltaTicks = PlaybackPosition.GetInputRate().AsFrameTime(
|
|
(IsPlayingForward() ? InDeltaSeconds : (-InDeltaSeconds)) * PlayRate);
|
|
const FFrameTime NextTick = PreviousTick + DeltaTicks;
|
|
|
|
FFrameTime WarpedNextTick = NextTick;
|
|
if (bTransformPlaybackTime)
|
|
{
|
|
if (TimeTransform.FindFirstWarpDomain() == ETimeWarpChannelDomain::PlayRate)
|
|
{
|
|
WarpedNextTick = TimeTransform.TransformTime(WarpedNextTick);
|
|
}
|
|
}
|
|
|
|
InternalUpdateToTick(WarpedNextTick.RoundToFrame(), OutContexts);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::UpdateTo(const FFrameTime NextTime, FContexts& OutContexts)
|
|
{
|
|
const FFrameTime NextTick = ConvertFrameTime(NextTime, DisplayRate, PlaybackPosition.GetInputRate());
|
|
InternalUpdateToTick(NextTick.RoundToFrame(), OutContexts);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::InternalUpdateToTick(const FFrameNumber NextTick, FContexts& OutContexts)
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
// If we are stopped, just move the playhead to the given time, without generating evaluation contexts.
|
|
if (PlaybackStatus == EMovieScenePlayerStatus::Stopped)
|
|
{
|
|
PlaybackPosition.Reset(NextTick);
|
|
return;
|
|
}
|
|
|
|
// Check we have some loop counters that make sense.
|
|
if (NumLoopsToPlay > 0 && !ensure(NumLoopsCompleted < NumLoopsToPlay))
|
|
{
|
|
PlaybackStatus = EMovieScenePlayerStatus::Stopped;
|
|
PlaybackPosition.Reset(NextTick);
|
|
}
|
|
|
|
// Gather some information about this update.
|
|
const bool bShouldJump =
|
|
(PlaybackStatus != EMovieScenePlayerStatus::Playing && PlaybackStatus != EMovieScenePlayerStatus::Scrubbing);
|
|
|
|
const FFrameNumber EffectiveStartTick = SequenceStartTick + StartOffsetTicks;
|
|
const FFrameNumber EffectiveEndTick = SequenceEndTick - EndOffsetTicks;
|
|
const FFrameNumber EffectiveDurationTicks = FMath::Max(FFrameNumber(0), EffectiveEndTick - EffectiveStartTick);
|
|
const FFrameNumber LastValidTick = GetLastValidTick();
|
|
// IMPORTANT: we assume that LastValidTick is less than the duration (current implementation is
|
|
// duration minus one tick).
|
|
|
|
bool bIsPlayingForward = IsPlayingForward();
|
|
FFrameNumber LoopStartTick = bIsPlayingForward ? EffectiveStartTick : LastValidTick;
|
|
FFrameNumber LoopLastTick = bIsPlayingForward ? LastValidTick : EffectiveStartTick;
|
|
FFrameNumber LastLoopLastTick = PlaybackEndTick.Get(LoopLastTick);
|
|
// If the start/end offsets make the duration 0, we treat each loop as one tick long.
|
|
const FFrameNumber LoopDurationTicks = FMath::Max(FFrameNumber(1), EffectiveDurationTicks);
|
|
|
|
// Figure out if we crossed the loop-end boundary, or the "stop at" time on the last loop.
|
|
const bool bCrossedLoopEnd = (
|
|
(bIsPlayingForward && NextTick > LoopLastTick) ||
|
|
(!bIsPlayingForward && NextTick < LoopLastTick)
|
|
);
|
|
const bool bReachedLastLoopEnd = (
|
|
(NumLoopsToPlay > 0 && NumLoopsCompleted >= NumLoopsToPlay - 1) &&
|
|
(
|
|
(bIsPlayingForward && NextTick >= LastLoopLastTick) ||
|
|
(!bIsPlayingForward && NextTick <= LastLoopLastTick)
|
|
));
|
|
if (bReachedLastLoopEnd)
|
|
{
|
|
// Special case when we reach or pass the last tick of the last loop. Play the last
|
|
// bit of the animation, and stop.
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LastLoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(bShouldJump)
|
|
);
|
|
|
|
++NumLoopsCompleted;
|
|
ensure(NumLoopsToPlay > 0 && NumLoopsCompleted == NumLoopsToPlay);
|
|
|
|
PlaybackStatus = EMovieScenePlayerStatus::Stopped;
|
|
}
|
|
else if (bCrossedLoopEnd)
|
|
{
|
|
// Compute how many times we crossed the loop-end boundary.
|
|
// NOTE: we completed one or more loops. Any extra loop may be the last loop, and therefore
|
|
// may need to consider a different end time (see LastLoopLastTick).
|
|
const FFrameNumber LoopRelativeTick = NextTick - LoopStartTick;
|
|
const int32 NumLoopingsOver = FMath::Abs(LoopRelativeTick.Value) / LoopDurationTicks.Value;
|
|
ensure(NumLoopingsOver > 0);
|
|
|
|
// Get the actual number of loops we want to consider completed, and see if this will bring
|
|
// us inside or past the last loop.
|
|
const int32 NumLoopsNewlyCompleted = ((NumLoopsToPlay > 0) ?
|
|
FMath::Min(NumLoopingsOver, NumLoopsToPlay - NumLoopsCompleted) :
|
|
NumLoopingsOver);
|
|
const bool bCompletesLastLoop = (
|
|
NumLoopsToPlay > 0 && NumLoopsCompleted + NumLoopsNewlyCompleted >= NumLoopsToPlay);
|
|
ensure(NumLoopsNewlyCompleted > 0);
|
|
|
|
// Play the last bit of the loop if we are looping and doing any sort of dissections.
|
|
// We know this isn't the last loop (otherwise we'd be in the bReachedLastLoopEnd block)
|
|
// so we don't need to be worried about any custom end time.
|
|
if (DissectLooping != EMovieSceneLoopDissection::None)
|
|
{
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(bShouldJump)
|
|
);
|
|
}
|
|
|
|
// See if we need to generate more update ranges for the loops. This can happen if we had a
|
|
// large delta-time, and the duration of a loop is pretty short (i.e. we could have looped
|
|
// several times in one update).
|
|
if (NumLoopsNewlyCompleted > 1 && DissectLooping == EMovieSceneLoopDissection::DissectAll)
|
|
{
|
|
// Add an explicit update for each loop, from start to end. Do one less extra loop if
|
|
// the last extra loop is the final loop. We will handle the last loop later (see below).
|
|
const int32 ExtraLoops = NumLoopsNewlyCompleted - 1 - (bCompletesLastLoop ? 1 : 0);
|
|
ensure(ExtraLoops >= 0);
|
|
|
|
if (!bPingPongPlayback)
|
|
{
|
|
for (int32 Index = 0; Index < ExtraLoops; ++Index)
|
|
{
|
|
PlaybackPosition.Reset(LoopStartTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReversePlayDirection();
|
|
|
|
for (int32 Index = 0; Index < ExtraLoops; ++Index)
|
|
{
|
|
if (IsPlayingForward())
|
|
{
|
|
PlaybackPosition.Reset(EffectiveStartTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LastValidTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PlaybackPosition.Reset(LastValidTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(EffectiveStartTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
}
|
|
|
|
ReversePlayDirection();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are ping-pong'ing, keep track of the direction to go next. We don't need to do
|
|
// this if we were dissecting each loop, because then we already updated the play direction
|
|
// while dissecting (see above).
|
|
if (bPingPongPlayback && DissectLooping != EMovieSceneLoopDissection::DissectAll)
|
|
{
|
|
// If we are reaching the end of the last loop, don't flip an extra time. Just finish
|
|
// playing in that loop's direction.
|
|
const int32 NumPingPongs = NumLoopsNewlyCompleted - (bCompletesLastLoop ? 1 : 0);
|
|
if (NumPingPongs % 2 != 0)
|
|
{
|
|
ReversePlayDirection();
|
|
}
|
|
}
|
|
|
|
// Complete the loops we said we completed.
|
|
NumLoopsCompleted += NumLoopsNewlyCompleted;
|
|
|
|
// Re-query the direction of play in case we were ping-pong'ing, and update loop times.
|
|
bIsPlayingForward = IsPlayingForward();
|
|
LoopStartTick = bIsPlayingForward ? EffectiveStartTick : LastValidTick;
|
|
LoopLastTick = bIsPlayingForward ? LastValidTick : EffectiveStartTick;
|
|
LastLoopLastTick = PlaybackEndTick.Get(LoopLastTick);
|
|
|
|
if (bCompletesLastLoop)
|
|
{
|
|
// We completed one loop, plus one or more extra loops that brought us to the end.
|
|
ensure(NumLoopsNewlyCompleted > 1);
|
|
|
|
if (DissectLooping == EMovieSceneLoopDissection::None)
|
|
{
|
|
// If we didn't dissect anything, we play to the end time if that's possible from
|
|
// where we were last, or reset and play the last loop from the start if not.
|
|
const FFrameNumber CurrentTick = PlaybackPosition.GetCurrentPosition().FrameNumber;
|
|
const bool bCanPlayToEndFromCurrentTick =
|
|
(bIsPlayingForward && CurrentTick < LastLoopLastTick) ||
|
|
(!bIsPlayingForward && CurrentTick > LastLoopLastTick);
|
|
if (bCanPlayToEndFromCurrentTick)
|
|
{
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LastLoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(bShouldJump)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
PlaybackPosition.Reset(LoopStartTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LastLoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we dissect all loops, we left the last loop earlier to do now.
|
|
// If we dissect only one loop, we play the last loop now.
|
|
PlaybackPosition.Reset(LoopStartTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(LastLoopLastTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
}
|
|
|
|
PlaybackStatus = EMovieScenePlayerStatus::Stopped;
|
|
}
|
|
else
|
|
{
|
|
// Start the next loop with any overplay from the update.
|
|
//
|
|
// When playing forward, we have, e.g.:
|
|
// loop = [-30, -10], next time = -5, relative time = 25, mod(25, 20) = 5
|
|
//
|
|
// When playing backwards, we have, e.g.:
|
|
// loop = [-30, -10], next time = -35, relative time = -25, mod(-25, 20) = -5
|
|
const FFrameNumber OverplayTicks = LoopRelativeTick.Value % LoopDurationTicks.Value;
|
|
|
|
// We reverse OverplayTicks when ping-pong'in since we're playing this overplay in the
|
|
// reverse direction (otherwise, it already has the correct sign).
|
|
FFrameTime EffectiveOverplayTick = LoopStartTick + ((!bPingPongPlayback) ? OverplayTicks : (-OverplayTicks));
|
|
|
|
// If we have reached the last loop, clamp our overplay with any custom end time.
|
|
const bool bIsLastLoop = (
|
|
NumLoopsToPlay > 0 && NumLoopsCompleted >= NumLoopsToPlay - 1);
|
|
if (bIsLastLoop)
|
|
{
|
|
EffectiveOverplayTick = bIsPlayingForward ?
|
|
FMath::Min(EffectiveOverplayTick, FFrameTime(LastLoopLastTick)) :
|
|
FMath::Max(EffectiveOverplayTick, FFrameTime(LastLoopLastTick));
|
|
}
|
|
|
|
// Evaluate the overplay.
|
|
PlaybackPosition.Reset(bIsPlayingForward ? EffectiveStartTick : LastValidTick);
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(EffectiveOverplayTick), PlaybackStatus)
|
|
.SetHasJumped(true)
|
|
.SetHasLooped(true)
|
|
);
|
|
|
|
// If the overplay leads us at/beyond the last tick of the last loop, let's count that
|
|
// as a completed loop and finish playback. Otherwise, we'll wait for the next update
|
|
// to loop over in order to avoid counting that loop twice.
|
|
if (bIsLastLoop && EffectiveOverplayTick == LastLoopLastTick)
|
|
{
|
|
++NumLoopsCompleted;
|
|
|
|
PlaybackStatus = EMovieScenePlayerStatus::Stopped;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We haven't crossed a loop-end boundary... just chug along.
|
|
OutContexts.Add(
|
|
FMovieSceneContext(PlaybackPosition.PlayTo(NextTick), PlaybackStatus)
|
|
.SetHasJumped(bShouldJump)
|
|
);
|
|
}
|
|
|
|
ensure(OutContexts.Num() > 0);
|
|
ensure(OutContexts.Num() == 1 || (DissectLooping != EMovieSceneLoopDissection::None));
|
|
}
|
|
|
|
FFrameNumber FMovieScenePlaybackManager::GetLastValidTick() const
|
|
{
|
|
// TODO: handle precision problems with float SubFrame, or change SubFrame to double.
|
|
return SequenceEndTick - EndOffsetTicks - 1; // minus one tick for exclusive end frame.
|
|
}
|
|
|
|
FMovieSceneContext FMovieScenePlaybackManager::UpdateToNextTick()
|
|
{
|
|
const FFrameTime CurrentTick = PlaybackPosition.GetCurrentPosition();
|
|
return FMovieSceneContext(PlaybackPosition.PlayTo(CurrentTick, PlayDirection), PlaybackStatus);
|
|
}
|
|
|
|
FMovieSceneContext FMovieScenePlaybackManager::UpdateAtCurrentTime() const
|
|
{
|
|
return FMovieSceneContext(PlaybackPosition.GetCurrentPositionAsRange(), PlaybackStatus);
|
|
}
|
|
|
|
FFrameTime FMovieScenePlaybackManager::GetCurrentTime() const
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(PlaybackPosition.GetCurrentPosition(), TickResolution, DisplayRate);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetCurrentTime(const FFrameTime& InFrameTime)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
const FFrameNumber CurrentTick = ConvertFrameTime(InFrameTime, DisplayRate, TickResolution).RoundToFrame();
|
|
const FFrameNumber EffectiveStartTick = SequenceStartTick + StartOffsetTicks;
|
|
const FFrameNumber LastValidTick = GetLastValidTick();
|
|
PlaybackPosition.Reset(FMath::Clamp(CurrentTick, EffectiveStartTick, LastValidTick));
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetCurrentTimeOffset(const FFrameTime& InFrameTimeOffset)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
const FFrameNumber CurrentTickOffset = ConvertFrameTime(InFrameTimeOffset, DisplayRate, TickResolution).RoundToFrame();
|
|
const FFrameNumber EffectiveStartTick = SequenceStartTick + StartOffsetTicks;
|
|
const FFrameNumber LastValidTick = GetLastValidTick();
|
|
PlaybackPosition.Reset(FMath::Clamp(EffectiveStartTick + CurrentTickOffset, EffectiveStartTick, LastValidTick));
|
|
}
|
|
|
|
TRange<FFrameTime> FMovieScenePlaybackManager::GetEffectivePlaybackRange() const
|
|
{
|
|
const FFrameNumber StartTick = SequenceStartTick + StartOffsetTicks;
|
|
const FFrameNumber EndTick = SequenceEndTick - EndOffsetTicks;
|
|
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
|
|
const FFrameTime StartFrame = ConvertFrameTime(StartTick, TickResolution, DisplayRate);
|
|
const FFrameTime EndFrame = ConvertFrameTime(EndTick, TickResolution, DisplayRate);
|
|
|
|
return TRange<FFrameTime>(TRangeBound<FFrameTime>::Inclusive(StartFrame), TRangeBound<FFrameTime>::Exclusive(EndFrame));
|
|
}
|
|
|
|
FFrameTime FMovieScenePlaybackManager::GetEffectiveStartTime() const
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(SequenceStartTick + StartOffsetTicks, TickResolution, DisplayRate);
|
|
}
|
|
|
|
FFrameTime FMovieScenePlaybackManager::GetEffectiveEndTime() const
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(SequenceEndTick - EndOffsetTicks, TickResolution, DisplayRate);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetStartOffset(const FFrameTime& InStartOffset)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
const FFrameNumber InStartOffsetTicks = ConvertFrameTime(InStartOffset, DisplayRate, TickResolution).RoundToFrame();
|
|
|
|
SetStartAndEndOffsetTicks(InStartOffsetTicks, EndOffsetTicks);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetEndOffset(const FFrameTime& InEndOffset)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
const FFrameNumber InEndOffsetTicks = ConvertFrameTime(InEndOffset, DisplayRate, TickResolution).RoundToFrame();
|
|
|
|
SetStartAndEndOffsetTicks(StartOffsetTicks, InEndOffsetTicks);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetEndOffsetAsTime(const FFrameTime& InEndTime)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
const FFrameNumber InEndTick = ConvertFrameTime(InEndTime, DisplayRate, TickResolution).RoundToFrame();
|
|
|
|
const FFrameNumber InEndOffsetTicks = SequenceEndTick - InEndTick;
|
|
SetStartAndEndOffsetTicks(StartOffsetTicks, InEndOffsetTicks);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetStartAndEndOffsetTicks(FFrameNumber InStartOffsetTicks, FFrameNumber InEndOffsetTicks)
|
|
{
|
|
const FFrameNumber SequenceDurationTicks = SequenceEndTick - SequenceStartTick;
|
|
|
|
StartOffsetTicks = FMath::Min(
|
|
FMath::Max(InStartOffsetTicks, FFrameNumber(0)),
|
|
SequenceDurationTicks);
|
|
|
|
EndOffsetTicks = FMath::Min(
|
|
FMath::Max(InEndOffsetTicks, FFrameNumber(0)),
|
|
SequenceDurationTicks - StartOffsetTicks);
|
|
}
|
|
|
|
FFrameTime FMovieScenePlaybackManager::GetStartOffset() const
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(StartOffsetTicks, TickResolution, DisplayRate);
|
|
}
|
|
|
|
FFrameTime FMovieScenePlaybackManager::GetEndOffset() const
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(EndOffsetTicks, TickResolution, DisplayRate);
|
|
}
|
|
|
|
TOptional<FFrameTime> FMovieScenePlaybackManager::GetPlaybackEndTime() const
|
|
{
|
|
if (const FFrameNumber* EndTickValue = PlaybackEndTick.GetPtrOrNull())
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
return ConvertFrameTime(*EndTickValue, TickResolution, DisplayRate);
|
|
}
|
|
return TOptional<FFrameTime>();
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetPlaybackEndTime(const FFrameTime& InEndTime)
|
|
{
|
|
const FFrameRate TickResolution = PlaybackPosition.GetOutputRate();
|
|
PlaybackEndTick = FMath::Clamp(
|
|
ConvertFrameTime(InEndTime, DisplayRate, TickResolution).RoundToFrame(),
|
|
SequenceStartTick + StartOffsetTicks,
|
|
SequenceEndTick - EndOffsetTicks);
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::ClearPlaybackEndTime()
|
|
{
|
|
PlaybackEndTick.Reset();
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetNumLoopsToPlay(int32 InNumLoopsToPlay)
|
|
{
|
|
NumLoopsToPlay = InNumLoopsToPlay;
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::ReversePlayDirection()
|
|
{
|
|
if (PlayDirection == EPlayDirection::Forwards)
|
|
{
|
|
PlayDirection = EPlayDirection::Backwards;
|
|
}
|
|
else
|
|
{
|
|
PlayDirection = EPlayDirection::Forwards;
|
|
}
|
|
}
|
|
|
|
void FMovieScenePlaybackManager::SetPingPongPlayback(bool bInPingPongPlayback)
|
|
{
|
|
bPingPongPlayback = bInPingPongPlayback;
|
|
}
|
|
|