UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
MovieSceneDrawBezierCurve.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
5#include "Curves/RealCurve.h"
6#include "Misc/FrameRate.h"
8#include <type_traits>
9
10namespace UE::MovieScene
11{
13 template <typename ChannelType>
15 std::is_same_v<ChannelType, FMovieSceneDoubleChannel> ||
16 std::is_same_v<ChannelType, FMovieSceneFloatChannel>;
17
18 // Fwd
19 namespace Private
20 {
21 template <typename ChannelType, typename ChannelValueType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
23
24 template <typename ChannelType, typename ChannelValueType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
25 static void PopulateCurvePoints(const ChannelType& InChannel, const FFrameRate& InTickResolution, const double InTimeThreshold, const CurveValueType InValueThreshold, const double InStartTimeSeconds, const double InEndTimeSeconds, TArray<TTuple<double, double>>& OutPoints);
26
27 template <typename ChannelType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
28 static void RefineCurvePoints(const ChannelType& InChannel, FFrameRate InTickResolution, double TimeThreshold, CurveValueType ValueThreshold, TArray<TTuple<double, double>>& OutPoints);
29 }
30
42 template <typename ChannelType> requires CSupportsBezierCurvePainting<ChannelType>
43 static void DrawBezierCurve(
44 const ChannelType& InChannel,
46 const double InTimeThreshold,
47 const double InValueThreshold,
48 const double InStartTimeSeconds,
49 const double InEndTimeSeconds,
51 {
53 using ChannelValueType = typename ChannelType::ChannelValueType;
54
56 using CurveValueType = typename ChannelType::CurveValueType;
57
58 const ERichCurveExtrapolation PreInfinityExtrapolation = InChannel.PreInfinityExtrap;
59 const ERichCurveExtrapolation PostInfinityExtrapolation = InChannel.PostInfinityExtrap;
60
61 const TArrayView<const FFrameNumber> Times = InChannel.GetTimes();
62 const TArrayView<const ChannelValueType> Values = InChannel.GetValues();
63
64 const bool bComplexPreInfinityExtrapolation =
65 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Cycle ||
66 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ||
67 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
68
69 const bool bComplexPostInfinityExtrapolation =
70 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Cycle ||
71 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ||
72 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
73
75 (bComplexPreInfinityExtrapolation || bComplexPostInfinityExtrapolation) &&
76 (Times.Num() > 1 && Values.Num() > 1);
77
79 {
88 }
89 else
90 {
91 Private::PopulateCurvePoints<ChannelType, ChannelValueType, CurveValueType>(
92 InChannel,
99 }
100 }
101
102 namespace Private
103 {
104 /*
105 * Populate the specified array with times and values that represent the smooth interpolation of
106 * the given channel across the specified range.
107 *
108 * Mind this algo only works correctly within in curve range, from the first to the last key.
109 * Mind when pre-/post-infinity is complex, this may not draw the first and the last point correctly when time is near bounds.
110 *
111 * @param InChannel The Movie Scene Channel for which the curve should be painted.
112 * @param InTickResolution The Movie Scene's current tick resolution
113 * @param InTimeThreshold The time that is visible as pixel delta on screen
114 * @param InValueThreshold The value that is visible as pixel delta on screen
115 * @param InStartTimeSeconds The desired start time in seconds
116 * @param InEndTimeSeconds The desired end time in seconds
117 * @param OutInterpolatingPoints The resulting interpolating points of the curve
118 */
119 template <typename ChannelType, typename ChannelValueType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
120 static void PopulateCurvePoints(
121 const ChannelType& InChannel,
123 const double InTimeThreshold,
124 const CurveValueType InValueThreshold,
125 const double InStartTimeSeconds,
126 const double InEndTimeSeconds,
128 {
129 const FFrameNumber StartFrame = (InStartTimeSeconds * InTickResolution).FloorToFrame();
130 const FFrameNumber EndFrame = (InEndTimeSeconds * InTickResolution).CeilToFrame();
131
132 const TArrayView<const FFrameNumber> Times = InChannel.GetTimes();
133 const TArrayView<const ChannelValueType> Values = InChannel.GetValues();
134
135 const int32 StartingIndex = Algo::UpperBound(Times, StartFrame);
137
138 // Add the lower bound of the visible space
139 CurveValueType EvaluatedValue;
140 if (InChannel.Evaluate(StartFrame, EvaluatedValue))
141 {
142 OutPoints.Emplace(StartFrame / InTickResolution, static_cast<double>(EvaluatedValue));
143 }
144
145 // Add all keys in-between
146 for (int32 KeyIndex = StartingIndex; KeyIndex < EndingIndex; ++KeyIndex)
147 {
148 OutPoints.Emplace(Times[KeyIndex] / InTickResolution, static_cast<double>(Values[KeyIndex].Value));
149 }
150
151 // Add the upper bound of the visible space
152 if (InChannel.Evaluate(EndFrame, EvaluatedValue))
153 {
154 OutPoints.Emplace(EndFrame / InTickResolution, static_cast<double>(EvaluatedValue));
155 }
156
157 int32 OldSize = OutPoints.Num();
158 do
159 {
160 OldSize = OutPoints.Num();
161 Private::RefineCurvePoints<ChannelType, CurveValueType>(InChannel, InTickResolution, InTimeThreshold, InValueThreshold, OutPoints);
162 } while (OldSize != OutPoints.Num());
163 }
164
176 template <typename ChannelType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
177 static void RefineCurvePoints(
178 const ChannelType& InChannel,
180 double InTimeThreshold,
181 CurveValueType InValueThreshold,
183 {
184 const float InterpTimes[] = { 0.25f, 0.5f, 0.6f };
185
186 for (int32 Index = 0; Index < OutPoints.Num() - 1; ++Index)
187 {
190
191 if ((Upper.Get<0>() - Lower.Get<0>()) >= InTimeThreshold)
192 {
193 bool bSegmentIsLinear = true;
194
196
198 {
199 double& EvalTime = Evaluated[InterpIndex].Get<0>();
200
201 EvalTime = FMath::Lerp(Lower.Get<0>(), Upper.Get<0>(), InterpTimes[InterpIndex]);
202
203 CurveValueType Value = 0.0;
204 InChannel.Evaluate(EvalTime * InTickResolution, Value);
205
206 const CurveValueType LinearValue = FMath::Lerp(Lower.Get<1>(), Upper.Get<1>(), InterpTimes[InterpIndex]);
208 {
210 }
211
212 Evaluated[InterpIndex].Get<1>() = Value;
213 }
214
215 if (!bSegmentIsLinear)
216 {
217 // Add the point
219 --Index;
220 }
221 }
222 }
223 }
224
229 template <typename ChannelType, typename ChannelValueType, typename CurveValueType> requires CSupportsBezierCurvePainting<ChannelType>
232 {
233 public:
245 const ChannelType& InChannel,
247 const double& InTimeThreshold,
248 const double& InValueThreshold,
249 const double& InStartTimeSeconds,
250 const double& InEndTimeSeconds,
252 : Channel(InChannel)
253 , TickResolution(InTickResolution)
254 , TimeThreshold(InTimeThreshold)
255 , ValueThreshold(InValueThreshold)
256 , StartTimeSeconds(InStartTimeSeconds)
257 , EndTimeSeconds(InEndTimeSeconds)
258 {
259 PreInfinityExtrapolation = InChannel.PreInfinityExtrap;
260 PostInfinityExtrapolation = InChannel.PostInfinityExtrap;
261
262 const TArrayView<const FFrameNumber> Times = InChannel.GetTimes();
263 const TArrayView<const ChannelValueType> Values = InChannel.GetValues();
264
265 bComplexPreInfinityExtrapolation =
266 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Cycle ||
267 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ||
268 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
269
270 bComplexPostInfinityExtrapolation =
271 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Cycle ||
272 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ||
273 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
274
276 (bComplexPreInfinityExtrapolation || bComplexPostInfinityExtrapolation) &&
277 (Times.Num() > 1 && Values.Num() > 1);
278
280 {
282 InChannel,
289
290 return;
291 }
292
293 CurveStartSeconds = Times[0] / TickResolution;
294 CurveEndSeconds = Times.Last() / TickResolution;
295 CurveDurationSeconds = CurveEndSeconds - CurveStartSeconds;
296
297 VisibleCurveStartSeconds = FMath::Max(CurveStartSeconds, StartTimeSeconds);
298 VisibleCurveEndSeconds = FMath::Min(CurveEndSeconds, EndTimeSeconds);
299
300 PreInfinityDuration = CurveStartSeconds - StartTimeSeconds;
301 PostInfinityDuration = EndTimeSeconds - CurveEndSeconds;
302
303 bPreInfinityVisible = PreInfinityDuration > 0.0;
304 bPostInfinityVisible = PostInfinityDuration > 0.0;
305
306 bPreInfinityOscillates = PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
307 bPostInfinityOscillates = PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate;
308
309 // Find the interpolation range that is required to draw the curve, but also pre- and post-infinity extrapolation
312 GetInterpolationRangeSeconds(InterpolationStartSeconds, InterpolationEndSeconds);
313
316 InChannel,
317 TickResolution,
318 TimeThreshold,
319 ValueThreshold,
323
324 // Handle the case where PopulateCurvePoints overdraws to frame time. This would be problematic as it can hold
325 // evaluated values from pre-/post-infinity, e.g. a cycled value, instead of the last value of the curve.
326 if (!CurveInterpolatingPoints.IsEmpty())
327 {
328 if (CurveInterpolatingPoints[0].Get<0>() < CurveStartSeconds)
329 {
330 CurveInterpolatingPoints[0] = MakeTuple(CurveStartSeconds, static_cast<double>(Values[0].Value));
331 }
332
333 if (CurveInterpolatingPoints.Last().Get<0>() > CurveEndSeconds)
334 {
335 CurveInterpolatingPoints.Last() = MakeTuple(CurveEndSeconds, static_cast<double>(Values.Last().Value));
336 }
337 }
338
339 // Draw the curve
340 if (bPreInfinityVisible)
341 {
342 if (bComplexPreInfinityExtrapolation)
343 {
344 DrawPreInfinityExtrapolationComplex(CurveInterpolatingPoints, OutInterpolatingPoints);
345 }
346 else
347 {
348 DrawPreInfinityExtrapolationLine(CurveInterpolatingPoints, OutInterpolatingPoints);
349 }
350 }
351
353
354 if (bPostInfinityVisible)
355 {
356 if (bComplexPostInfinityExtrapolation)
357 {
358 DrawPostInfinityExtrapolationComplex(CurveInterpolatingPoints, OutInterpolatingPoints);
359 }
360 else
361 {
362 DrawPostInfinityExtrapolationLine(CurveInterpolatingPoints, OutInterpolatingPoints);
363 }
364 }
365 }
366
367 private:
369 const ChannelType& Channel;
370
372 const FFrameRate TickResolution;
373
375 const double TimeThreshold = 0.0;
376
378 const double ValueThreshold = 0.0;
379
381 const double StartTimeSeconds = 0.0;
382
384 const double EndTimeSeconds = 0.0;
385
387 ERichCurveExtrapolation PreInfinityExtrapolation = ERichCurveExtrapolation::RCCE_None;
388
390 ERichCurveExtrapolation PostInfinityExtrapolation = ERichCurveExtrapolation::RCCE_None;
391
393 bool bComplexPreInfinityExtrapolation = false;
394
396 bool bComplexPostInfinityExtrapolation = false;
397
399 double CurveStartSeconds = 0.0;
400
402 double CurveEndSeconds = 0.0;
403
405 double CurveDurationSeconds = 0.0;
406
408 double VisibleCurveStartSeconds = 0.0;
409
411 double VisibleCurveEndSeconds = 0.0;
412
414 double PreInfinityDuration = 0.0;
415
417 double PostInfinityDuration = 0.0;
418
420 bool bPreInfinityVisible = false;
421
423 bool bPostInfinityVisible = false;
424
426 bool bPreInfinityOscillates = false;
427
429 bool bPostInfinityOscillates = false;
430
435 void GetInterpolationRangeSeconds(double& OutInterpolationStartSeconds, double& OutInterpolationEndSeconds)
436 {
437 // Translate pre- and post-infinity ranges to curve range
438 const double TranslatedPreInfinityStartSeconds = [this]()
439 {
440 if (bPreInfinityVisible && bComplexPreInfinityExtrapolation)
441 {
442 return bPreInfinityOscillates ?
443 CurveStartSeconds :
444 FMath::Max(CurveStartSeconds, CurveEndSeconds - PreInfinityDuration);
445 }
446 else
447 {
448 return VisibleCurveStartSeconds;
449 }
450 }();
451
452 const double TranslatedPreInfinityEndSeconds = [this]()
453 {
454 if (bPreInfinityVisible && bComplexPreInfinityExtrapolation)
455 {
456 return bPreInfinityOscillates ?
457 FMath::Min(CurveEndSeconds, CurveStartSeconds + PreInfinityDuration) :
458 CurveEndSeconds;
459 }
460 else
461 {
462 return VisibleCurveEndSeconds;
463 }
464 }();
465
466
467 const double TranslatedPostInfinityStartSeconds = [this]()
468 {
469 if (bPostInfinityVisible && bComplexPostInfinityExtrapolation)
470 {
471 return bPostInfinityOscillates ?
472 FMath::Max(CurveStartSeconds, CurveEndSeconds - PostInfinityDuration) :
473 CurveStartSeconds;
474 }
475 else
476 {
477 return VisibleCurveStartSeconds;
478 }
479 }();
480
481
482 const double TranslatedPostInfinityEndSeconds = [this]()
483 {
484 if (bPostInfinityVisible && bComplexPostInfinityExtrapolation)
485 {
486 return bPostInfinityOscillates ?
487 CurveEndSeconds :
488 FMath::Min(CurveEndSeconds, CurveStartSeconds + PostInfinityDuration);
489 }
490 else
491 {
492 return VisibleCurveEndSeconds;
493 }
494 }();
495
498 }
499
502 {
504
505 if (PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_None ||
506 PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Constant)
507 {
508 OutInterpolatingPoints.Emplace(StartTimeSeconds, FirstKey.Y);
509 }
510 else if (PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Linear)
511 {
512 const FFrameNumber StartFrame = (StartTimeSeconds * TickResolution).FloorToFrame();
513
514 CurveValueType EvaluatedValue;
515 if (Channel.Evaluate(StartFrame, EvaluatedValue))
516 {
517 OutInterpolatingPoints.Emplace(StartTimeSeconds, EvaluatedValue);
518 }
519 else
520 {
521 OutInterpolatingPoints.Emplace(StartTimeSeconds, FirstKey.Y);
522 }
523 }
524 else
525 {
526 ensureMsgf(0, TEXT("Unhandled enum value"));
527 }
528 }
529
532 {
534 const FVector2D LastKey(InCurveInterpolatingPoints.Last().Get<0>(), InCurveInterpolatingPoints.Last().Get<1>());
535
536 const double InfinityOffset = FirstKey.X - StartTimeSeconds;
537
538 if (FMath::IsNearlyZero(CurveDurationSeconds))
539 {
540 // Draw a single line in the odd case where there are many keys with a nearly zero duration.
542 StartTimeSeconds,
543 FirstKey.Y);
544
546 FirstKey.X,
547 FirstKey.Y);
548 }
549 else
550 {
551 // Cycle or oscillate
552 const double StartTime = FirstKey.X;
553 const double ValueOffset = PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ?
554 LastKey.Y - FirstKey.Y :
555 0.0;
556
557 const int32 NumIterations = static_cast<int32>(InfinityOffset / CurveDurationSeconds) + 1;
558 for (int32 Iteration = NumIterations; Iteration > 0; Iteration--)
559 {
560 const bool bReverse = PreInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate && Iteration % 2 != 0;
561
562 if (bReverse)
563 {
565 {
566 // Mirror around start time
567 const double Time = 2 * StartTime + CurveDurationSeconds - InCurveInterpolatingPoints[PointIndex].Get<0>() - CurveDurationSeconds * Iteration;
568 const double Value = InCurveInterpolatingPoints[PointIndex].Get<1>() - ValueOffset * Iteration;
569
571 }
572 }
573 else
574 {
576 {
577 const double Time = Point.Get<0>() - CurveDurationSeconds * Iteration;
578 const double Value = Point.Get<1>() - ValueOffset * Iteration;
579
581 }
582 }
583 }
584 }
585 }
586
589 {
590 const FVector2D LastKey(InCurveInterpolatingPoints.Last().Get<0>(), InCurveInterpolatingPoints.Last().Get<1>());
591
592 if (PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_None ||
593 PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Constant)
594 {
595 OutInterpolatingPoints.Emplace(EndTimeSeconds, LastKey.Y);
596 }
597 else if (PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Linear)
598 {
599 const FFrameNumber EndFrame = (EndTimeSeconds * TickResolution).CeilToFrame();
600
601 CurveValueType EvaluatedValue;
602 if (Channel.Evaluate(EndFrame, EvaluatedValue))
603 {
604 OutInterpolatingPoints.Emplace(EndTimeSeconds, EvaluatedValue);
605 }
606 else
607 {
608 OutInterpolatingPoints.Emplace(EndTimeSeconds, LastKey.Y);
609 }
610 }
611 else
612 {
613 ensureMsgf(0, TEXT("Unhandled enum value"));
614 }
615 }
616
619 {
621 const FVector2D LastKey(InCurveInterpolatingPoints.Last().Get<0>(), InCurveInterpolatingPoints.Last().Get<1>());
622
623 const double InfinityOffset = EndTimeSeconds - LastKey.X;
624
625 if (FMath::IsNearlyZero(CurveDurationSeconds))
626 {
627 // Draw a single line in the odd case where there are many keys with a nearly zero duration.
629 LastKey.X,
630 LastKey.Y);
631
633 EndTimeSeconds,
634 LastKey.Y);
635 }
636 else
637 {
638 // Cycle or oscillate
639 const double StartTime = FirstKey.X;
640 const double ValueOffset = PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_CycleWithOffset ?
641 LastKey.Y - FirstKey.Y :
642 0.0;
643
644 const int32 NumIterations = static_cast<int32>(InfinityOffset / CurveDurationSeconds) + 1;
645 for (int32 Iteration = 1; Iteration <= NumIterations; Iteration++)
646 {
647 const bool bReverse = PostInfinityExtrapolation == ERichCurveExtrapolation::RCCE_Oscillate && Iteration % 2 != 0;
648
649 if (bReverse)
650 {
652 {
653 // Mirror around start time
654 const double Time = 2 * StartTime + CurveDurationSeconds - InCurveInterpolatingPoints[PointIndex].Get<0>() + CurveDurationSeconds * Iteration;
655 const double Value = InCurveInterpolatingPoints[PointIndex].Get<1>() - ValueOffset * Iteration;
656
658 }
659 }
660 else
661 {
663 {
664 const double Time = Point.Get<0>() + CurveDurationSeconds * Iteration;
665 const double Value = Point.Get<1>() + ValueOffset * Iteration;
666
668 }
669 }
670 }
671 }
672 }
673 };
674 }
675}
#define ensureMsgf( InExpression, InFormat,...)
Definition AssertionMacros.h:465
#define ensure( InExpression)
Definition AssertionMacros.h:464
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
ERichCurveExtrapolation
Definition RealCurve.h:27
constexpr TTuple< std::decay_t< Types >... > MakeTuple(Types &&... Args)
Definition Tuple.h:794
#define UE_ARRAY_COUNT(array)
Definition UnrealTemplate.h:212
Definition UnrealTemplate.h:321
Definition ArrayView.h:139
UE_FORCEINLINE_HINT constexpr SizeType Num() const
Definition ArrayView.h:380
constexpr ElementType & Last(SizeType IndexFromTheEnd=0) const
Definition ArrayView.h:410
Definition Array.h:670
Definition MovieSceneDrawBezierCurve.h:14
UE_REWRITE auto LowerBound(const RangeType &Range, const ValueType &Value, SortPredicateType SortPredicate) -> decltype(GetNum(Range))
Definition BinarySearch.h:92
UE_REWRITE auto UpperBound(const RangeType &Range, const ValueType &Value, SortPredicateType SortPredicate) -> decltype(GetNum(Range))
Definition BinarySearch.h:133
constexpr int32 NumIterations
Definition SoftsEvolution.cpp:13
Definition OverriddenPropertySet.cpp:45
Definition ConstraintsManager.h:14
U16 Index
Definition radfft.cpp:71
Definition FrameNumber.h:18
Definition FrameRate.h:21
static UE_FORCEINLINE_HINT bool IsNearlyEqual(float A, float B, float ErrorTolerance=UE_SMALL_NUMBER)
Definition UnrealMathUtility.h:388
static constexpr UE_FORCEINLINE_HINT T Lerp(const T &A, const T &B, const U &Alpha)
Definition UnrealMathUtility.h:1116
static constexpr UE_FORCEINLINE_HINT T Min3(const T A, const T B, const T C)
Definition UnrealMathUtility.h:558
static UE_FORCEINLINE_HINT bool IsNearlyZero(float Value, float ErrorTolerance=UE_SMALL_NUMBER)
Definition UnrealMathUtility.h:407
static constexpr UE_FORCEINLINE_HINT T Max3(const T A, const T B, const T C)
Definition UnrealMathUtility.h:551
Definition Tuple.h:652
Definition MovieSceneDrawBezierCurve.h:232
FMovieSceneDrawInfiniteBezierCurve(const ChannelType &InChannel, const FFrameRate &InTickResolution, const double &InTimeThreshold, const double &InValueThreshold, const double &InStartTimeSeconds, const double &InEndTimeSeconds, TArray< TTuple< double, double > > &OutInterpolatingPoints)
Definition MovieSceneDrawBezierCurve.h:244