Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

624 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "CoreMinimal.h"
#include "Evaluation/MovieSceneSequenceTransform.h"
#include "Evaluation/MovieSceneTimeTransform.h"
#include "Evaluation/MovieSceneTimeWarping.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "Math/NumericLimits.h"
#include "Math/Range.h"
#include "Math/RangeBound.h"
#include "Misc/AssertionMacros.h"
#include "Misc/FrameNumber.h"
#include "Misc/FrameRate.h"
#include "Misc/FrameTime.h"
#include "Misc/Optional.h"
#include "MovieSceneFwd.h"
#include "MovieSceneTimeHelpers.h"
/** Enumeration specifying whether we're playing forwards or backwards */
enum class EPlayDirection
{
Forwards, Backwards
};
/** MovieScene evaluation context. Should remain bitwise copyable, and contain no external state since this has the potential to be used on a thread */
struct FMovieSceneEvaluationRange
{
/**
* Construct this range from a single fixed time
*/
MOVIESCENE_API FMovieSceneEvaluationRange(FFrameTime InTime, FFrameRate InFrameRate);
/**
* Construct this range from a raw range and a direction
*/
MOVIESCENE_API FMovieSceneEvaluationRange(TRange<FFrameTime> InRange, FFrameRate InFrameRate, EPlayDirection InDirection);
/**
* Construct this range from 2 times, and whether the range should include the previous time or not
*/
MOVIESCENE_API FMovieSceneEvaluationRange(FFrameTime InCurrentTime, FFrameTime InPreviousTime, FFrameRate InFrameRate, bool bInclusivePreviousTime = false, EPlayDirection PreferredDirection = EPlayDirection::Forwards);
/**
* Convert a frame time range to a frame number range comprising all the frame numbers traversed in the range
*/
static MOVIESCENE_API TRange<FFrameNumber> TimeRangeToNumberRange(const TRange<FFrameTime>& InFrameTimeRange);
/**
* Convert a frame number range to a frame time range
*/
static MOVIESCENE_API TRange<FFrameTime> NumberRangeToTimeRange(const TRange<FFrameNumber>& InFrameTimeRange);
/**
* Get the range that we should be evaluating
*/
inline TRange<FFrameTime> GetRange() const
{
return EvaluationRange;
}
/**
* Get the range of frame numbers traversed over this evaluation range, not including partial frames
*/
inline TRange<FFrameNumber> GetFrameNumberRange() const
{
return TimeRangeToNumberRange(EvaluationRange);
}
/**
* Get the range of frame numbers traversed over this evaluation range by flooring the lower bound, and ceiling the upper bound.
* For example: a time range of [1.5, 5.6] will yield the equivalent of [1, 6). A time range of (2.0, 2.9) will yield the equivalent of [2,3).
*/
MOVIESCENE_API TRange<FFrameNumber> GetTraversedFrameNumberRange() const;
/**
* Get the direction to evaluate our range
*/
inline EPlayDirection GetDirection() const
{
return Direction;
}
/**
* Get the current time of evaluation.
*/
inline FFrameTime GetTime() const
{
if (TimeOverride != TNumericLimits<int32>::Lowest())
{
return TimeOverride;
}
return Direction == EPlayDirection::Forwards ? EvaluationRange.GetUpperBoundValue() : EvaluationRange.GetLowerBoundValue();
}
/**
* Get the current time to use for looking up within an evaluation field.
* Subtly different from GetTime in that it returns the previous tick for exclusive boundaries
*/
inline FFrameNumber GetEvaluationFieldTime() const
{
TRange<FFrameNumber> Range = GetFrameNumberRange();
if (Direction == EPlayDirection::Forwards)
{
return UE::MovieScene::DiscreteExclusiveUpper(Range)-1;
}
else
{
return UE::MovieScene::DiscreteInclusiveLower(Range);
}
}
/**
* Get the absolute amount of time that has passed since the last update (will always be >= 0)
*/
inline FFrameTime GetDelta() const
{
return EvaluationRange.Size<FFrameTime>();
}
/**
* Get the previous time of evaluation. Should not generally be used. Prefer GetRange instead.
*/
inline FFrameTime GetPreviousTime() const
{
return Direction == EPlayDirection::Forwards ? EvaluationRange.GetLowerBoundValue() : EvaluationRange.GetUpperBoundValue();
}
/**
* Get the current time offset by the specified amount in the direction of play
*/
inline FFrameTime GetOffsetTime(FFrameTime InOffset) const
{
FFrameTime Now = GetTime();
return Direction == EPlayDirection::Forwards ? Now + InOffset : Now - InOffset;
}
/**
* Override the time that we're actually evaluating at
*/
inline void OverrideTime(FFrameNumber InTimeOverride)
{
TimeOverride = InTimeOverride;
}
/**
* Get the framerate that this context's times are in
* @return The framerate that all times are relative to
*/
inline FFrameRate GetFrameRate() const
{
return CurrentFrameRate;
}
/**
* Reset this range to a new range while retaining the current framerate and direction
*/
MOVIESCENE_API void ResetRange(const TRange<FFrameTime>& NewRange);
protected:
/** The range to evaluate */
TRange<FFrameTime> EvaluationRange;
/** The framerate of the current sequence. */
FFrameRate CurrentFrameRate;
/** Whether to evaluate the range forwards, or backwards */
EPlayDirection Direction;
/** Overridden current time (doesn't manipulate the actual evaluated range) */
FFrameNumber TimeOverride;
};
/** MovieScene evaluation context. Should remain bitwise copyable, and contain no external state since this has the potential to be used on a thread */
struct FMovieSceneContext : FMovieSceneEvaluationRange
{
/**
* Construction from an evaluation range, and a current status
*/
FMovieSceneContext()
: FMovieSceneEvaluationRange(TRange<FFrameTime>::Empty(), FFrameRate(60, 1), EPlayDirection::Forwards)
, Status(EMovieScenePlayerStatus::Stopped)
, PrePostRollStartEndTime(TNumericLimits<int32>::Lowest())
, HierarchicalBias(0)
, bHasJumped(false)
, bHasLooped(false)
, bSilent(false)
, bSectionPreRoll(false)
, bSectionPostRoll(false)
, bHasPreRollEndTime(false)
, bHasPostRollStartTime(false)
{}
/**
* Construction from an evaluation range, and a current status
*/
FMovieSceneContext(FMovieSceneEvaluationRange InRange)
: FMovieSceneEvaluationRange(InRange)
, Status(EMovieScenePlayerStatus::Stopped)
, PrePostRollStartEndTime(TNumericLimits<int32>::Lowest())
, HierarchicalBias(0)
, bHasJumped(false)
, bHasLooped(false)
, bSilent(false)
, bSectionPreRoll(false)
, bSectionPostRoll(false)
, bHasPreRollEndTime(false)
, bHasPostRollStartTime(false)
{}
/**
* Construction from an evaluation range, and a current status
*/
FMovieSceneContext(FMovieSceneEvaluationRange InRange, EMovieScenePlayerStatus::Type InStatus)
: FMovieSceneEvaluationRange(InRange)
, Status(InStatus)
, PrePostRollStartEndTime(TNumericLimits<int32>::Lowest())
, HierarchicalBias(0)
, bHasJumped(false)
, bHasLooped(false)
, bSilent(false)
, bSectionPreRoll(false)
, bSectionPostRoll(false)
, bHasPreRollEndTime(false)
, bHasPostRollStartTime(false)
{}
/**
* Get the playback status
*/
inline EMovieScenePlayerStatus::Type GetStatus() const
{
return Status;
}
/**
* Check whether we've just jumped to a different time
*/
inline bool HasJumped() const
{
return bHasJumped;
}
/**
* Check whether we've just looped around the sequence
* If this returns true, HasJumped() usually also returns true
*/
inline bool HasLooped() const
{
return bHasLooped;
}
/**
* Check whether we're evaluating in silent mode (no audio or mutating eval)
*/
inline bool IsSilent() const
{
return bSilent;
}
/**
* Get the current root to sequence transform for the current sub sequence
*/
inline const FMovieSceneSequenceTransform& GetRootToSequenceTransform() const
{
return RootToSequenceTransform;
}
/**
* Get the current root to sequence warp counter for the current sub sequence
*/
inline const FMovieSceneWarpCounter& GetRootToSequenceWarpCounter() const
{
return RootToSequenceWarpCounter;
}
/**
* Get the inverse transform of the current sub sequence, to transform local times back to root times.
*/
UE_DEPRECATED(5.4, "Please use GetSequenceToRootSequenceTransform instead.")
MOVIESCENE_API FMovieSceneTimeTransform GetSequenceToRootTransform() const;
/**
* Get the inverse sequence transform of the current sub sequence, to transform local times back to root times.
*/
MOVIESCENE_API FMovieSceneInverseSequenceTransform GetSequenceToRootSequenceTransform() const;
/**
* Apply section pre and post roll based on whether we're in the leading (preroll), or trailing (postroll) region for the section, and the current play direction
*
* @param bInLeadingRegion Whether we are considered to be in the section's leading (aka preroll) region
* @param bInTrailingRegion Whether we are considered to be in the section's trailing (aka postroll) region
*/
inline void ApplySectionPrePostRoll(bool bInLeadingRegion, bool bInTrailingRegion)
{
if (Direction == EPlayDirection::Forwards)
{
bSectionPreRoll = bInLeadingRegion;
bSectionPostRoll = bInTrailingRegion;
}
else
{
bSectionPreRoll = bInTrailingRegion;
bSectionPostRoll = bInLeadingRegion;
}
}
public:
/**
* Indicate that we've just jumped to a different time
*/
FMovieSceneContext& SetHasJumped(bool bInHasJumped)
{
bHasJumped = bInHasJumped;
return *this;
}
/**
* Indicate that we've just looped around the sequence
* The caller should also probably call SetHasJumped(true)
*/
FMovieSceneContext& SetHasLooped(bool bInHasLooped)
{
bHasLooped = bInHasLooped;
return *this;
}
/**
* Set the context to silent mode
*/
FMovieSceneContext& SetIsSilent(bool bInIsSilent)
{
bSilent = bInIsSilent;
return *this;
}
/**
* Clamp the current evaluation range to the specified range (in the current transform space)
*/
FMovieSceneContext Clamp(TRange<FFrameTime> NewRange) const
{
FMovieSceneContext NewContext = *this;
NewContext.EvaluationRange = TRange<FFrameTime>::Intersection(NewRange, NewContext.EvaluationRange);
return NewContext;
}
/**
* Transform this context to a different sub sequence space
*/
MOVIESCENE_API FMovieSceneContext Transform(const FMovieSceneSequenceTransform& InTransform, FFrameRate NewFrameRate) const;
/**
* Get the hierarchical bias for the current context
* @param InHierarchicalBias The current hierarchical bias
*/
void SetHierarchicalBias(int32 InHierarchicalBias)
{
HierarchicalBias = InHierarchicalBias;
}
/**
* Get the hierarchical bias for the current context
* @return The current hierarchical bias
*/
int32 GetHierarchicalBias() const
{
return HierarchicalBias;
}
public:
/**
* Check if we're in any kind of preroll (either prerolling section specifically, or as part of a sub-section)
* @note Play direction has already been considered in the calculation of this function, so needs no extra consideration.
*/
bool IsPreRoll() const
{
return bHasPreRollEndTime || bSectionPreRoll;
}
/**
* Check if we're in any kind of postroll (either postrolling section specifically, or as part of a sub-section)
* @note Play direction has already been considered in the calculation of this function, so needs no extra consideration.
*/
bool IsPostRoll() const
{
return bHasPostRollStartTime || bSectionPostRoll;
}
/**
* Check whether we have an externally supplied time at which preroll will end.
* @note When not set (and IsPreRoll() is true), preroll ends at either the start or end of section bounds, depending on play direction.
*/
bool HasPreRollEndTime() const
{
return bHasPreRollEndTime;
}
/**
* Check whether we have an externally supplied time at which postroll started.
* @note When not set (and IsPostRoll() is true), preroll ends at either the start or end of section bounds, depending on play direction.
*/
bool HasPostRollStartTime() const
{
return bHasPostRollStartTime;
}
/**
* Access the time at which preroll will stop, and evaluation will commence
* @note: Only valid to call when HasPreRollEndTime() is true
*/
FFrameNumber GetPreRollEndFrame() const
{
checkf(bHasPreRollEndTime, TEXT("It's invalid to call GetPreRollEndFrame() without first checking HasPreRollEndTime()"));
return PrePostRollStartEndTime;
}
/**
* Access the time at which post roll started (or in other terms: when evaluation stopped)
* @note: Only valid to call when HasPostRollStartTime() is true
*/
FFrameNumber GetPostRollStartFrame() const
{
checkf(bHasPostRollStartTime, TEXT("It's invalid to call GetPostRollStartFrame() without first checking HasPostRollStartTime()"));
return PrePostRollStartEndTime;
}
/**
* Report the outer section pre and post roll ranges for the current context
*
* @param InLeadingRange The leading (preroll) range in front of the outer section, in the current transformation's time space
* @param InTrailingRange The trailing (postroll) range at the end of the outer section, in the current transformation's time space
*/
void ReportOuterSectionRanges(TRange<FFrameNumber> InLeadingRange, TRange<FFrameNumber> InTrailingRange)
{
const FFrameNumber Now = GetTime().FrameNumber;
if (InLeadingRange.Contains(Now) && InLeadingRange.HasUpperBound())
{
PrePostRollStartEndTime = InLeadingRange.GetUpperBoundValue();
bHasPreRollEndTime = Direction == EPlayDirection::Forwards;
bHasPostRollStartTime = !bHasPreRollEndTime;
}
else if (InTrailingRange.Contains(Now) && InTrailingRange.HasLowerBound())
{
PrePostRollStartEndTime = InTrailingRange.GetLowerBoundValue();
bHasPreRollEndTime = Direction == EPlayDirection::Backwards;
bHasPostRollStartTime = !bHasPreRollEndTime;
}
else
{
bHasPreRollEndTime = bHasPostRollStartTime = false;
PrePostRollStartEndTime = FFrameNumber(TNumericLimits<int32>::Lowest());
}
}
protected:
/** The transform from the root sequence to the current sequence space */
FMovieSceneSequenceTransform RootToSequenceTransform;
/** The current warp count for any sub-sequence in the current sequence's hierarchy */
FMovieSceneWarpCounter RootToSequenceWarpCounter;
/** The current playback status */
EMovieScenePlayerStatus::Type Status;
/** When bHasPreRollEndTime or bHasPostRollStartTime is true, this defines either the frame at which 'real' evaluation commences, or finished */
FFrameNumber PrePostRollStartEndTime;
/** Hierachical bias. Higher bias should take precedence. */
int32 HierarchicalBias;
protected:
/** Whether this evaluation frame is happening as part of a large jump */
bool bHasJumped : 1;
/** Whether this evaluation frame is happening as part of looping around the sequence */
bool bHasLooped : 1;
/** Whether this evaluation should happen silently */
bool bSilent : 1;
/** True if we should explicitly preroll the section. Already reconciled with play direction. */
bool bSectionPreRoll : 1;
/** True if we should explicitly postroll the section. Already reconciled with play direction. */
bool bSectionPostRoll : 1;
/** True if the value of PrePostRollStartEndTime has been set, and refers to the time at which preroll will end. Already reconciled with play direction. */
bool bHasPreRollEndTime : 1;
/** True if the value of PrePostRollStartEndTime has been set, and refers to the time at which postroll started. Already reconciled with play direction. */
bool bHasPostRollStartTime : 1;
};
/** Helper class designed to abstract the complexity of calculating evaluation ranges for previous times and fixed time intervals */
struct FMovieScenePlaybackPosition
{
FMovieScenePlaybackPosition()
: InputRate(0,0), OutputRate(0,0), EvaluationType(EMovieSceneEvaluationType::WithSubFrames)
{}
/**
* @return Whether we are evaluating with sub frames, or frame-locked
*/
inline EMovieSceneEvaluationType GetEvaluationType() const
{
return EvaluationType;
}
/**
* @return The input frame rate that all frame times provided to this class will be interpreted as
*/
inline FFrameRate GetInputRate() const
{
return InputRate;
}
/**
* @return The output frame rate that all frame times returned from this class will be interpreted as
*/
inline FFrameRate GetOutputRate() const
{
return OutputRate;
}
public:
/**
* Assign the input and output rates that frame times should be interpreted as.
*
* @param InInputRate The framerate to interpret any frame time provided to this class
* @param InOutputRate The framerate to use when returning any frame range from this class
* @param InputEvaluationType Whether we're using frame-locked or sub-frame evaluation
*/
MOVIESCENE_API void SetTimeBase(FFrameRate InInputRate, FFrameRate InOutputRate, EMovieSceneEvaluationType InputEvaluationType);
/**
* Reset this position to the specified time.
* @note Future calls to 'PlayTo' will include this time in its resulting evaluation range
*/
MOVIESCENE_API void Reset(FFrameTime StartPos);
/**
* Get the last position that was set, in InputRate space
*/
FFrameTime GetCurrentPosition() const { return CurrentPosition; }
/**
* Get the last actual time that was evaluated during playback, in InputRate space.
*/
TOptional<FFrameTime> GetLastPlayEvalPostition() const { return PreviousPlayEvalPosition; }
public:
/**
* Jump to the specified input time.
* @note Will reset previous play position. Any subsequent call to 'PlayTo' will include NewPosition.
*
* @param NewPosition The new frame time to set, in InputRate space
* @return A range encompassing only the specified time, in OutputRate space.
*/
MOVIESCENE_API FMovieSceneEvaluationRange JumpTo(FFrameTime NewPosition, EPlayDirection PreferredDirection = EPlayDirection::Forwards);
/**
* Play from the previously evaluated play time, to the specified time
*
* @param NewPosition The new frame time to set, in InputRate space
* @return An evaluation range from the previously evaluated time to the specified time, in OutputRate space.
*/
MOVIESCENE_API FMovieSceneEvaluationRange PlayTo(FFrameTime NewPosition, EPlayDirection PreferredDirection = EPlayDirection::Forwards);
/**
* Get a range that encompasses the last evaluated range in OutputRate space.
* @return An optional evaluation range in OutputRate space.
*/
MOVIESCENE_API TOptional<FMovieSceneEvaluationRange> GetLastRange() const;
/**
* Get a range encompassing only the current time, if available (in OutputRate space)
* @return An optional evaluation range in OutputRate space.
*/
MOVIESCENE_API FMovieSceneEvaluationRange GetCurrentPositionAsRange() const;
private:
/**
* Check this class's invariants
*/
MOVIESCENE_API void CheckInvariants() const;
private:
/** The framerate to be used when interpreting frame time values provided to this class (i.e. moviescene display rate) */
FFrameRate InputRate;
/** The framerate to be used when returning frame time values from this class (i.e. moviescene tick resolution) */
FFrameRate OutputRate;
/** The type of evaluation to use */
EMovieSceneEvaluationType EvaluationType;
/** The current time position set, in 'InputRate' time-space. */
FFrameTime CurrentPosition;
/** The previously evaluated position when playing, in 'InputRate' time-space. */
TOptional<FFrameTime> PreviousPlayEvalPosition;
/** The previously evaluated range if available, in 'OutputRate' time-space */
TOptional<FMovieSceneEvaluationRange> LastRange;
};