// Copyright Epic Games, Inc. All Rights Reserved. #include "Evaluation/MovieSceneSequenceTransform.h" #include "MovieSceneTimeHelpers.h" #include "Misc/FrameRate.h" #include "Variants/MovieSceneTimeWarpGetter.h" #include "Variants/MovieSceneTimeWarpVariantPayloads.h" #include "MovieSceneTransformTypes.h" #include "MovieSceneTimeHelpers.h" #include "Algo/AllOf.h" #include "Algo/AnyOf.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneSequenceTransform) namespace UE::MovieScene { TRange TranslateRange(const TRange& InRange, FFrameTime Offset) { TRange Result = InRange; if (InRange.HasLowerBound()) { Result.SetLowerBoundValue(InRange.GetLowerBoundValue() + Offset); } if (InRange.HasUpperBound()) { Result.SetUpperBoundValue(InRange.GetUpperBoundValue() + Offset); } return Result; } void CorrectInsideOutRange(TRange& InOutRange) { if (InOutRange.HasUpperBound() && InOutRange.HasLowerBound()) { FFrameTime LowerBoundValue = InOutRange.GetLowerBoundValue(); if (LowerBoundValue > InOutRange.GetUpperBoundValue()) { InOutRange.SetLowerBoundValue(InOutRange.GetUpperBoundValue()); InOutRange.SetUpperBoundValue(LowerBoundValue); } } } FTransformTimeParams& FTransformTimeParams::HarvestBreadcrumbs(FMovieSceneTransformBreadcrumbs& OutBreadcrumbs) { OutBreadcrumbs.Reset(); Breadcrumbs = &OutBreadcrumbs; return *this; } FTransformTimeParams& FTransformTimeParams::AppendBreadcrumbs(FMovieSceneTransformBreadcrumbs& OutBreadcrumbs) { Breadcrumbs = &OutBreadcrumbs; return *this; } FTransformTimeParams& FTransformTimeParams::TrackCycleCounts(TOptional* OutCycleCounter) { CycleCount = OutCycleCounter; return *this; } FTransformTimeParams& FTransformTimeParams::IgnoreClamps() { bIgnoreClamps = true; return *this; } } // namespace UE::MovieScene; FString LexToString(const FMovieSceneSequenceTransform& InTransform) { TStringBuilder<256> Builder; Builder.Append(LexToString(InTransform.LinearTransform)); int32 NestedIndex = 0; for (const FMovieSceneNestedSequenceTransform& Nested : InTransform.NestedTransforms) { if (Nested.IsIdentity()) { Builder.Appendf(TEXT(" [ %d = "), NestedIndex); Nested.ToString(Builder); Builder.Append(TEXT(" ]")); } ++NestedIndex; } return Builder.ToString(); } FString LexToString(const FMovieSceneWarpCounter& InCounter) { if (InCounter.Num() == 0) { return FString(TEXT("[]")); } TStringBuilder<256> Builder; Builder.Append(TEXT("[")); int32 Index = 0; for (FFrameTime Breadcrumb : InCounter) { if (Index > 0) { Builder.Append(TEXT(",")); } Builder.Appendf(TEXT("%.3f"), Breadcrumb.AsDecimal()); ++Index; } Builder.Append(TEXT("]")); FString OutString = Builder.ToString(); return OutString; } FFrameTime operator*(FFrameTime InTime, const FMovieSceneSequenceTransform& RHS) { const uint32 NestedTransformsSize = RHS.NestedTransforms.Num(); if (NestedTransformsSize == 0) { return InTime * RHS.LinearTransform; } else { FFrameTime OutTime = InTime * RHS.LinearTransform; for (const FMovieSceneNestedSequenceTransform& NestedTransform : RHS.NestedTransforms) { OutTime = NestedTransform.TransformTime(OutTime); } return OutTime; } } FMovieSceneTransformBreadcrumbs::operator TArrayView() const { return Breadcrumbs; } PRAGMA_DISABLE_DEPRECATION_WARNINGS FMovieSceneWarpCounter::FMovieSceneWarpCounter() = default; FMovieSceneWarpCounter::FMovieSceneWarpCounter(const FMovieSceneWarpCounter&) = default; FMovieSceneWarpCounter& FMovieSceneWarpCounter::operator=(const FMovieSceneWarpCounter&) = default; FMovieSceneWarpCounter::FMovieSceneWarpCounter(FMovieSceneWarpCounter&&) = default; FMovieSceneWarpCounter& FMovieSceneWarpCounter::operator=(FMovieSceneWarpCounter&&) = default; FMovieSceneWarpCounter::~FMovieSceneWarpCounter() = default; PRAGMA_ENABLE_DEPRECATION_WARNINGS void FMovieSceneNestedSequenceTransform::PostSerialize(const FArchive& Ar) { if (Ar.IsLoading()) { TimeScale.MakeWeakUnsafe(); } } void FMovieSceneNestedSequenceTransform::ToString(TStringBuilderBase& OutBuilder) const { switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::FixedPlayRate: OutBuilder.Append(LexToString(FMovieSceneTimeTransform(Offset, TimeScale.AsFixedPlayRateFloat()))); return; // Explicit return because there's nothing else top do case EMovieSceneTimeWarpType::FixedTime: { FMovieSceneTimeWarpFixedFrame Value = TimeScale.AsFixedTime(); OutBuilder.Appendf(TEXT("Fixed Frame: %s"), *LexToString(FFrameTime(Value.FrameNumber) + Offset)); } break; case EMovieSceneTimeWarpType::FrameRate: { FFrameRate FrameRate = TimeScale.AsFrameRate().GetFrameRate(); OutBuilder.Appendf(TEXT("Frame Rate: [%i/%i]"), FrameRate.Numerator, FrameRate.Denominator); } break; case EMovieSceneTimeWarpType::Loop: { FMovieSceneTimeWarpLoop Value = TimeScale.AsLoop(); OutBuilder.Appendf(TEXT("Loop [%s:%s)"), *LexToString(-Offset), *LexToString(-Offset+Value.Duration)); } break; case EMovieSceneTimeWarpType::Clamp: { FMovieSceneTimeWarpClamp Value = TimeScale.AsClamp(); OutBuilder.Appendf(TEXT("Clamp [%s:%s)"), *LexToString(-Offset), *LexToString(-Offset+Value.Max)); } break; case EMovieSceneTimeWarpType::LoopFloat: { FMovieSceneTimeWarpLoopFloat Value = TimeScale.AsLoopFloat(); OutBuilder.Appendf(TEXT("Loop [%s:%s)"), *LexToString(-Offset), *LexToString(-Offset+FFrameTime::FromDecimal(Value.Duration))); } break; case EMovieSceneTimeWarpType::ClampFloat: { FMovieSceneTimeWarpClampFloat Value = TimeScale.AsClampFloat(); OutBuilder.Appendf(TEXT("Clamp [%s:%s)"), *LexToString(-Offset), *LexToString(-Offset+FFrameTime::FromDecimal(Value.Max))); } break; case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { OutBuilder.Append(Custom->GetName()); } break; } if (Offset != 0) { OutBuilder.Appendf(TEXT(" + %s"), *LexToString(Offset)); } } FFrameTime FMovieSceneNestedSequenceTransform::TransformTime(FFrameTime InTime) const { switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::FixedPlayRate: return InTime * FMovieSceneTimeTransform(Offset, TimeScale.AsFixedPlayRateFloat()); case EMovieSceneTimeWarpType::FixedTime: return TimeScale.AsFixedTime().FrameNumber + Offset; case EMovieSceneTimeWarpType::FrameRate: return ConvertFrameTime(InTime + Offset, TimeScale.AsFrameRate().GetFrameRate(), FFrameRate(1, 1)); case EMovieSceneTimeWarpType::Loop: return TimeScale.AsLoop().LoopTime(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::Clamp: return TimeScale.AsClamp().Clamp(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::LoopFloat: return TimeScale.AsLoopFloat().LoopTime(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::ClampFloat: return TimeScale.AsClampFloat().Clamp(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { return Custom->RemapTime(InTime + Offset); } break; } return InTime; } FFrameTime FMovieSceneNestedSequenceTransform::TransformTime(FFrameTime InTime, const UE::MovieScene::FTransformTimeParams& Params) const { switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::FixedPlayRate: return InTime * FMovieSceneTimeTransform(Offset, TimeScale.AsFixedPlayRateFloat()); case EMovieSceneTimeWarpType::FixedTime: return TimeScale.AsFixedTime().FrameNumber + Offset; case EMovieSceneTimeWarpType::FrameRate: return ConvertFrameTime(InTime + Offset, TimeScale.AsFrameRate().GetFrameRate(), FFrameRate(1, 1)); case EMovieSceneTimeWarpType::Loop: if (Params.CycleCount) { *Params.CycleCount = 0; return TimeScale.AsLoop().LoopTime(InTime + Offset, Params.CycleCount->GetValue()) - Offset; } else { return TimeScale.AsLoop().LoopTime(InTime + Offset) - Offset; } case EMovieSceneTimeWarpType::Clamp: if (Params.bIgnoreClamps) { break; } return TimeScale.AsClamp().Clamp(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::LoopFloat: return TimeScale.AsLoopFloat().LoopTime(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::ClampFloat: if (Params.bIgnoreClamps) { break; } return TimeScale.AsClampFloat().Clamp(InTime + Offset) - Offset; case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { return Custom->RemapTime(InTime + Offset); } break; } return InTime; } TRange FMovieSceneNestedSequenceTransform::ComputeTraversedHull(const TRange& InRange) const { using namespace UE::MovieScene; EMovieSceneTimeWarpType Type = TimeScale.GetType(); if (Type == EMovieSceneTimeWarpType::FixedPlayRate) { FMovieSceneTimeTransform LinearTransform(Offset, TimeScale.AsFixedPlayRate()); TRange Result = InRange * LinearTransform; CorrectInsideOutRange(Result); return Result; } else if (Type == EMovieSceneTimeWarpType::FixedTime) { FFrameTime FixedFrame = Offset + TimeScale.AsFixedTime().FrameNumber; return TRange::Inclusive(FixedFrame, FixedFrame); } else if (Type == EMovieSceneTimeWarpType::FrameRate) { FFrameRate Rate = TimeScale.AsFrameRate().GetFrameRate(); TRange Result = InRange; if (Result.HasLowerBound()) { Result.SetLowerBoundValue(ConvertFrameTime(Result.GetLowerBoundValue() + Offset, Rate, FFrameRate(1, 1))); } if (Result.HasUpperBound()) { Result.SetUpperBoundValue(ConvertFrameTime(Result.GetUpperBoundValue() + Offset, Rate, FFrameRate(1, 1))); } return Result; } // Handle other types TRange OffsetRange = TranslateRange(InRange, Offset); switch (Type) { case EMovieSceneTimeWarpType::Loop: { OffsetRange = TimeScale.AsLoop().ComputeTraversedHull(OffsetRange); break; } case EMovieSceneTimeWarpType::Clamp: { OffsetRange = TimeScale.AsClamp().ComputeTraversedHull(OffsetRange); break; } case EMovieSceneTimeWarpType::LoopFloat: { OffsetRange = TimeScale.AsLoopFloat().ComputeTraversedHull(OffsetRange); break; } case EMovieSceneTimeWarpType::ClampFloat: { OffsetRange = TimeScale.AsClampFloat().ComputeTraversedHull(OffsetRange); break; } case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { OffsetRange = Custom->ComputeTraversedHull(OffsetRange); } return OffsetRange; } OffsetRange = TranslateRange(OffsetRange, -Offset); return OffsetRange; } bool FMovieSceneNestedSequenceTransform::ExtractBoundariesWithinRange(const TRange& Range, const TFunctionRef& InVisitor) const { EMovieSceneTimeWarpType WarpType = TimeScale.GetType(); auto VisitorOffsetWrapper = [this, Range, InVisitor](FFrameTime Time) { // Factor the loop offset Time -= this->Offset; if (Range.Contains(Time)) { return InVisitor(Time); } return true; }; switch (WarpType) { case EMovieSceneTimeWarpType::Loop: return TimeScale.AsLoop().ExtractBoundariesWithinRange(Range, VisitorOffsetWrapper); case EMovieSceneTimeWarpType::LoopFloat: return TimeScale.AsLoopFloat().ExtractBoundariesWithinRange(Range, VisitorOffsetWrapper); default: return false; } } bool FMovieSceneNestedSequenceTransform::SupportsBoundaries() const { switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::Loop: case EMovieSceneTimeWarpType::LoopFloat: return true; default: return false; } } TOptional FMovieSceneNestedSequenceTransform::GetWarpDomain() const { UMovieSceneTimeWarpGetter* Getter = TimeScale.GetType() == EMovieSceneTimeWarpType::Custom ? TimeScale.AsCustom() : nullptr; if (Getter) { return Getter->GetDomain(); } return TOptional(); } FMovieSceneInverseNestedSequenceTransform FMovieSceneNestedSequenceTransform::Inverse() const { if (TimeScale.GetType() == EMovieSceneTimeWarpType::FixedPlayRate) { const double PlayRate = TimeScale.AsFixedPlayRate(); checkf(!FMath::IsNearlyZero(PlayRate), TEXT("Play rate cannot be zero! This should be expressed as a nullptr TimeScale with FLAG_Zero.")); FMovieSceneInverseNestedSequenceTransform InverseTransform; InverseTransform.Offset = -Offset / PlayRate; InverseTransform.TimeScale = 1.0 / PlayRate; return InverseTransform; } FMovieSceneInverseNestedSequenceTransform InverseTransform; InverseTransform.Offset = Offset; InverseTransform.TimeScale = TimeScale; return InverseTransform; } FMovieSceneTimeTransform FMovieSceneInverseNestedSequenceTransform::AsLinear() const { const float PlayRate = TimeScale.AsFixedPlayRateFloat(); checkf(!FMath::IsNearlyZero(PlayRate), TEXT("Play rate cannot be zero! This should be expressed as a nullptr TimeScale with FLAG_Zero.")); return FMovieSceneTimeTransform(Offset, TimeScale.AsFixedPlayRateFloat()); } bool FMovieSceneInverseNestedSequenceTransform::TransformTimeWithinRange(FFrameTime InTime, const TFunctionRef& InVisitor, FFrameTime RangeStart, FFrameTime RangeEnd) const { auto OffsetVisitor = [this, &InVisitor](FFrameTime VisitTime) { return InVisitor(VisitTime - this->Offset); }; FFrameTime TransformedTime; switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::FixedPlayRate: TransformedTime = InTime * AsLinear(); break; case EMovieSceneTimeWarpType::FixedTime: TransformedTime = TimeScale.AsFixedTime().FrameNumber + Offset; break; case EMovieSceneTimeWarpType::FrameRate: TransformedTime = ConvertFrameTime(InTime + Offset, FFrameRate(1, 1), TimeScale.AsFrameRate().GetFrameRate()); break; case EMovieSceneTimeWarpType::Loop: if (RangeStart.FrameNumber.Value != MIN_int32) { RangeStart += Offset; } if (RangeEnd.FrameNumber.Value != MAX_int32) { RangeEnd += Offset; } return TimeScale.AsLoop().InverseRemapTimeWithinRange(InTime + Offset, RangeStart, RangeEnd, OffsetVisitor); case EMovieSceneTimeWarpType::Clamp: if (InTime < -Offset && InTime > TimeScale.AsClamp().Max - Offset) { return true; } TransformedTime = InTime; break; case EMovieSceneTimeWarpType::LoopFloat: if (RangeStart.FrameNumber.Value != MIN_int32) { RangeStart += Offset; } if (RangeEnd.FrameNumber.Value != MAX_int32) { RangeEnd += Offset; } return TimeScale.AsLoopFloat().InverseRemapTimeWithinRange(InTime + Offset, RangeStart, RangeEnd, OffsetVisitor); case EMovieSceneTimeWarpType::ClampFloat: if (InTime < -Offset && InTime > FFrameTime::FromDecimal(TimeScale.AsClampFloat().Max) - Offset) { return true; } TransformedTime = InTime; break; case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { return Custom->InverseRemapTimeWithinRange(InTime, RangeStart, RangeEnd, OffsetVisitor); } return true; } if (TransformedTime >= RangeStart && TransformedTime <= RangeEnd) { return InVisitor(TransformedTime); } return true; } TOptional FMovieSceneInverseNestedSequenceTransform::TryTransformTime(FFrameTime InTime, FFrameTime Breadcrumb) const { return TryTransformTime(InTime, Breadcrumb, UE::MovieScene::FInverseTransformTimeParams()); } TOptional FMovieSceneInverseNestedSequenceTransform::TryTransformTime(FFrameTime InTime, FFrameTime Breadcrumb, const UE::MovieScene::FInverseTransformTimeParams& Params) const { using namespace UE::MovieScene; switch (TimeScale.GetType()) { case EMovieSceneTimeWarpType::FixedPlayRate: return InTime * FMovieSceneTimeTransform(Offset, TimeScale.AsFixedPlayRateFloat()); case EMovieSceneTimeWarpType::FixedTime: if (InTime == TimeScale.AsFixedTime().FrameNumber + Offset) { return InTime; } break; case EMovieSceneTimeWarpType::FrameRate: return ConvertFrameTime(InTime, FFrameRate(1, 1), TimeScale.AsFrameRate().GetFrameRate()) - Offset; case EMovieSceneTimeWarpType::Loop: { TOptional Result = TimeScale.AsLoop().InverseRemapTimeCycled(InTime + Offset, Breadcrumb + Offset, Params); if (Result) { return Result.GetValue() - Offset; } } break; case EMovieSceneTimeWarpType::Clamp: if (EnumHasAnyFlags(Params.Flags, EInverseEvaluateFlags::IgnoreClamps) || ( InTime >= -Offset && InTime <= TimeScale.AsClamp().Max - Offset) ) { return InTime; } break; case EMovieSceneTimeWarpType::LoopFloat: { TOptional Result = TimeScale.AsLoopFloat().InverseRemapTimeCycled(InTime + Offset, Breadcrumb + Offset, Params); if (Result) { return Result.GetValue() - Offset; } } break; case EMovieSceneTimeWarpType::ClampFloat: if (EnumHasAnyFlags(Params.Flags, EInverseEvaluateFlags::IgnoreClamps) || ( InTime >= -Offset && InTime <= FFrameTime::FromDecimal(TimeScale.AsClampFloat().Max) - Offset) ) { return InTime; } break; case EMovieSceneTimeWarpType::Custom: if (UMovieSceneTimeWarpGetter* Custom = TimeScale.AsCustom()) { TOptional Result = Custom->InverseRemapTimeCycled(InTime, Breadcrumb, Params); if (Result) { return Result.GetValue() - Offset; } } break; } return TOptional(); } TOptional FMovieSceneInverseSequenceTransform::TryTransformTime(FFrameTime InTime) const { return TryTransformTime(InTime, UE::MovieScene::FInverseTransformTimeParams()); } TOptional FMovieSceneInverseSequenceTransform::TryTransformTime(FFrameTime InTime, const UE::MovieScene::FInverseTransformTimeParams& Params) const { FFrameTime OutTime = InTime; for (const FMovieSceneInverseNestedSequenceTransform& NestedTransform : NestedTransforms) { FFrameTime Breadcrumb; TOptional NewTime = NestedTransform.TryTransformTime(OutTime, Breadcrumb, Params); if (!NewTime) { return TOptional(); } OutTime = NewTime.GetValue(); } return OutTime * LinearTransform; } TOptional FMovieSceneInverseSequenceTransform::TryTransformTime(FFrameTime InTime, const FMovieSceneTransformBreadcrumbs& InBreadcrumbs) const { return TryTransformTime(InTime, InBreadcrumbs, UE::MovieScene::FInverseTransformTimeParams()); } TOptional FMovieSceneInverseSequenceTransform::TryTransformTime(FFrameTime InTime, const FMovieSceneTransformBreadcrumbs& InBreadcrumbs, const UE::MovieScene::FInverseTransformTimeParams& Params) const { FFrameTime OutTime = InTime; int32 BreadcrumbIndex = InBreadcrumbs.Num() - 1; for (const FMovieSceneInverseNestedSequenceTransform& NestedTransform : NestedTransforms) { FFrameTime Breadcrumb; const bool bShouldHaveBreadcrumb = (InBreadcrumbs.GetMode() == EMovieSceneBreadcrumbMode::Dense || NestedTransform.NeedsBreadcrumb()); if (bShouldHaveBreadcrumb && InBreadcrumbs.IsValidIndex(BreadcrumbIndex)) { Breadcrumb = InBreadcrumbs[BreadcrumbIndex--]; } TOptional NewTime = NestedTransform.TryTransformTime(OutTime, Breadcrumb, Params); if (!NewTime) { return TOptional(); } OutTime = NewTime.GetValue(); } return OutTime * LinearTransform; } bool FMovieSceneInverseSequenceTransform::RecursiveTransformTimeWithinRange(int32 NestingIndex, FFrameTime InTime, const TFunctionRef& FinalVisitor, TArrayView StartBreadcrumbs, TArrayView EndBreadcrumbs) const { for ( ; NestingIndex < NestedTransforms.Num(); ++NestingIndex) { const FMovieSceneInverseNestedSequenceTransform& NestedTransform = NestedTransforms[NestingIndex]; // Linear transform is easy - keep looping them if (NestedTransform.IsLinear()) { InTime = InTime * NestedTransform.AsLinear(); } // Warped ranges may map to zero or more times in the outer sequence // so perform a complete recursive expansion on all of them else if (ensureMsgf(StartBreadcrumbs.Num() > 0 && EndBreadcrumbs.Num() > 0, TEXT("Breadcrumb count mismatch in inverse transform computation"))) { auto TransformNext = [this, FinalVisitor, NestingIndex, StartBreadcrumbs, EndBreadcrumbs](FFrameTime NextTime) { return this->RecursiveTransformTimeWithinRange( NestingIndex+1, NextTime, FinalVisitor, StartBreadcrumbs.LeftChop(1), EndBreadcrumbs.LeftChop(1) ); }; // RecursiveTransformTimeWithinRange will complete the recursion return NestedTransform.TransformTimeWithinRange( InTime, TransformNext, StartBreadcrumbs.Last(), EndBreadcrumbs.Last() ); } } return FinalVisitor(InTime * LinearTransform); } bool FMovieSceneInverseSequenceTransform::TransformFiniteRangeWithinRange(const TRange& InRange, TFunctionRef)> InVisitor, const FMovieSceneTransformBreadcrumbs& StartBreadcrumbs, const FMovieSceneTransformBreadcrumbs& EndBreadcrumbs) const { check(!InRange.GetLowerBound().IsOpen() && !InRange.GetUpperBound().IsOpen()); if (NestedTransforms.Num() == 0) { // Only one solution return InVisitor(InRange * LinearTransform); } TArray LowerBounds, UpperBounds; // Transform lower bounds auto VisitLower = [&LowerBounds](FFrameTime InFrameTime) { LowerBounds.Add(InFrameTime); return true; }; TransformTimeWithinRange(InRange.GetLowerBoundValue(), VisitLower, StartBreadcrumbs, EndBreadcrumbs); // Transform upper bounds auto VisitUpper = [&UpperBounds](FFrameTime InFrameTime) { UpperBounds.Add(InFrameTime); return true; }; TransformTimeWithinRange(InRange.GetUpperBoundValue(), VisitUpper, StartBreadcrumbs, EndBreadcrumbs); Algo::Sort(LowerBounds); Algo::Sort(UpperBounds); int32 LwrIndex = 0, UprIndex = 0; // Handle leading upper bounds - should only be one? while (UprIndex < UpperBounds.Num()) { if (LwrIndex < LowerBounds.Num() && UpperBounds[UprIndex] >= LowerBounds[LwrIndex]) { break; } // Maintain bound exclusivity TRange Result = InRange; if (UprIndex < UpperBounds.Num() - 1) { TRangeBound NewLower = Result.GetUpperBound(); NewLower.SetValue(UpperBounds[UprIndex + 1]); Result.SetLowerBound(TRangeBound::FlipInclusion(NewLower)); } else { Result.SetLowerBound(TRangeBound::Open()); } Result.SetUpperBoundValue(UpperBounds[UprIndex]); if (!InVisitor(Result)) { return false; } ++UprIndex; } // Handle finite ranges while (LwrIndex < LowerBounds.Num() && UprIndex < UpperBounds.Num()) { FFrameTime LowerBound = LowerBounds[LwrIndex]; TRange Result = InRange; Result.SetLowerBoundValue(LowerBound); // Skip any upper bounds that are < this lower bound while (UprIndex < UpperBounds.Num() && UpperBounds[UprIndex] <= LowerBound) { ++UprIndex; } if (UprIndex < UpperBounds.Num()) { Result.SetUpperBoundValue(UpperBounds[UprIndex]); if (!Result.IsEmpty() && !InVisitor(Result)) { return false; } } ++LwrIndex; ++UprIndex; } // Handle trailing lower bounds - there can be cases where there are multiple, especially in the case of nested // if a looping subsequences that have their end cropped while (LwrIndex < LowerBounds.Num()) { // Maintain bound exclusivity TRange Result = InRange; Result.SetLowerBoundValue(LowerBounds[LwrIndex]); if (LwrIndex < LowerBounds.Num()-1) { TRangeBound NewUpper = Result.GetLowerBound(); NewUpper.SetValue(LowerBounds[LwrIndex + 1]); Result.SetUpperBound(TRangeBound::FlipInclusion(NewUpper)); } else { Result.SetUpperBound(TRangeBound::Open()); } if (!Result.IsEmpty() && !InVisitor(Result)) { return false; } ++LwrIndex; } return true; } bool FMovieSceneInverseSequenceTransform::TransformTimeWithinRange(FFrameTime InTime, const TFunctionRef& InVisitor, const FMovieSceneTransformBreadcrumbs& StartBreadcrumbs, const FMovieSceneTransformBreadcrumbs& EndBreadcrumbs) const { return RecursiveTransformTimeWithinRange(0, InTime, InVisitor, StartBreadcrumbs, EndBreadcrumbs); } FMovieSceneTimeTransform FMovieSceneInverseSequenceTransform::AsLegacyLinearTimeTransform() const { FMovieSceneTimeTransform Result; for (const FMovieSceneInverseNestedSequenceTransform& Nested : NestedTransforms) { if (Nested.IsLinear()) { Result = Result * Nested.AsLinear(); } } // Linear Transform should apply last, and transform multiplication applies RtL return LinearTransform * Result; } PRAGMA_DISABLE_DEPRECATION_WARNINGS FMovieSceneNestedSequenceTransform::FMovieSceneNestedSequenceTransform() = default; FMovieSceneNestedSequenceTransform::FMovieSceneNestedSequenceTransform(const FMovieSceneNestedSequenceTransform&) = default; FMovieSceneNestedSequenceTransform& FMovieSceneNestedSequenceTransform::operator=(const FMovieSceneNestedSequenceTransform&) = default; FMovieSceneNestedSequenceTransform::FMovieSceneNestedSequenceTransform(FMovieSceneNestedSequenceTransform&&) = default; FMovieSceneNestedSequenceTransform& FMovieSceneNestedSequenceTransform::operator=(FMovieSceneNestedSequenceTransform&&) = default; FMovieSceneNestedSequenceTransform::~FMovieSceneNestedSequenceTransform() = default; PRAGMA_ENABLE_DEPRECATION_WARNINGS void FMovieSceneSequenceTransform::Add(FMovieSceneTimeTransform InTransform) { if (InTransform.IsIdentity()) { return; } if (IsLinear()) { LinearTransform = InTransform * LinearTransform; } else { NestedTransforms.Emplace(InTransform); } } void FMovieSceneSequenceTransform::Add(FMovieSceneNestedSequenceTransform InTransform) { if (InTransform.IsIdentity()) { return; } if (IsLinear() && InTransform.IsLinear()) { LinearTransform = InTransform.AsLinear() * LinearTransform; } else { NestedTransforms.Emplace(MoveTemp(InTransform)); } } void FMovieSceneSequenceTransform::Add(FFrameTime InOffset, FMovieSceneTimeWarpVariant&& InTimeWarp) { if (InTimeWarp.GetType() == EMovieSceneTimeWarpType::FixedPlayRate) { Add(FMovieSceneTimeTransform(InOffset, InTimeWarp.AsFixedPlayRateFloat())); } else { if (InTimeWarp.GetType() == EMovieSceneTimeWarpType::Custom) { UMovieSceneTimeWarpGetter* Getter = InTimeWarp.AsCustom(); if (Getter && Getter->IsMuted()) { return; } } NestedTransforms.Emplace(InOffset, MoveTemp(InTimeWarp)); } } FFrameTime FMovieSceneSequenceTransform::TransformTime(FFrameTime InTime) const { if (NestedTransforms.Num() == 0) { return InTime * LinearTransform; } FFrameTime OutTime = InTime * LinearTransform; for (const FMovieSceneNestedSequenceTransform& NestedTransform : NestedTransforms) { OutTime = NestedTransform.TransformTime(OutTime); } return OutTime; } FFrameTime FMovieSceneSequenceTransform::TransformTime(FFrameTime InTime, const UE::MovieScene::FTransformTimeParams& Params) const { FFrameTime OutTime = InTime * LinearTransform; if (Params.Breadcrumbs && Params.Breadcrumbs->GetMode() == EMovieSceneBreadcrumbMode::Dense) { Params.Breadcrumbs->AddBreadcrumb(OutTime); } if (NestedTransforms.Num() == 0) { return OutTime; } for (const FMovieSceneNestedSequenceTransform& NestedTransform : NestedTransforms) { if (Params.Breadcrumbs && (Params.Breadcrumbs->GetMode() == EMovieSceneBreadcrumbMode::Dense || NestedTransform.NeedsBreadcrumb()) ) { Params.Breadcrumbs->AddBreadcrumb(OutTime); } OutTime = NestedTransform.TransformTime(OutTime, Params); } return OutTime; } TRange FMovieSceneSequenceTransform::ComputeTraversedHull(const TRange& Range) const { using namespace UE::MovieScene; TRange Result = Range * LinearTransform; CorrectInsideOutRange(Result); for (const FMovieSceneNestedSequenceTransform& NestedTransform : NestedTransforms) { Result = NestedTransform.ComputeTraversedHull(Result); if (Result.IsEmpty()) { return Result; } } return Result; } TRange FMovieSceneSequenceTransform::ComputeTraversedHull(const TRange& Range) const { return ComputeTraversedHull(UE::MovieScene::ConvertToFrameTimeRange(Range)); } bool FMovieSceneSequenceTransform::ExtractBoundariesWithinRange(FFrameTime Start, FFrameTime End, const TFunctionRef& InVisitor) const { using namespace UE::MovieScene; FMovieSceneInverseSequenceTransform Inverse; FMovieSceneTransformBreadcrumbs StartBreadcrumbs, EndBreadcrumbs; TRange TraversedHull = TRange::All(); if (Start != MIN_int32) { TraversedHull.SetLowerBound(Start * LinearTransform); } if (End != MAX_int32) { TraversedHull.SetUpperBound(End * LinearTransform); } CorrectInsideOutRange(TraversedHull); for (int32 NestedIndex = 0; NestedIndex < NestedTransforms.Num(); ++NestedIndex) { const FMovieSceneNestedSequenceTransform& NestedTransform = NestedTransforms[NestedIndex]; // Find the first transform that has any boundaries if (!NestedTransform.SupportsBoundaries()) { if (NestedTransform.NeedsBreadcrumb()) { StartBreadcrumbs.AddBreadcrumb(TraversedHull.HasLowerBound() ? TraversedHull.GetLowerBoundValue() : FFrameTime(MIN_int32)); EndBreadcrumbs.AddBreadcrumb(TraversedHull.HasUpperBound() ? TraversedHull.GetUpperBoundValue() : FFrameTime(MAX_int32)); } TraversedHull = NestedTransform.ComputeTraversedHull(TraversedHull); continue; } FMovieSceneSequenceTransform RootToParentTransform = *this; RootToParentTransform.NestedTransforms.SetNum(NestedIndex); FMovieSceneInverseSequenceTransform ParentToRootTransform = RootToParentTransform.Inverse(); auto VisitWrapper = [&InVisitor, &ParentToRootTransform, &StartBreadcrumbs, &EndBreadcrumbs](FFrameTime InBoundary) { return ParentToRootTransform.TransformTimeWithinRange(InBoundary, InVisitor, StartBreadcrumbs, EndBreadcrumbs); }; return NestedTransform.ExtractBoundariesWithinRange(TraversedHull, VisitWrapper); } return false; } TOptional FMovieSceneSequenceTransform::FindFirstWarpDomain() const { for (const FMovieSceneNestedSequenceTransform& NestedTransform : NestedTransforms) { TOptional Domain = NestedTransform.GetWarpDomain(); if (Domain) { return Domain; } } return TOptional(); } void FMovieSceneSequenceTransform::AddLoop(FFrameNumber InStart, FFrameNumber InEnd) { check(InStart < InEnd); // Offset by -InStart because our looping variant can only loop from 0:Max NestedTransforms.Emplace(-InStart, FMovieSceneTimeWarpVariant(FMovieSceneTimeWarpLoop{ InEnd - InStart })); } bool FMovieSceneSequenceTransform::IsIdentity() const { return LinearTransform.IsIdentity() && Algo::AllOf(NestedTransforms, &FMovieSceneNestedSequenceTransform::IsIdentity); } FMovieSceneInverseSequenceTransform FMovieSceneSequenceTransform::Inverse() const { FMovieSceneInverseSequenceTransform Result; if (NestedTransforms.Num() == 0) { Result.LinearTransform = LinearTransform.Inverse(); return Result; } // Start accumulating the inverse transforms in reverse order. Result.NestedTransforms.Reserve(NestedTransforms.Num()); for(int Index = NestedTransforms.Num() - 1; Index >= 0; --Index) { const FMovieSceneNestedSequenceTransform& Nested = NestedTransforms[Index]; if (Nested.IsLinear()) { Result.LinearTransform = Result.LinearTransform * Nested.AsLinear().Inverse(); continue; } if (!Result.LinearTransform.IsIdentity()) { // If we have any linear transform that needs to get added to the stack before this one's inverse Result.NestedTransforms.Add(Result.LinearTransform); Result.LinearTransform = FMovieSceneTimeTransform(); } Result.NestedTransforms.Add(NestedTransforms[Index].Inverse()); } // Add the inverse of the main linear transform if not identity if (!LinearTransform.IsIdentity()) { Result.LinearTransform = LinearTransform.Inverse() * Result.LinearTransform; } return Result; } void FMovieSceneSequenceTransform::Append(const FMovieSceneSequenceTransform& Tail) { if (IsLinear()) { if (!Tail.LinearTransform.IsIdentity()) { LinearTransform = Tail.LinearTransform * LinearTransform; } } else if (!Tail.LinearTransform.IsIdentity()) { NestedTransforms.Add(Tail.LinearTransform); } NestedTransforms.Append(Tail.NestedTransforms); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::operator*(const FMovieSceneSequenceTransform& RHS) const { if (IsLinear() && RHS.IsLinear()) { // None of the transforms are warping... we can combine them into another linear transform. return FMovieSceneSequenceTransform(LinearTransform * RHS.LinearTransform); } else if (IsLinear()) { // LHS is linear, but RHS is warping. Since transforms are supposed to apply from right to left, // we need to append LHS at the "bottom" of RHS, i.e. add a new nested transform that's LHS. However // if LHS is identity, we have nothing to do, and if both LHS and RHS' deeper transform are linear, // we can combine both. FMovieSceneSequenceTransform Result(RHS); if (!LinearTransform.IsIdentity()) { FMovieSceneNestedSequenceTransform& LastNested = Result.NestedTransforms.Last(); if (LastNested.IsLinear()) { FMovieSceneTimeTransform NewLinear = LinearTransform * LastNested.AsLinear(); LastNested = FMovieSceneNestedSequenceTransform(NewLinear); } else { Result.NestedTransforms.Emplace(LinearTransform); } } return Result; } else if (RHS.IsLinear()) { // RHS isn't warping, but LHS is, so we combine the linear transform parts and start looping // from there. FMovieSceneSequenceTransform Result; Result.LinearTransform = LinearTransform * RHS.LinearTransform; Result.NestedTransforms = NestedTransforms; return Result; } else { // Both are looping, we need to combine them. Usually, a warping transform doesn't use its linear part, // because whatever linear placement/scaling it has would be in the linear part of the nested transform // struct. FMovieSceneSequenceTransform Result(RHS); const bool bHasOnlyNested = LinearTransform.IsIdentity(); if (!bHasOnlyNested) { Result.NestedTransforms.Add(FMovieSceneNestedSequenceTransform(LinearTransform)); } Result.NestedTransforms.Append(NestedTransforms); return Result; } } PRAGMA_DISABLE_DEPRECATION_WARNINGS bool FMovieSceneSequenceTransform::IsLooping() const { return Algo::AnyOf(NestedTransforms, &FMovieSceneNestedSequenceTransform::IsLooping); } void FMovieSceneSequenceTransform::TransformTime(FFrameTime InTime, FFrameTime& OutTime, FMovieSceneWarpCounter& OutWarpCounter) const { OutTime = TransformTime(InTime, UE::MovieScene::FTransformTimeParams().HarvestBreadcrumbs(OutWarpCounter)); } float FMovieSceneSequenceTransform::GetTimeScale() const { float TimeScale = LinearTransform.TimeScale; for (const FMovieSceneNestedSequenceTransform& NestedTransform : NestedTransforms) { if (NestedTransform.IsLinear()) { TimeScale *= NestedTransform.AsLinear().TimeScale; } } return TimeScale; } TRange FMovieSceneSequenceTransform::TransformRangeConstrained(const TRange& Range) const { return ComputeTraversedHull(Range); } TRange FMovieSceneSequenceTransform::TransformRangePure(const TRange& Range) const { return ComputeTraversedHull(Range); } TRange FMovieSceneSequenceTransform::TransformRangeUnwarped(const TRange& Range) const { return ComputeTraversedHull(Range); } TRange FMovieSceneSequenceTransform::TransformRangePure(const TRange& Range) const { using namespace UE::MovieScene; TRange TimeRange = TransformRangePure(ConvertRange(Range)); return ConvertRange(TimeRange); } TRange FMovieSceneSequenceTransform::TransformRangeUnwarped(const TRange& Range) const { using namespace UE::MovieScene; TRange TimeRange = ConvertRange(Range); TimeRange = TransformRangeUnwarped(TimeRange); return ConvertRange(TimeRange); } TRange FMovieSceneSequenceTransform::TransformRangeConstrained(const TRange& Range) const { using namespace UE::MovieScene; TRange TimeRange = ConvertRange(Range); TimeRange = TransformRangeConstrained(TimeRange); return ConvertRange(TimeRange); } FMovieSceneTimeTransform FMovieSceneSequenceTransform::InverseLinearOnly() const { ensureMsgf(!FMath::IsNearlyZero(LinearTransform.TimeScale), TEXT("Inverse of a zero timescale transform is undefined in a FMovieSceneTimeTransform. Please use InverseNoLooping for proper behavior.")); return LinearTransform.Inverse(); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::InverseNoLooping() const { return FMovieSceneSequenceTransform(); } FMovieSceneTimeTransform FMovieSceneSequenceTransform::InverseFromAllFirstWarps() const { return FMovieSceneTimeTransform(); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::InverseFromAllFirstLoops() const { return FMovieSceneSequenceTransform(); } FMovieSceneTimeTransform FMovieSceneSequenceTransform::InverseFromWarp(const FMovieSceneWarpCounter& WarpCounter) const { return FMovieSceneTimeTransform(); } FMovieSceneTimeTransform FMovieSceneSequenceTransform::InverseFromWarp(const TArrayView& WarpCounts) const { return FMovieSceneTimeTransform(); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::InverseFromLoop(const FMovieSceneWarpCounter& LoopCounter) const { return FMovieSceneSequenceTransform(); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::InverseFromLoop(const TArrayView& Breadcrumbs) const { return FMovieSceneSequenceTransform(); } FMovieSceneSequenceTransform FMovieSceneSequenceTransform::InverseFromLoop(const TArrayView& LoopCounts) const { return FMovieSceneSequenceTransform(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS