// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/UnrealString.h" #include "CoreTypes.h" #include "Math/NumericLimits.h" #include "Math/Range.h" #include "Math/RangeBound.h" #include "Math/UnrealMathUtility.h" #include "Misc/AssertionMacros.h" #include "Misc/FrameNumber.h" #include "Misc/FrameTime.h" class UMovieScene; struct FFrameRate; namespace UE { namespace MovieScene { struct IRetimingInterface; class TimeHelpers { public: /** * Migrate the frame times of the movie scene from the source frame rate to the destination frame rate */ static MOVIESCENE_API void MigrateFrameTimes(FFrameRate SourceRate, FFrameRate DestinationRate, UMovieScene* MovieScene, bool bApplyRecursively = false); static MOVIESCENE_API void MigrateFrameTimes(const IRetimingInterface& Retimer, UMovieScene* MovieScene, bool bApplyRecursively = false); }; /** * Return the first frame number included by the specified closed lower bound. For example, a bound of (0 would return 1, and [0 would return 0 */ inline FFrameNumber DiscreteInclusiveLower(const TRangeBound& InLowerBound) { check(!InLowerBound.IsOpen()); // Add one for exclusive lower bounds since they start on the next subsequent frame static const int32 Offsets[] = { 0, 1 }; const int32 OffsetIndex = (int32)InLowerBound.IsExclusive(); return InLowerBound.GetValue() + Offsets[OffsetIndex]; } /** * Return the first frame number included by the specified range. Assumes a closed lower bound. For example, a range of (0:10) would return 1, and [0:10] would return 0 */ inline FFrameNumber DiscreteInclusiveLower(const TRange& InRange) { return DiscreteInclusiveLower(InRange.GetLowerBound()); } /** * Return the first frame number that is not contained by the specified closed upper bound. For example, a bound of 10) would return 10, and 10] would return 11 */ inline FFrameNumber DiscreteExclusiveUpper(const TRangeBound& InUpperBound) { check(!InUpperBound.IsOpen()); // Add one for inclusive upper bounds since they finish on the next subsequent frame static const int32 Offsets[] = { 0, 1 }; const int32 OffsetIndex = (int32)InUpperBound.IsInclusive(); return InUpperBound.GetValue() + Offsets[OffsetIndex]; } /** * Return the first frame number not contained by the specified range. Assumes a closed upper bound. For example, a range of (0:10) would return 10, and [0:10] would return 11 */ inline FFrameNumber DiscreteExclusiveUpper(const TRange& InRange) { return DiscreteExclusiveUpper(InRange.GetUpperBound()); } /** * Make a new range that includes the given minimum and excludes the given maximum. * * @param MinInclusive The minimum value for the inclusive lower bound * @param MaxExclusive The maximum value for the exclusive upper bound * @return A new range. */ inline TRange MakeDiscreteRange(FFrameNumber MinInclusive, FFrameNumber MaxExclusive) { return TRange(TRangeBound::Inclusive(MinInclusive), TRangeBound::Exclusive(MaxExclusive)); } /** * Make a new range that includes both the lower and upper bounds of the given range. */ inline TRange MakeHullRange(const TRange& InRange) { return TRange( InRange.HasLowerBound() ? TRangeBound::Inclusive(InRange.GetLowerBoundValue()) : TRangeBound::Open(), InRange.HasUpperBound() ? TRangeBound::Inclusive(InRange.GetUpperBoundValue()) : TRangeBound::Open()); } /** * Make a new range that includes both the given minimum and maximum. * * @param MinInclusive The minimum value for the inclusive lower bound * @param MaxInclusive The maximum value for the inclusive lower bound * @return A new range. */ inline TRange MakeHullRange(FFrameNumber MinInclusive, FFrameNumber MaxInclusive) { return TRange(TRangeBound::Inclusive(MinInclusive), TRangeBound::Inclusive(MaxInclusive)); } /** * Return whether the given range is empty or zero frames wide */ inline bool IsEmptyOrZeroSize(const TRange& InRange) { return InRange.IsEmpty() || InRange.Size() == 0; } /** * Make a new range using the specified lower bound, and a given size. */ inline TRange MakeDiscreteRangeFromLower(const TRangeBound& InLowerBound, int32 DiscreteSize) { check(!InLowerBound.IsOpen()); // Add one for exclusive lower bounds to ensure we end up with a range of the correct discrete size static const int32 Offsets[] = { 0, 1 }; const int32 OffsetIndex = (int32)InLowerBound.IsExclusive(); const FFrameNumber ExclusiveUpperValue = InLowerBound.GetValue() + DiscreteSize + Offsets[OffsetIndex]; return TRange(InLowerBound, TRangeBound::Exclusive(ExclusiveUpperValue)); } /** * Make a new range using the specified upper bound, and a given size. */ inline TRange MakeDiscreteRangeFromUpper(const TRangeBound& InUpperBound, int32 DiscreteSize) { check(!InUpperBound.IsOpen()); // Add one for inclusve upper bounds to ensure we end up with a range of the correct discrete size static const int32 Offsets[] = { 0, 1 }; const int32 OffsetIndex = (int32)InUpperBound.IsInclusive(); const FFrameNumber InclusiveLowerValue = InUpperBound.GetValue() - DiscreteSize + Offsets[OffsetIndex]; return TRange(TRangeBound::Inclusive(InclusiveLowerValue), InUpperBound); } /** * Calculate the size of a discrete frame range, taking into account inclusive/exclusive boundaries. * * @param InRange The range to calculate for. Must be a frinite range. * @return The size of the range (considering inclusive and exclusive boundaries) */ inline int32 DiscreteSize(const TRange& InRange) { return (int64)DiscreteExclusiveUpper(InRange).Value - (int64)DiscreteInclusiveLower(InRange).Value; } /** * Check whether the specified range contains any integer frame numbers or not */ inline bool DiscreteRangeIsEmpty(const TRange& InRange) { if (InRange.GetLowerBound().IsOpen() || InRange.GetUpperBound().IsOpen()) { return false; } // From here on we're handling ranges of the form [x,y], [x,y), (x,y] and (x,y) const bool bLowerInclusive = InRange.GetLowerBound().IsInclusive(); const bool bUpperInclusive = InRange.GetUpperBound().IsInclusive(); if (bLowerInclusive) { // Lower is inclusive return bUpperInclusive ? InRange.GetLowerBoundValue() > InRange.GetUpperBoundValue() // [x, y] - empty if x > y : InRange.GetLowerBoundValue() >= InRange.GetUpperBoundValue(); // [x, y) - empty if x >= y } else { // Lower is exclusive return bUpperInclusive ? InRange.GetLowerBoundValue() >= InRange.GetUpperBoundValue() // (x, y] - empty if x >= y : InRange.GetLowerBoundValue() >= InRange.GetUpperBoundValue()-1; // (x, y) - empty if x >= y-1 } } /** * Dilate the specified range by adding a specific size to the lower and upper bounds (if closed) */ template inline TRange DilateRange(const TRange& InRange, T LowerAmount, T UpperAmount) { TRangeBound LowerBound = InRange.GetLowerBound(); TRangeBound UpperBound = InRange.GetUpperBound(); return TRange( LowerBound.IsOpen() ? TRangeBound::Open() : LowerBound.IsInclusive() ? TRangeBound::Inclusive(LowerBound.GetValue() + LowerAmount) : TRangeBound::Exclusive(LowerBound.GetValue() + LowerAmount), UpperBound.IsOpen() ? TRangeBound::Open() : UpperBound.IsInclusive() ? TRangeBound::Inclusive(UpperBound.GetValue() + UpperAmount) : TRangeBound::Exclusive(UpperBound.GetValue() + UpperAmount) ); } /** * Expand the specified range by subtracting the specified amount from the lower bound, and adding it to the upper bound */ template inline TRange ExpandRange(const TRange& InRange, T Amount) { return DilateRange(InRange, -Amount, Amount); } /** * Translate the specified range by adding the specified amount to both bounds. */ template inline TRange TranslateRange(const TRange& InRange, T Amount) { return DilateRange(InRange, Amount, Amount); } /** * Clamp the specified time to a range */ inline FFrameTime ClampToDiscreteRange(FFrameTime InTime, const TRange& InRange) { FFrameTime MinTime = InRange.GetLowerBound().IsClosed() ? DiscreteInclusiveLower(InRange) : FFrameTime(TNumericLimits::Lowest()); FFrameTime MaxTime = FFrameTime(InRange.GetUpperBound().IsClosed() ? DiscreteExclusiveUpper(InRange)-1 : TNumericLimits::Max(), 0.99999994f); return FMath::Clamp(InTime, MinTime, MaxTime); } /** * Convert a frame number range into a frame time range */ inline TRange ConvertToFrameTimeRange(const TRange& InRange) { TRange Result; if (InRange.HasLowerBound()) { if (InRange.GetLowerBound().IsInclusive()) { Result.SetLowerBound(TRangeBound::Inclusive(InRange.GetLowerBoundValue())); } else { Result.SetLowerBound(TRangeBound::Exclusive(InRange.GetLowerBoundValue())); } } if (InRange.HasUpperBound()) { if (InRange.GetUpperBound().IsInclusive()) { Result.SetUpperBound(TRangeBound::Inclusive(InRange.GetUpperBoundValue())); } else { Result.SetUpperBound(TRangeBound::Exclusive(InRange.GetUpperBoundValue())); } } return Result; } inline TRange ConvertToFrameTimeRange(const TRange& InRange) { TRange Result; if (InRange.HasLowerBound()) { if (InRange.GetLowerBound().IsInclusive()) { Result.SetLowerBound(TRangeBound::Inclusive(FFrameTime::FromDecimal(InRange.GetLowerBoundValue()))); } else { Result.SetLowerBound(TRangeBound::Exclusive(FFrameTime::FromDecimal(InRange.GetLowerBoundValue()))); } } if (InRange.HasUpperBound()) { if (InRange.GetUpperBound().IsInclusive()) { Result.SetUpperBound(TRangeBound::Inclusive(FFrameTime::FromDecimal(InRange.GetUpperBoundValue()))); } else { Result.SetUpperBound(TRangeBound::Exclusive(FFrameTime::FromDecimal(InRange.GetUpperBoundValue()))); } } return Result; } inline TRange ConvertToDiscreteRange(const TRange& InRange) { TRange Result; if (InRange.HasLowerBound()) { // A frame time of (10.5 does not include _all_ of frame 10, so we can't include that frame FFrameTime LowerBound = InRange.GetLowerBoundValue(); if (InRange.GetLowerBound().IsInclusive() && LowerBound.GetSubFrame() == 0.f) { Result.SetLowerBound(TRangeBound::Inclusive(LowerBound.GetFrame())); } else { Result.SetLowerBound(TRangeBound::Inclusive(LowerBound.GetFrame()+1)); } } if (InRange.HasUpperBound()) { FFrameTime UpperBound = InRange.GetUpperBoundValue(); Result.SetUpperBound(TRangeBound::Exclusive(UpperBound.GetFrame())); } return Result; } /** * Converts a range from one type of bounds to another. The output bounds type must be implicitly * constructable from the input bounds type. */ template inline TRange ConvertRange(const TRange& Range) { const TRangeBound SourceLower = Range.GetLowerBound(); TRangeBound DestLower = SourceLower.IsOpen() ? TRangeBound() : SourceLower.IsInclusive() ? TRangeBound::Inclusive(SourceLower.GetValue()) : TRangeBound::Exclusive(SourceLower.GetValue()); const TRangeBound SourceUpper = Range.GetUpperBound(); TRangeBound DestUpper = SourceUpper.IsOpen() ? TRangeBound() : SourceUpper.IsInclusive() ? TRangeBound::Inclusive(SourceUpper.GetValue()) : TRangeBound::Exclusive(SourceUpper.GetValue()); return TRange(DestLower, DestUpper); } // Specialization of ConvertRange for round down FFrameTime to FFrameNumber. template<> inline TRange ConvertRange(const TRange& Range) { const TRangeBound SourceLower = Range.GetLowerBound(); TRangeBound DestLower = SourceLower.IsOpen() ? TRangeBound() : SourceLower.IsInclusive() ? TRangeBound::Inclusive(SourceLower.GetValue().FloorToFrame()) : TRangeBound::Exclusive(SourceLower.GetValue().FloorToFrame()); const TRangeBound SourceUpper = Range.GetUpperBound(); TRangeBound DestUpper = SourceUpper.IsOpen() ? TRangeBound() : SourceUpper.IsInclusive() ? TRangeBound::Inclusive(SourceUpper.GetValue().FloorToFrame()) : TRangeBound::Exclusive(SourceUpper.GetValue().FloorToFrame()); return TRange(DestLower, DestUpper); } } // namespace MovieScene } // namespace UE inline FString LexToString(const TRange& InRange) { TRangeBound SourceLower = InRange.GetLowerBound(); TRangeBound SourceUpper = InRange.GetUpperBound(); return *FString::Printf(TEXT("%s,%s"), SourceLower.IsOpen() ? TEXT("[-inf") : SourceLower.IsInclusive() ? *FString::Printf(TEXT("[%i"), SourceLower.GetValue().Value) : *FString::Printf(TEXT("(%i"), SourceLower.GetValue().Value), SourceUpper.IsOpen() ? TEXT("+inf]") : SourceUpper.IsInclusive() ? *FString::Printf(TEXT("%i]"), SourceUpper.GetValue().Value) : *FString::Printf(TEXT("%i)"), SourceUpper.GetValue().Value) ); }