// Copyright Epic Games, Inc. All Rights Reserved. #include "MovieSceneInterpolatingPointsDrawTask.h" #include "Cache/MovieSceneCachedCurve.h" #include "Channels/MovieSceneDoubleChannel.h" #include "Channels/MovieSceneFloatChannel.h" #include "Channels/MovieScenePiecewiseCurve.h" namespace UE::MovieSceneTools { template struct FMovieSceneInterpolatingPointsDrawTask; template struct FMovieSceneInterpolatingPointsDrawTask; template FMovieSceneInterpolatingPointsDrawTask::FMovieSceneInterpolatingPointsDrawTask( const TSharedRef>& CachedCurve, const TFunction, TArray)>& InCallback) : Callback(InCallback) , ScreenSpace(CachedCurve->GetScreenSpace()) , TickResolution(CachedCurve->GetTickResolution()) , TimeThreshold(CachedCurve->GetTimeThreshold()) , ValueThreshold(CachedCurve->GetValueThreshold()) , PiecewiseCurve(CachedCurve->GetPiecewiseCurve()) { const TArray& Times = CachedCurve->GetTimes(); const TArray& Values = CachedCurve->GetValues(); if (!ensureMsgf(Times.Num() > 1, TEXT("Curve paint tasks should only be created for curves with more than one key"))) { SetFlags(ECurvePainterTaskStateFlags::Completed); return; } // Remember key points KeyPoints.Reserve(Times.Num()); for (int32 DataIndex = 0; DataIndex < Times.Num(); DataIndex++) { KeyPoints.Emplace(Times[DataIndex] / TickResolution, double(Values[DataIndex].Value)); } InterpolatingPoints = KeyPoints; } template void FMovieSceneInterpolatingPointsDrawTask::RefineFullRangeInterpolatingPoints() { // Make sure there's no concurrency check(!bWorking); bWorking = true; const int32 OldSize = InterpolatingPoints.Num(); RefineFullRangeInterpolatingPointsInternal(); const int32 NewSize = InterpolatingPoints.Num(); if (OldSize == NewSize) { // Remove straight lines to reduce slate elements needed to draw the curve const float PixelArea = FMath::Pow(FMath::Min(TimeThreshold, ValueThreshold), 2.f); for (int32 PointIndex = 0; PointIndex < InterpolatingPoints.Num() - 3;) { const FVector2D& First = InterpolatingPoints[0]; const FVector2D& Second = InterpolatingPoints[1]; const FVector2D& Third = InterpolatingPoints[2]; const float LineArea = FMath::Abs(First.X * (Second.Y - Third.Y) + Second.X * (Third.Y - First.Y) + Third.X * (First.Y - Third.Y)); if (LineArea < PixelArea) { InterpolatingPoints.RemoveAt(PointIndex + 1); } else { PointIndex++; } } InvokeCallback(); SetFlags(ECurvePainterTaskStateFlags::Completed); } bWorking = false; } template void FMovieSceneInterpolatingPointsDrawTask::SetFlags(ECurvePainterTaskStateFlags NewFlags) { StateFlags.store(NewFlags); } template bool FMovieSceneInterpolatingPointsDrawTask::HasAnyFlags(ECurvePainterTaskStateFlags Flags) const { return EnumHasAnyFlags(StateFlags.load(), Flags); } template void FMovieSceneInterpolatingPointsDrawTask::RefineFullRangeInterpolatingPointsInternal() { constexpr float InterpTimes[] = { 0.25f, 0.5f, 0.6f }; for (int32 Index = 0; Index < InterpolatingPoints.Num() - 1; Index++) { const FVector2D& Lower = InterpolatingPoints[Index]; const FVector2D& Upper = InterpolatingPoints[Index + 1]; if ((Upper.X - Lower.X) >= TimeThreshold) { bool bSegmentIsLinear = true; FVector2D Evaluated[UE_ARRAY_COUNT(InterpTimes)] = { FVector2D::ZeroVector }; for (int32 InterpIndex = 0; InterpIndex < UE_ARRAY_COUNT(InterpTimes); ++InterpIndex) { double& EvalTime = Evaluated[InterpIndex].X; EvalTime = FMath::Lerp(Lower.X, Upper.X, InterpTimes[InterpIndex]); double Value = 0.0; PiecewiseCurve->Evaluate(EvalTime * TickResolution, Value); const double LinearValue = FMath::Lerp(Lower.Y, Upper.Y, InterpTimes[InterpIndex]); if (bSegmentIsLinear) { bSegmentIsLinear = FMath::IsNearlyEqual(Value, LinearValue, ValueThreshold); } Evaluated[InterpIndex].Y = Value; } if (!bSegmentIsLinear) { // Add the point InterpolatingPoints.Insert(Evaluated, UE_ARRAY_COUNT(Evaluated), Index + 1); --Index; } } } } template void FMovieSceneInterpolatingPointsDrawTask::InvokeCallback() { TArray KeyOffsets; int32 InterpolatingPointIndex = 0; for (const FVector2D& KeyPoint : KeyPoints) { while ( InterpolatingPoints.IsValidIndex(InterpolatingPointIndex) && KeyPoint.X > InterpolatingPoints[InterpolatingPointIndex].X) { InterpolatingPointIndex++; } if (InterpolatingPoints.IsValidIndex(InterpolatingPointIndex)) { KeyOffsets.Add(InterpolatingPointIndex); } else if (!InterpolatingPoints.IsEmpty()) { KeyOffsets.Add(InterpolatingPoints.Num() - 1); } } Callback(InterpolatingPoints, KeyOffsets); } }