Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObjectEditor/Private/MuCOE/SMutableCurveViewer.cpp
2025-05-18 13:04:45 +08:00

572 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCOE/SMutableCurveViewer.h"
#include "CurveEditor.h"
#include "CurveEditorScreenSpace.h"
#include "Framework/Views/TableViewMetadata.h"
#include "ICurveEditorModule.h"
#include "MuT/TypeInfo.h"
#include "SCurveEditorPanel.h"
#include "Widgets/Views/SListView.h"
class ITableRow;
class STableViewBase;
class SWidget;
#define LOCTEXT_NAMESPACE "SMutableCurveViewer"
#pragma region FMutableCurveModel method definitions
void FMutablePreviewerCurveModel::DrawCurve(const FCurveEditor& CurveEditor, const FCurveEditorScreenSpace& ScreenSpace,
TArray<TTuple<double, double>>& InterpolatingPoints) const
{
const double StartTimeSeconds = ScreenSpace.GetInputMin();
const double EndTimeSeconds = ScreenSpace.GetInputMax();
const double TimeThreshold = FMath::Max(0.0001, 1.0 / ScreenSpace.PixelsPerInput());
const double ValueThreshold = FMath::Max(0.0001, 1.0 / ScreenSpace.PixelsPerOutput());
InterpolatingPoints.Add(MakeTuple(StartTimeSeconds, double(RichCurve.Eval(StartTimeSeconds))));
const TArray<FRichCurveKey>& CurveKeys = RichCurve.GetConstRefOfKeys();
for (const FRichCurveKey& Key : CurveKeys)
{
if (Key.Time > StartTimeSeconds && Key.Time < EndTimeSeconds)
{
InterpolatingPoints.Add(MakeTuple(double(Key.Time), double(Key.Value)));
}
}
InterpolatingPoints.Add(MakeTuple(EndTimeSeconds, double(RichCurve.Eval(EndTimeSeconds))));
// Generate the points between the key positions we have already set
int32 OldSize = InterpolatingPoints.Num();
do
{
OldSize = InterpolatingPoints.Num();
RefineCurvePoints(RichCurve, TimeThreshold, ValueThreshold, InterpolatingPoints);
} while (OldSize != InterpolatingPoints.Num());
}
void FMutablePreviewerCurveModel::RefineCurvePoints(const FRichCurve& InRichCurve, double TimeThreshold,
float ValueThreshold, TArray<TTuple<double, double>>& InOutPoints) const
{
const float InterpTimes[] = { 0.25f, 0.5f, 0.6f };
for (int32 Index = 0; Index < InOutPoints.Num() - 1; ++Index)
{
TTuple<double, double> Lower = InOutPoints[Index];
TTuple<double, double> Upper = InOutPoints[Index + 1];
if ((Upper.Get<0>() - Lower.Get<0>()) >= TimeThreshold)
{
bool bSegmentIsLinear = true;
TTuple<double, double> Evaluated[UE_ARRAY_COUNT(InterpTimes)] = { TTuple<double, double>(0, 0) };
for (int32 InterpIndex = 0; InterpIndex < UE_ARRAY_COUNT(InterpTimes); ++InterpIndex)
{
double& EvalTime = Evaluated[InterpIndex].Get<0>();
EvalTime = FMath::Lerp(Lower.Get<0>(), Upper.Get<0>(), InterpTimes[InterpIndex]);
float Value = InRichCurve.Eval(EvalTime);
const float LinearValue = FMath::Lerp(Lower.Get<1>(), Upper.Get<1>(), InterpTimes[InterpIndex]);
if (bSegmentIsLinear)
{
bSegmentIsLinear = FMath::IsNearlyEqual(Value, LinearValue, ValueThreshold);
}
Evaluated[InterpIndex].Get<1>() = Value;
}
if (!bSegmentIsLinear)
{
// Add the point
InOutPoints.Insert(Evaluated, UE_ARRAY_COUNT(Evaluated), Index + 1);
--Index;
}
}
}
}
void FMutablePreviewerCurveModel::AddKeys(TArrayView<const FKeyPosition> InPositions,
TArrayView<const FKeyAttributes> InAttributes, TArrayView<TOptional<FKeyHandle>>* OutKeyHandles)
{
check(InPositions.Num() == InAttributes.Num());
// Iterate over the positions and store their data
for (int32 KeyIndex = 0; KeyIndex < InPositions.Num(); KeyIndex++)
{
// Add a new key on our rich curve and cache the handle
const float KeyTime = InPositions[KeyIndex].InputValue;
const float KeyValue = InPositions[KeyIndex].OutputValue;
// RichCurve add key is the one that sets the index to whatever it wants!!
KeyHandles.Add( RichCurve.AddKey(KeyTime,KeyValue));
// Load attributes onto the key
const FKeyAttributes KeyAttributes = InAttributes[KeyIndex];
{
// Set interpolation and weight modes
RichCurve.SetKeyInterpMode(KeyHandles.Last(),KeyAttributes.GetInterpMode());
RichCurve.SetKeyTangentMode(KeyHandles.Last(),KeyAttributes.GetTangentMode());
RichCurve.SetKeyTangentWeightMode(KeyHandles.Last(),KeyAttributes.GetTangentWeightMode());
// Set the tangent data
FRichCurveKey& RichKey = RichCurve.GetKey(KeyHandles.Last());
RichKey.ArriveTangentWeight = KeyAttributes.GetArriveTangentWeight();
RichKey.LeaveTangentWeight = KeyAttributes.GetLeaveTangentWeight();
RichKey.ArriveTangent = KeyAttributes.GetArriveTangent();
RichKey.LeaveTangent = KeyAttributes.GetLeaveTangent();
}
}
}
void FMutablePreviewerCurveModel::GetKeyPositions(TArrayView<const FKeyHandle> InKeys,
TArrayView<FKeyPosition> OutKeyPositions) const
{
if (!InKeys.Num())
{
return;
}
TArray<FKeyPosition> LocatedPositions;
// Retrieve the keys by looking for those same key handles on our array of key handles
for (int32 InKeyIndex = 0; InKeyIndex < InKeys.Num(); InKeyIndex++)
{
// Index of the provided key on our array of keys
const int32 ActualIndex = KeyHandles.IndexOfByKey(InKeys[InKeyIndex]);
const FRichCurveKey Key = RichCurve.GetKey(KeyHandles[ActualIndex] );
FKeyPosition KeyPosition;
{
KeyPosition.InputValue = Key.Time;
KeyPosition.OutputValue = Key.Value;
}
LocatedPositions.Add(KeyPosition);
}
OutKeyPositions = TArrayView<FKeyPosition>(LocatedPositions);
}
void FMutablePreviewerCurveModel::GetTimeRange(double& MinTime, double& MaxTime) const
{
if (RichCurve.Keys.Num() > 1)
{
MinTime = RichCurve.Keys[0].Time;
MaxTime = RichCurve.Keys[RichCurve.Keys.Num()-1].Time;
}
else
{
MinTime = 0;
MaxTime = 1;
}
}
void FMutablePreviewerCurveModel::GetValueRange(double& MinValue, double& MaxValue) const
{
MinValue = 0;
MaxValue = 1;
// Get max and min values
for (int32 RichCurveKeyIndex = 0; RichCurveKeyIndex < RichCurve.Keys.Num(); RichCurveKeyIndex++)
{
const double PositionValue = RichCurve.Keys[RichCurveKeyIndex].Value;
if (PositionValue > MaxValue)
{
MaxValue = PositionValue;
}
if (PositionValue < MinValue)
{
MinValue = PositionValue;
}
}
}
void FMutablePreviewerCurveModel::GetNeighboringKeys(const FKeyHandle InKeyHandle,
TOptional<FKeyHandle>& OutPreviousKeyHandle, TOptional<FKeyHandle>& OutNextKeyHandle) const
{
const int32 IndexOfTargetKey = KeyHandles.IndexOfByKey(InKeyHandle);
check(IndexOfTargetKey >= 0);
if (IndexOfTargetKey > 0)
{
OutPreviousKeyHandle = KeyHandles[IndexOfTargetKey - 1];
}
if (IndexOfTargetKey < KeyHandles.Num() - 1)
{
OutNextKeyHandle = KeyHandles[IndexOfTargetKey + 1];
}
}
#pragma endregion
namespace MutableCurveKeyFramesListColumns
{
static const FName KeyFrameIdColumnID("Keyframe");
static const FName KeyFrameTimeColumnID ("Time");
static const FName KeyFrameValueColumnID ("Value");
static const FName KeyFrameInTangentColumnID ("In Tangent");
static const FName KeyFrameInTangentWeightColumnID ("In Tangent Weight");
static const FName KeyFrameOutTangentColumnID ("Out Tangent");
static const FName KeyFrameOutTangentWeightColumnID ("Out Tangent Weight");
static const FName KeyFrameInterpolationModeColumnID ("Interpolation Mode");
static const FName KeyFrameTangentModeColumnID ("Tangent Mode");
static const FName KeyFrameTangentWeightModeColumnID ("Weight Mode");
}
class SMutableCurveKeyFrameTableRow : public SMultiColumnTableRow<TSharedPtr<FMutableCurveElement>>
{
public:
void Construct(const FArguments& Args, const TSharedRef<STableViewBase>& InOwnerTableView, const TSharedPtr<FMutableCurveElement>& InRowItem)
{
RowItem = InRowItem;
SMultiColumnTableRow< TSharedPtr<FMutableCurveElement> >::Construct(
STableRow::FArguments()
.ShowSelection(true)
, InOwnerTableView
);
}
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& InColumnName ) override
{
// INDEX
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameIdColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::FromInt(RowItem->KeyFrameIndex)))
];
}
// time
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameTimeColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.Time) ))
];
}
// value
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameValueColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.Value) ))
];
}
// In tangent
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameInTangentColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.ArriveTangent) ))
];
}
// In tangent weight
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameInTangentWeightColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.ArriveTangentWeight) ))
];
}
// out tangent
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameOutTangentColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.LeaveTangent) ))
];
}
// Out tangent weight
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameOutTangentWeightColumnID)
{
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString( FString::SanitizeFloat(RowItem->CurveKeyFrame.LeaveTangentWeight) ))
];
}
// interp_mode
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameInterpolationModeColumnID)
{
const uint8 InterpolationMode = RowItem->CurveKeyFrame.InterpMode;
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString(* FString::Printf(TEXT("%d"),InterpolationMode)))
];
}
// tangent mode
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameTangentModeColumnID)
{
const uint8 TangentMode = RowItem->CurveKeyFrame.TangentMode;
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString(*FString::Printf(TEXT("%d"), TangentMode)))
];
}
// tangent weight mode
if (InColumnName == MutableCurveKeyFramesListColumns::KeyFrameTangentWeightModeColumnID)
{
const uint8 TangentWeightMode = RowItem->CurveKeyFrame.TangentWeightMode;
return SNew(SHorizontalBox)+SHorizontalBox::Slot()
[
SNew(STextBlock).
Text(FText::FromString(*FString::Printf(TEXT("%d"), TangentWeightMode)))
];
}
// Invalid column name so no widget will be produced
checkNoEntry();
return SNullWidget::NullWidget;
}
private:
TSharedPtr<FMutableCurveElement> RowItem;
};
void SMutableCurveViewer::Construct(const FArguments& InArgs)
{
// Create the Curve Editor
{
this->CurveEditor = MakeShared<FCurveEditor>();
CurveEditor = MakeShared<FCurveEditor>();
FCurveEditorInitParams InitParams;
CurveEditor->InitCurveEditor(InitParams);
CurveEditor->GridLineLabelFormatXAttribute = LOCTEXT("GridXLabelFormat", "{0}");
}
// Formatting
constexpr float VerticalPadding = 30.0f;
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(0.6f)
[
// Curve display
SAssignNew(CurveEditorPanel, SCurveEditorPanel, CurveEditor.ToSharedRef())
]
+ SVerticalBox::Slot()
.Padding(0,VerticalPadding)
.AutoHeight()
[
// Curve table with all the mutable key data
SAssignNew(CurveListView, SListView<TSharedPtr<FMutableCurveElement>>)
.ListItemsSource(&CurveElements)
.OnGenerateRow(this, &SMutableCurveViewer::OnGenerateCurveTableRow)
.SelectionMode(ESelectionMode::None)
.HeaderRow
(
SNew(SHeaderRow)
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameIdColumnID)
.DefaultLabel(LOCTEXT("KeyframeID", "ID"))
.FillWidth(0.22)
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameTimeColumnID)
.DefaultLabel(LOCTEXT("KeyframeTime", "Time"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameValueColumnID)
.DefaultLabel(LOCTEXT("KeyframeValue", "Value"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameInTangentColumnID)
.DefaultLabel(LOCTEXT("InTangent", "In-Tangent"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameInTangentWeightColumnID)
.DefaultLabel(LOCTEXT("InTangentWeight", "In-Tangent Weight"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameOutTangentColumnID)
.DefaultLabel(LOCTEXT("OutTangent", "Out-Tangent"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameOutTangentWeightColumnID)
.DefaultLabel(LOCTEXT("OutTangentWeight", "Out-Tangent Weight"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameInterpolationModeColumnID)
.DefaultLabel(LOCTEXT("InterpolationMode", "Interpolation Mode"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameTangentModeColumnID)
.DefaultLabel(LOCTEXT("TangentWeight", "Tangent Mode"))
+ SHeaderRow::Column(MutableCurveKeyFramesListColumns::KeyFrameTangentWeightModeColumnID)
.DefaultLabel(LOCTEXT("TangentWeightMode", "Weight Mode"))
)
]
];
SetCurve(InArgs._MutableCurve);
}
void SMutableCurveViewer::SetCurve(const FRichCurve& InMutableCurve)
{
this->MutableCurve = InMutableCurve;
// Load the Curve Editor with the data found on the mu::Curve
SetupMutableCurveGraph();
// Load the data required by the SListView to display the data on the mu::Curve
SetupMutableCurveListView();
}
void SMutableCurveViewer::SetupMutableCurveListView()
{
const int32 KeyFrameCount = MutableCurve.Keys.Num() ;
CurveElements.SetNum(KeyFrameCount);
for (int32 KeyFrameIndex = 0; KeyFrameIndex < KeyFrameCount; KeyFrameIndex++)
{
const FRichCurveKey& CurrentKeyFrame = MutableCurve.Keys[KeyFrameIndex];
const TSharedPtr<FMutableCurveElement> NewCurveElement =
MakeShareable(new FMutableCurveElement(KeyFrameIndex,CurrentKeyFrame));
CurveElements[KeyFrameIndex] = NewCurveElement;
}
CurveListView->RequestListRefresh();
}
TSharedRef<ITableRow> SMutableCurveViewer::OnGenerateCurveTableRow(TSharedPtr<FMutableCurveElement> InElement,
const TSharedRef<STableViewBase>& OwnerTable) const
{
TSharedRef<SMutableCurveKeyFrameTableRow> Row = SNew(SMutableCurveKeyFrameTableRow, OwnerTable, InElement);
return Row;
}
void SMutableCurveViewer::SetupMutableCurveGraph() const
{
// Clear all possible curves set on previous iterations
if (CurveEditor->GetCurves().Num())
{
CurveEditor->RemoveAllCurves();
}
// Add a default curve
TUniquePtr<FMutablePreviewerCurveModel> CurveEditorModule = MakeUnique<FMutablePreviewerCurveModel>();
CurveEditorModule->SetColor(FLinearColor(FColor::Yellow));
// Cache the last key time to be able to compare it with the one being currently processed
float PreviousFrameTime = TNumericLimits<float>::Lowest();
// Fill the curve with data
const int32 KeyFrameCount = MutableCurve.Keys.Num() ;
for (int32 KeyframeIndex = 0; KeyframeIndex < KeyFrameCount; KeyframeIndex++)
{
// Load the mutable data
const FRichCurveKey& CurrentKeyframe = MutableCurve.Keys[KeyframeIndex];
// Setup basic data (x and y axis)
FKeyPosition KeyPosition;
{
// time and value
KeyPosition.InputValue = CurrentKeyframe.Time;
KeyPosition.OutputValue = CurrentKeyframe.Value;
}
/*
* Important : The time value for each key MUST be different. nearly equal values can produce ill-formed graphs
* depending on the order of the keys.
*
* Check out FRichCurve::AddKey for more information (for at the beginning of the method setting the index).
*/
{
// Base amount of time delta to be applied to correct the time as index behaviour of FRichCurve
// @note TNumericLimits<float>::Min() is too small;
constexpr float ApplicableTimeDelta = 0.00000005f;
// Shift the position of the current element if we are too close to the last processed element or if
// the previous element for whatever reason is now in front of us (previousTime > currentTime>.
if (FMath::IsNearlyEqual(PreviousFrameTime,CurrentKeyframe.Time,ApplicableTimeDelta)
|| PreviousFrameTime > CurrentKeyframe.Time)
{
KeyPosition.InputValue += ApplicableTimeDelta;
}
}
// Update previousFrameTime to represent the updated value of the current keyframe
PreviousFrameTime = KeyPosition.InputValue;
// Setup the curve behaviour for this key of the curve.
FKeyAttributes KeyAttributes;
{
// tangents
KeyAttributes.SetArriveTangent(CurrentKeyframe.ArriveTangent);
KeyAttributes.SetLeaveTangent(CurrentKeyframe.LeaveTangent);
// Weights
KeyAttributes.SetArriveTangentWeight(CurrentKeyframe.ArriveTangentWeight);
KeyAttributes.SetLeaveTangentWeight(CurrentKeyframe.LeaveTangentWeight);
// Interp mode
const TEnumAsByte UnrealInterpolationMode = static_cast<ERichCurveInterpMode>(CurrentKeyframe.InterpMode);
KeyAttributes.SetInterpMode(UnrealInterpolationMode);
// Tangent mode
const TEnumAsByte UnrealTangentMode =
static_cast<ERichCurveTangentMode>(CurrentKeyframe.TangentMode);
KeyAttributes.SetTangentMode(UnrealTangentMode);
// Tangent weight
const TEnumAsByte UnrealTangentWeightMode =
static_cast<ERichCurveTangentWeightMode>(CurrentKeyframe.TangentWeightMode);
KeyAttributes.SetTangentWeightMode(UnrealTangentWeightMode);
}
CurveEditorModule->AddKey(KeyPosition,KeyAttributes);
}
// Add the curve to the editor
const FCurveModelID CurveId = CurveEditor->AddCurve(MoveTemp(CurveEditorModule));
CurveEditor->PinCurve(CurveId);
}
#undef LOCTEXT_NAMESPACE