Files
UnrealEngine/Engine/Source/Editor/MovieSceneTools/Private/Channels/BezierChannelCurveModel.cpp
2025-05-18 13:04:45 +08:00

666 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Channels/BezierChannelCurveModel.h"
#include "Cache/MovieSceneCachedCurve.h"
#include "Cache/MovieSceneUpdateCachedCurveData.h"
#include "Channels/CurveModelHelpers.h"
#include "Channels/MovieSceneChannelData.h"
#include "Channels/MovieSceneCurveChannelCommon.h"
#include "Channels/MovieSceneDoubleChannel.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "Containers/EnumAsByte.h"
#include "CurveDataAbstraction.h"
#include "CurveDrawInfo.h"
#include "CurveEditorScreenSpace.h"
#include "Curves/RealCurve.h"
#include "Curves/RichCurve.h"
#include "Delegates/Delegate.h"
#include "HAL/PlatformCrt.h"
#include "Math/Color.h"
#include "Math/UnrealMathUtility.h"
#include "Math/Vector2D.h"
#include "Misc/FrameNumber.h"
#include "Misc/FrameRate.h"
#include "Misc/Optional.h"
#include "MovieScene.h"
#include "MovieSceneSection.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Templates/UnrealTemplate.h"
#include "UObject/WeakObjectPtr.h"
class FCurveEditor;
template<typename ChannelType>
FBezierChannelBufferedCurveModel<ChannelType>::FBezierChannelBufferedCurveModel(
const ChannelType* InChannel, TWeakObjectPtr<UMovieSceneSection> InWeakSection,
TArray<FKeyPosition>&& InKeyPositions, TArray<FKeyAttributes>&& InKeyAttributes, const FString& InLongDisplayName, const double InValueMin, const double InValueMax)
: IBufferedCurveModel(MoveTemp(InKeyPositions), MoveTemp(InKeyAttributes), InLongDisplayName, InValueMin, InValueMax)
, Channel(*InChannel)
, WeakSection(InWeakSection)
{}
template<typename ChannelType>
void FBezierChannelBufferedCurveModel<ChannelType>::DrawCurve(const FCurveEditor& InCurveEditor, const FCurveEditorScreenSpace& InScreenSpace, TArray<TTuple<double, double>>& OutInterpolatingPoints) const
{
UMovieSceneSection* Section = WeakSection.Get();
if (Section && Section->GetTypedOuter<UMovieScene>())
{
FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
const double StartTimeSeconds = InScreenSpace.GetInputMin();
const double EndTimeSeconds = InScreenSpace.GetInputMax();
const double TimeThreshold = FMath::Max(0.0001, 1.0 / InScreenSpace.PixelsPerInput());
const double ValueThreshold = FMath::Max(0.0001, 1.0 / InScreenSpace.PixelsPerOutput());
Channel.PopulateCurvePoints(StartTimeSeconds, EndTimeSeconds, TimeThreshold, ValueThreshold, TickResolution, OutInterpolatingPoints);
}
}
template <typename ChannelType>
bool FBezierChannelBufferedCurveModel<ChannelType>::Evaluate(double InTime, double& OutValue) const
{
return UE::MovieSceneTools::CurveHelpers::Evaluate(InTime, OutValue, Channel, WeakSection);
}
template class FBezierChannelBufferedCurveModel<FMovieSceneFloatChannel>;
template class FBezierChannelBufferedCurveModel<FMovieSceneDoubleChannel>;
template<typename ChannelType, typename ChannelValue, typename KeyType>
FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::FBezierChannelCurveModel(TMovieSceneChannelHandle<ChannelType> InChannel, UMovieSceneSection* OwningSection, TWeakPtr<ISequencer> InWeakSequencer)
: FChannelCurveModel<ChannelType, ChannelValue, KeyType>(InChannel, OwningSection, InWeakSequencer)
{
ChannelType* Channel = InChannel.Get();
if (Channel && OwningSection && OwningSection->GetTypedOuter<UMovieScene>())
{
Channel->SetTickResolution(OwningSection->GetTypedOuter<UMovieScene>()->GetTickResolution());
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::FBezierChannelCurveModel(TMovieSceneChannelHandle<ChannelType> InChannel, UMovieSceneSection* OwningSection, UObject* InOwningObject, TWeakPtr<ISequencer> InWeakSequencer)
: FChannelCurveModel<ChannelType, ChannelValue, KeyType>(InChannel, OwningSection, InOwningObject, InWeakSequencer)
{
ChannelType* Channel = InChannel.Get();
if (Channel && OwningSection && OwningSection->GetTypedOuter<UMovieScene>())
{
Channel->SetTickResolution(OwningSection->GetTypedOuter<UMovieScene>()->GetTickResolution());
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::DrawCurve(const FCurveEditor& InCurveEditor, const FCurveEditorScreenSpace& InScreenSpace, TArray<TTuple<double, double>>& OutInterpolatingPoints) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSection* Section = this->template GetOwningObjectOrOuter<UMovieSceneSection>();
if (Channel && Section && Section->GetTypedOuter<UMovieScene>())
{
FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
const double StartTimeSeconds = InScreenSpace.GetInputMin();
const double EndTimeSeconds = InScreenSpace.GetInputMax();
const double TimeThreshold = FMath::Max(0.0001, 1.0 / InScreenSpace.PixelsPerInput());
const double ValueThreshold = FMath::Max(0.0001, 1.0 / InScreenSpace.PixelsPerOutput());
Channel->PopulateCurvePoints(StartTimeSeconds, EndTimeSeconds, TimeThreshold, ValueThreshold, TickResolution, OutInterpolatingPoints);
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
UE::CurveEditor::ICurveEditorCurveCachePool* FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::DrawCurveToCachePool(
const TSharedRef<FCurveEditor>& CurveEditor,
const UE::CurveEditor::FCurveDrawParamsHandle& CurveDrawParamsHandle,
const FCurveEditorScreenSpace& ScreenSpace)
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSection* Section = this->template GetOwningObjectOrOuter<UMovieSceneSection>();
if (!CachedCurve.IsValid())
{
CachedCurve = MakeShared<UE::MovieSceneTools::FMovieSceneCachedCurve<ChannelType>>(CurveDrawParamsHandle.GetID());
CachedCurve->Initialize(CurveEditor);
}
if (Channel && Section && Section->GetTypedOuter<UMovieScene>())
{
const FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
UE::MovieSceneTools::FMovieSceneUpdateCachedCurveData<ChannelType> UpdateData(*CurveEditor, *this, *Channel, ScreenSpace, TickResolution);
CachedCurve->UpdateCachedCurve(UpdateData, CurveDrawParamsHandle);
}
return &UE::MovieSceneTools::FMovieSceneCurveCachePool::Get();
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
bool FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::HasChangedAndResetTest()
{
if (CachedCurve.IsValid())
{
return CachedCurve->HasChanged() || Super::HasChangedAndResetTest();
}
return Super::HasChangedAndResetTest();
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetKeyDrawInfo(ECurvePointType PointType, const FKeyHandle InKeyHandle, FKeyDrawInfo& OutDrawInfo) const
{
if (PointType == ECurvePointType::ArriveTangent || PointType == ECurvePointType::LeaveTangent)
{
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.TangentHandle");
OutDrawInfo.ScreenSize = FVector2D(8, 8);
}
else
{
// All keys are the same size by default
OutDrawInfo.ScreenSize = FVector2D(11, 11);
ERichCurveInterpMode KeyInterpType = RCIM_None;
ERichCurveTangentWeightMode KeyTWType = RCTWM_WeightedNone;
// Get the key type from the supplied key handle if it's valid
ChannelType* Channel = this->GetChannelHandle().Get();
if (Channel && InKeyHandle != FKeyHandle::Invalid())
{
TMovieSceneChannelData<ChannelValue> ChannelData = Channel->GetData();
const int32 KeyIndex = ChannelData.GetIndex(InKeyHandle);
if (KeyIndex != INDEX_NONE)
{
KeyInterpType = ChannelData.GetValues()[KeyIndex].InterpMode;
KeyTWType = ChannelData.GetValues()[KeyIndex].Tangent.TangentWeightMode;
}
}
switch (KeyInterpType)
{
case ERichCurveInterpMode::RCIM_Constant:
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.ConstantKey");
break;
case ERichCurveInterpMode::RCIM_Linear:
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.LinearKey");
break;
case ERichCurveInterpMode::RCIM_Cubic:
if (KeyTWType == ERichCurveTangentWeightMode::RCTWM_WeightedBoth)
{
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.WeightedTangentCubicKey");
}
else
{
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.CubicKey");
}
break;
default:
OutDrawInfo.Brush = FAppStyle::GetBrush("GenericCurveEditor.Key");
break;
}
if (this->IsReadOnly())
{
OutDrawInfo.Tint = OutDrawInfo.Tint.IsSet() ? OutDrawInfo.Tint.GetValue() * 0.5f : FLinearColor(0.5f, 0.5f, 0.5f);
}
}
}
template <class ChannelType, class ChannelValue, class KeyType>
TPair<ERichCurveInterpMode, ERichCurveTangentMode> FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetInterpolationMode(const double& InTime, ERichCurveInterpMode DefaultInterpolationMode, ERichCurveTangentMode DefaultTangentMode) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSection* Section = this->template GetOwningObjectOrOuter<UMovieSceneSection>();
if (Channel && Section)
{
FFrameRate TickResolution = Section->GetTypedOuter<UMovieScene>()->GetTickResolution();
TMovieSceneChannelData<ChannelValue> ChannelData = Channel->GetData();
TArrayView<const FFrameNumber> Times = ChannelData.GetTimes();
TArrayView<const ChannelValue> Values = ChannelData.GetValues();
const FFrameNumber InFrame = (InTime * TickResolution).RoundToFrame();
if (Times.Num() > 0)
{
int32 InterpolationIndex = Algo::LowerBound(Times, InFrame) - 1;
if (InterpolationIndex < 0)
{
InterpolationIndex = 0;
}
const FKeyHandle KeyHandle = ChannelData.GetHandle(InterpolationIndex);
TArrayView<const FKeyHandle> InKey(&KeyHandle, 1);
TArray<FKeyAttributes> KeyAttributes;
KeyAttributes.SetNum(1);
GetKeyAttributes(InKey, KeyAttributes);
ERichCurveInterpMode InterpMode = KeyAttributes[0].GetInterpMode();
ERichCurveTangentMode TangentMode = KeyAttributes[0].HasTangentMode() ? KeyAttributes[0].GetTangentMode() : DefaultTangentMode;
//if we are cubic, with anything but auto tangents we use the default instead, since they will give us flat tangents which aren't good
if (InterpMode == ERichCurveInterpMode::RCIM_Cubic &&
(TangentMode != ERichCurveTangentMode::RCTM_Auto && TangentMode != ERichCurveTangentMode::RCTM_SmartAuto))
{
TangentMode = DefaultTangentMode;
}
return TPair<ERichCurveInterpMode, ERichCurveTangentMode>(InterpMode, TangentMode);
}
}
return TPair<ERichCurveInterpMode, ERichCurveTangentMode>(DefaultInterpolationMode, DefaultTangentMode);
}
static bool IsAuto(ERichCurveTangentMode TangentMode)
{
return (TangentMode == RCTM_Auto || TangentMode == RCTM_SmartAuto);
}
/**
* Shared implementation for getting keys.
*
* Typically, attributes reflect the settings that the user has manually configured for the keys, so certain attributes may remain unset.
* For instance, when TangentMode == RCTM_Auto, tangents and weights are automatically computed, meaning attributes like ArriveTangent are not explicitly set.
* Setting ArriveTangent would imply a user-defined value, which is incompatible with TangentMode == RCTM_Auto.
*
* Sometimes API users still need a way to get the auto-computed values, like those of tangents, though.
* For this, use bAllAttributes parameter:
* - true: get all attributes, even the auto-computed ones. The values are useful for e.g. UI visualization. Do not pass them to SetKeyAttributes.
* - false: get only the attributes that were set by the user (i.e. skip returning auto-computed values). You can pass them to SetKeyAttributes, e.g. for copy paste.
*/
template<bool bAllAttributes, typename ChannelType, typename ChannelValue, typename KeyType>
static void GetKeyAttributesDetail(
ChannelType* InChannel, UMovieSceneSection* InSection, UMovieScene* InMovieScene,
TArrayView<const FKeyHandle> InKeys,
TArrayView<FKeyAttributes> OutAttributes
)
{
if (!InChannel || !InSection || !InMovieScene)
{
return;
}
TMovieSceneChannelData<ChannelValue> ChannelData = InChannel->GetData();
TArrayView<ChannelValue> Values = ChannelData.GetValues();
const float TimeInterval = InMovieScene->GetTickResolution().AsInterval();
for (int32 Index = 0; Index < InKeys.Num(); ++Index)
{
const int32 KeyIndex = ChannelData.GetIndex(InKeys[Index]);
if (KeyIndex != INDEX_NONE)
{
const ChannelValue& KeyValue = Values[KeyIndex];
FKeyAttributes& Attributes = OutAttributes[Index];
Attributes.SetInterpMode(KeyValue.InterpMode);
// If the previous key is cubic, show the arrive tangent handle even if this key is constant
const int32 PreviousKeyIndex = KeyIndex - 1;
const bool bGetArriveTangent = Values.IsValidIndex(PreviousKeyIndex) && Values[PreviousKeyIndex].InterpMode == RCIM_Cubic;
if (bAllAttributes && bGetArriveTangent)
{
Attributes.SetArriveTangent(KeyValue.Tangent.ArriveTangent / TimeInterval);
}
if ((KeyValue.InterpMode != RCIM_Constant && KeyValue.InterpMode != RCIM_Linear))
{
Attributes.SetTangentMode(KeyValue.TangentMode);
// The remaining settings (arrive / leave tangent, arrive / leave weight, weight mode) can only be user specified, i.e. cannot be set when in auto.
// Setting any of them would imply they specified by the user, which would contradict the setting that it's auto.
if (!bAllAttributes && IsAuto(KeyValue.TangentMode))
{
continue;
}
Attributes.SetArriveTangent(KeyValue.Tangent.ArriveTangent / TimeInterval);
Attributes.SetLeaveTangent(KeyValue.Tangent.LeaveTangent / TimeInterval);
if (KeyValue.InterpMode == RCIM_Cubic)
{
Attributes.SetTangentWeightMode(KeyValue.Tangent.TangentWeightMode);
if (KeyValue.Tangent.TangentWeightMode != RCTWM_WeightedNone)
{
Attributes.SetArriveTangentWeight(KeyValue.Tangent.ArriveTangentWeight);
Attributes.SetLeaveTangentWeight(KeyValue.Tangent.LeaveTangentWeight);
}
}
}
}
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetKeyAttributes(TArrayView<const FKeyHandle> InKeys, TArrayView<FKeyAttributes> OutAttributes) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSection* Section = this->template GetOwningObjectOrOuter<UMovieSceneSection>();
UMovieScene* MovieScene = Section ? Section->GetTypedOuter<UMovieScene>() : nullptr;
GetKeyAttributesDetail<true, ChannelType, ChannelValue, KeyType>(Channel, Section, MovieScene, InKeys, OutAttributes);
}
template <typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetKeyAttributesExcludingAutoComputed(TArrayView<const FKeyHandle> InKeys,
TArrayView<FKeyAttributes> OutAttributes) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSection* Section = this->template GetOwningObjectOrOuter<UMovieSceneSection>();
UMovieScene* MovieScene = Section ? Section->GetTypedOuter<UMovieScene>() : nullptr;
GetKeyAttributesDetail<false, ChannelType, ChannelValue, KeyType>(Channel, Section, MovieScene, InKeys, OutAttributes);
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::SetKeyAttributes(TArrayView<const FKeyHandle> InKeys, TArrayView<const FKeyAttributes> InAttributes, EPropertyChangeType::Type ChangeType)
{
UE::MovieScene::FScopedSignedObjectModifyDefer Defer(false /*do not force upate*/);
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSignedObject* SignedOwner = this->template GetOwningObjectOrOuter<UMovieSceneSignedObject>();
UMovieScene* MovieScene = SignedOwner ? SignedOwner->GetTypedOuter<UMovieScene>() : nullptr;
// Rule: Auto means tangents and weights are auto computed.
// This is used below to correct invalid input InAttributes, e.g. if InAttributes says TangentMode is RCTM_Auto, then ArriveTangent cannot be set.
const auto ResetModeAndWeightForAuto = [](ChannelValue& KeyValue)
{
if (IsAuto(KeyValue.TangentMode))
{
KeyValue.TangentMode = RCTM_User;
KeyValue.Tangent.TangentWeightMode = RCTWM_WeightedNone;
}
};
if (Channel && SignedOwner && MovieScene && !this->IsReadOnly())
{
bool bAutoSetTangents = false;
TMovieSceneChannelData<ChannelValue> ChannelData = Channel->GetData();
TArrayView<ChannelValue> Values = ChannelData.GetValues();
FFrameRate TickResolution = MovieScene->GetTickResolution();
float TimeInterval = TickResolution.AsInterval();
for (int32 Index = 0; Index < InKeys.Num(); ++Index)
{
const int32 KeyIndex = ChannelData.GetIndex(InKeys[Index]);
if (KeyIndex != INDEX_NONE)
{
const FKeyAttributes& Attributes = InAttributes[Index];
ChannelValue& KeyValue = Values[KeyIndex];
if (Attributes.HasInterpMode()) { KeyValue.InterpMode = Attributes.GetInterpMode(); bAutoSetTangents = true; }
if (Attributes.HasTangentMode())
{
KeyValue.TangentMode = Attributes.GetTangentMode();
if (IsAuto(KeyValue.TangentMode))
{
KeyValue.Tangent.TangentWeightMode = RCTWM_WeightedNone;
}
bAutoSetTangents = true;
}
if (Attributes.HasTangentWeightMode())
{
if (KeyValue.Tangent.TangentWeightMode == RCTWM_WeightedNone) //set tangent weights to default use
{
TArrayView<const FFrameNumber> Times = Channel->GetTimes();
const float OneThird = 1.0f / 3.0f;
//calculate a tangent weight based upon tangent and time difference
//calculate arrive tangent weight
if (KeyIndex > 0 )
{
const float X = TickResolution.AsSeconds(Times[KeyIndex].Value - Times[KeyIndex - 1].Value);
const float ArriveTangentNormal = KeyValue.Tangent.ArriveTangent / (TimeInterval);
const float Y = ArriveTangentNormal * X;
KeyValue.Tangent.ArriveTangentWeight = FMath::Sqrt(X*X + Y * Y) * OneThird;
}
//calculate leave weight
if(KeyIndex < ( Times.Num() - 1))
{
const float X = TickResolution.AsSeconds(Times[KeyIndex].Value - Times[KeyIndex + 1].Value);
const float LeaveTangentNormal = KeyValue.Tangent.LeaveTangent / (TimeInterval);
const float Y = LeaveTangentNormal * X;
KeyValue.Tangent.LeaveTangentWeight = FMath::Sqrt(X*X + Y*Y) * OneThird;
}
}
KeyValue.Tangent.TangentWeightMode = Attributes.GetTangentWeightMode();
if( KeyValue.Tangent.TangentWeightMode != RCTWM_WeightedNone )
{
if (KeyValue.TangentMode != RCTM_User && KeyValue.TangentMode != RCTM_Break)
{
// Input attribute is invalid: Weights can only be set for user or break tangent modes. Correct it.
KeyValue.TangentMode = RCTM_User;
}
}
bAutoSetTangents = true;
}
if (Attributes.HasArriveTangent())
{
// If TangentMode is auto, then the input attribute is invalid: it implies arrive tangent is user specified. Correct TangentMode.
ResetModeAndWeightForAuto(KeyValue);
KeyValue.Tangent.ArriveTangent = Attributes.GetArriveTangent() * TimeInterval;
if (KeyValue.InterpMode == RCIM_Cubic && KeyValue.TangentMode != RCTM_Break)
{
KeyValue.Tangent.LeaveTangent = KeyValue.Tangent.ArriveTangent;
}
bAutoSetTangents = true;
}
if (Attributes.HasLeaveTangent())
{
// If TangentMode is auto, then the input attribute is invalid: it implies leave tangent is user specified. Correct TangentMode.
ResetModeAndWeightForAuto(KeyValue);
KeyValue.Tangent.LeaveTangent = Attributes.GetLeaveTangent() * TimeInterval;
if (KeyValue.InterpMode == RCIM_Cubic && KeyValue.TangentMode != RCTM_Break)
{
KeyValue.Tangent.ArriveTangent = KeyValue.Tangent.LeaveTangent;
}
bAutoSetTangents = true;
}
if (Attributes.HasArriveTangentWeight())
{
// If TangentMode is auto, then the input attribute is invalid: it implies arrive weight is user specified. Correct TangentMode.
ResetModeAndWeightForAuto(KeyValue);
KeyValue.Tangent.ArriveTangentWeight = Attributes.GetArriveTangentWeight();
if (KeyValue.InterpMode == RCIM_Cubic && KeyValue.TangentMode != RCTM_Break)
{
KeyValue.Tangent.LeaveTangentWeight = KeyValue.Tangent.ArriveTangentWeight;
}
bAutoSetTangents = true;
}
if (Attributes.HasLeaveTangentWeight())
{
// If TangentMode is auto, then the input attribute is invalid: it implies leave weight is user specified. Correct TangentMode.
ResetModeAndWeightForAuto(KeyValue);
KeyValue.Tangent.LeaveTangentWeight = Attributes.GetLeaveTangentWeight();
if (KeyValue.InterpMode == RCIM_Cubic && KeyValue.TangentMode != RCTM_Break)
{
KeyValue.Tangent.ArriveTangentWeight = KeyValue.Tangent.LeaveTangentWeight;
}
bAutoSetTangents = true;
}
}
}
if (bAutoSetTangents)
{
Channel->AutoSetTangents();
}
SignedOwner->MarkAsChanged();
this->CurveModifiedDelegate.Broadcast();
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetCurveAttributes(FCurveAttributes& OutCurveAttributes) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
if (Channel)
{
OutCurveAttributes.SetPreExtrapolation(Channel->PreInfinityExtrap);
OutCurveAttributes.SetPostExtrapolation(Channel->PostInfinityExtrap);
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::SetCurveAttributes(const FCurveAttributes& InCurveAttributes)
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSignedObject* SignedOwner = this->template GetOwningObjectOrOuter<UMovieSceneSignedObject>();
if (Channel && SignedOwner && !this->IsReadOnly())
{
if (InCurveAttributes.HasPreExtrapolation())
{
Channel->PreInfinityExtrap = InCurveAttributes.GetPreExtrapolation();
}
if (InCurveAttributes.HasPostExtrapolation())
{
Channel->PostInfinityExtrap = InCurveAttributes.GetPostExtrapolation();
}
SignedOwner->MarkAsChanged();
this->CurveModifiedDelegate.Broadcast();
}
}
/* Finds min/max for cubic curves:
Looks for feature points in the signal(determined by change in direction of local tangent), these locations are then re-examined in closer detail recursively
Similar to function in RichCurve but usees the Channel::Evaluate function, instead of CurveModel::Eval*/
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::FeaturePointMethod(double StartTime, double EndTime, double StartValue, double Mu, int Depth, int MaxDepth, double& MaxV, double& MinVal) const
{
if (Depth >= MaxDepth)
{
return;
}
double PrevValue = StartValue;
double EvalValue;
this->Evaluate(StartTime - Mu, EvalValue);
double PrevTangent = StartValue - EvalValue;
EndTime += Mu;
for (double f = StartTime + Mu; f < EndTime; f += Mu)
{
double Value;
this->Evaluate(f, Value);
MaxV = FMath::Max(Value, MaxV);
MinVal = FMath::Min(Value, MinVal);
double CurTangent = Value - PrevValue;
if (FMath::Sign(CurTangent) != FMath::Sign(PrevTangent))
{
//feature point centered around the previous tangent
double FeaturePointTime = f - Mu * 2.0f;
double NewVal;
this->Evaluate(FeaturePointTime, NewVal);
FeaturePointMethod(FeaturePointTime, f,NewVal, Mu*0.4f, Depth + 1, MaxDepth, MaxV, MinVal);
}
PrevTangent = CurTangent;
PrevValue = Value;
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetValueRange(double InMinTime, double InMaxTime, double& MinValue, double& MaxValue) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
UMovieSceneSignedObject* SignedOwner = this->template GetOwningObjectOrOuter<UMovieSceneSignedObject>();
UMovieScene* MovieScene = SignedOwner ? SignedOwner->GetTypedOuter<UMovieScene>() : nullptr;
if (Channel && SignedOwner && MovieScene && !this->IsReadOnly())
{
TArrayView<const FFrameNumber> Times = Channel->GetData().GetTimes();
TArrayView<const ChannelValue> Values = Channel->GetData().GetValues();
if (Times.Num() == 0)
{
// If there are no keys we just use the default value for the channel, defaulting to zero if there is no default.
MinValue = MaxValue = Channel->GetDefault().Get(0.f);
}
else
{
FFrameRate TickResolution = MovieScene->GetTickResolution();
double ToTime = TickResolution.AsInterval();
int32 LastKeyIndex = Values.Num() - 1;
MinValue = TNumericLimits<double>::Max();
MaxValue = TNumericLimits<double>::Lowest();
for (int32 i = 0; i < Values.Num(); i++)
{
double KeyTime = static_cast<double>(Times[i].Value) * ToTime;
if (KeyTime < InMinTime)
{
continue;
}
else if (KeyTime > InMaxTime)
{
break;
}
const ChannelValue& Key = Values[i];
MinValue = FMath::Min(MinValue, (double)Key.Value);
MaxValue = FMath::Max(MaxValue, (double)Key.Value);
if (Key.InterpMode == RCIM_Cubic && i != LastKeyIndex)
{
const ChannelValue& NextKey = Values[i + 1];
double NextTime = static_cast<double>(Times[i + 1].Value) * ToTime;
double TimeStep = (NextTime - KeyTime) * 0.2f;
FeaturePointMethod(KeyTime, NextTime, Key.Value, TimeStep, 0, 3, MaxValue, MinValue);
}
}
}
}
//if nothing found just set to zero
if (MinValue == TNumericLimits<double>::Max())
{
MinValue = 0.0;
}
if (MaxValue == TNumericLimits<double>::Lowest())
{
MaxValue = 0.0;
}
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetValueRange(double& MinValue, double& MaxValue) const
{
const double InMinTime = TNumericLimits<double>::Lowest();
const double InMaxTime = TNumericLimits<double>::Max();
GetValueRange(InMinTime, InMaxTime, MinValue, MaxValue);
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
double FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::GetKeyValue(TArrayView<const ChannelValue> Values, int32 Index) const
{
return Values[Index].Value;
}
template<typename ChannelType, typename ChannelValue, typename KeyType>
void FBezierChannelCurveModel<ChannelType, ChannelValue, KeyType>::SetKeyValue(int32 Index, double KeyValue) const
{
ChannelType* Channel = this->GetChannelHandle().Get();
if (Channel)
{
TMovieSceneChannelData<ChannelValue> ChannelData = Channel->GetData();
ChannelData.GetValues()[Index].Value = KeyValue;
}
}
template class FBezierChannelCurveModel<FMovieSceneFloatChannel, FMovieSceneFloatValue, float>;
template class FBezierChannelCurveModel<FMovieSceneDoubleChannel, FMovieSceneDoubleValue, double>;