Files
UnrealEngineUWP/Engine/Source/Editor/CurveEditor/Private/CurveEditorHelpers.cpp
andrew rodham 927c5d41b3 Sequencer: Added time-warp capabilities to sequences, sub-sections and skeletal animation sections
This is a large suite of changes that are being submitted in bulk for build-health reasons:

Core machinery:
 - FMovieSceneNumericVariant: This is the base variant type that represents either a double, or a custom object. It is not much use on its own, but provides the basis for the time-warp variant, and will be the basis for parameterization.
 - FMovieSceneTimeWarpVariant: This is a drop in replacement for a double PlayRate/TimeScale UProperty that can now represent a time-warp or play rate curve, a loop, a clamp, or a fixed time. It also provides functionality for remapping time, and inverse remapping time.
 - The following interpolation types have been added/improved to add Derivative and Integral functionality
   * FConstantValue: Represents a curve of the form y=c
   * FLinearInterpolation: Represents a curve of the form y=mx + c
   * FQuadraticInterpolation: Represents a curve of the form y=ax^2 + bx + c.
   * FCubicInterpolation: Represents a curve of the form y=ax^3 + bx^2 + cx + d.
   * FQuarticInterpolation: Represents a curve of the form y=ax^4 + bx^3 + cx^2 + dx + e. Does not support integration.
   * FCubicBezierInterpolation now has an AsCubic method that converts it to a cubic polynomial form, which also allows derivative/integral functionality.
 - FPiecewiseCurve(Model): A generic read-only piecewise curve implementation and curve editor model. Used to display derivative curves for playrate.
 - FMovieSceneTimingParameters: A struct for specifying section timing that simplifies computation of a transform. I wanted to replace all implementations with this but current deprecation mechanisms were prohibitively restrictive. More work will be needed there to remove legacy code.
 - FMovieSceneTimeWarpChannel: A new channel type that defines time in one of two domains: PlayRate or Time

Curve Editor: Added the ability to specify custom axes and child curves
- Custom Axes:
  * Custom axes can now be assigned to each curve within a view, and the axis defines the scaling for that curve.
  * This allows us to have multiple different 'spaces' on the same view that have vastly different scales (for instance, play rate, and tick resolution times)
- Child Curves:
  * Child curves are added whenever their parent is added. This allows us to show the derivative of a play rate curve on the curve editor.

Sequencer: Added Owning Object and key offsets to channel meta-data and curve models, made Sequencer key-editors polymorphic,
- New options on channel proxy entries allow us to specify that a channel's keys are relative to their section start time via a key offset
- New options in the curve editor models allow for a non-UMovieSceneSection owner object type

#jira UE-149871
#rb Ludovic.Chabant, David.Bromberg, Max.Chen

[CL 34813298 by andrew rodham in ue5-main branch]
2024-07-15 10:52:09 -04:00

169 lines
7.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CurveEditorHelpers.h"
#include "CurveEditorScreenSpace.h"
#include "Fonts/FontMeasure.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/SlateApplication.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Text.h"
#include "Math/UnrealMathSSE.h"
#include "Rendering/SlateRenderer.h"
#include "Styling/CoreStyle.h"
#include "Styling/ISlateStyle.h"
#include "Templates/SharedPointer.h"
#include "Templates/UnrealTemplate.h"
namespace CurveEditor
{
FVector2D ComputeScreenSpaceTangentOffset(const FCurveEditorScreenSpace& CurveSpace, float Tangent, float Weight)
{
const float Angle = FMath::Atan(-Tangent);
FVector2D Offset;
FMath::SinCos(&Offset.Y, &Offset.X, Angle);
Offset *= Weight;
Offset.X *= CurveSpace.PixelsPerInput();
Offset.Y *= CurveSpace.PixelsPerOutput();
return Offset;
}
void TangentAndWeightFromOffset(const FCurveEditorScreenSpace& CurveSpace, const FVector2D& TangentOffset, float& OutTangent, float& OutWeight)
{
double X = CurveSpace.ScreenToSeconds(TangentOffset.X) - CurveSpace.ScreenToSeconds(0);
double Y = CurveSpace.ScreenToValue(TangentOffset.Y) - CurveSpace.ScreenToValue(0);
OutTangent = Y / X;
OutWeight = FMath::Sqrt(X*X + Y*Y);
}
FVector2D GetVectorFromSlopeAndLength(float Slope, float Length)
{
float x = Length / FMath::Sqrt(Slope*Slope + 1.f);
float y = Slope * x;
return FVector2D(x, y);
}
void PopulateGridLineValues(float PhysicalSize, double ViewMin, double ViewMax, uint8 InMinorDivisions, TArray<double>& OutMajorGridLines, TArray<double>& OutMinorGridLines)
{
const double PixelsPerValue = PhysicalSize / FMath::Max(ViewMax - ViewMin, 1e-10);
const double GridPixelSpacing = PhysicalSize / 5.0;
const double Order = FMath::Pow(10.0, FMath::FloorToInt(FMath::LogX(10.0, GridPixelSpacing / PixelsPerValue)));
static const int32 DesirableBases[] = { 2, 5 };
static const int32 NumDesirableBases = UE_ARRAY_COUNT(DesirableBases);
const int32 Scale = FMath::RoundToInt(GridPixelSpacing / PixelsPerValue / Order);
int32 Base = DesirableBases[0];
for (int32 BaseIndex = 1; BaseIndex < NumDesirableBases; ++BaseIndex)
{
if (FMath::Abs(Scale - DesirableBases[BaseIndex]) < FMath::Abs(Scale - Base))
{
Base = DesirableBases[BaseIndex];
}
}
double MajorGridStep = FMath::Pow(static_cast<float>(Base), FMath::FloorToFloat(FMath::LogX(static_cast<float>(Base), static_cast<float>(Scale)))) * Order;
const double FirstMajorLine = FMath::FloorToDouble(ViewMin / MajorGridStep) * MajorGridStep;
const double LastMajorLine = FMath::CeilToDouble(ViewMax / MajorGridStep) * MajorGridStep;
for (double CurrentMajorLine = FirstMajorLine; CurrentMajorLine <= LastMajorLine; CurrentMajorLine += MajorGridStep)
{
OutMajorGridLines.Add(CurrentMajorLine);
for (int32 Step = 1; Step < InMinorDivisions; ++Step)
{
OutMinorGridLines.Add(CurrentMajorLine + Step * MajorGridStep / InMinorDivisions);
}
}
}
void ConstructYGridLines(const FCurveEditorScreenSpace& ViewSpace, uint8 InMinorDivisions, TArray<float>& OutMajorGridLines, TArray<float>& OutMinorGridLines, FText GridLineLabelFormatY, TArray<FText>* OutMajorGridLabels)
{
const double GridPixelSpacing = ViewSpace.GetPhysicalHeight() / 5.0;
const double Order = FMath::Pow(10.0, FMath::FloorToInt(FMath::LogX(10.0, GridPixelSpacing / ViewSpace.PixelsPerOutput())));
static const int32 DesirableBases[] = { 2, 5 };
static const int32 NumDesirableBases = UE_ARRAY_COUNT(DesirableBases);
const int32 Scale = FMath::RoundToInt(GridPixelSpacing / ViewSpace.PixelsPerOutput() / Order);
int32 Base = DesirableBases[0];
for (int32 BaseIndex = 1; BaseIndex < NumDesirableBases; ++BaseIndex)
{
if (FMath::Abs(Scale - DesirableBases[BaseIndex]) < FMath::Abs(Scale - Base))
{
Base = DesirableBases[BaseIndex];
}
}
double MajorGridStep = FMath::Pow(static_cast<float>(Base), FMath::FloorToFloat(FMath::LogX(static_cast<float>(Base), static_cast<float>(Scale)))) * Order;
const double FirstMajorLine = FMath::FloorToDouble(ViewSpace.GetOutputMin() / MajorGridStep) * MajorGridStep;
const double LastMajorLine = FMath::CeilToDouble(ViewSpace.GetOutputMax() / MajorGridStep) * MajorGridStep;
FNumberFormattingOptions FormattingOptions;
FormattingOptions.SetMaximumFractionalDigits(6);
for (double CurrentMajorLine = FirstMajorLine; CurrentMajorLine <= LastMajorLine; CurrentMajorLine += MajorGridStep)
{
OutMajorGridLines.Add(ViewSpace.ValueToScreen(CurrentMajorLine));
if (OutMajorGridLabels)
{
OutMajorGridLabels->Add(FText::Format(GridLineLabelFormatY, FText::AsNumber(CurrentMajorLine, &FormattingOptions)));
}
for (int32 Step = 1; Step < InMinorDivisions; ++Step)
{
OutMinorGridLines.Add(ViewSpace.ValueToScreen(CurrentMajorLine + Step * MajorGridStep / InMinorDivisions));
}
}
}
void ConstructFixedYGridLines(const FCurveEditorScreenSpace& ViewSpace, uint8 InMinorDivisions, double InMinorGridStep, TArray<float>& OutMajorGridLines, TArray<float>& OutMinorGridLines, FText GridLineLabelFormatY,
TArray<FText>* OutMajorGridLabels, TOptional<double> InOutputMin, TOptional<double> InOutputMax)
{
const double MajorGridStep = InMinorGridStep * InMinorDivisions;
const double FirstMinorLine = InOutputMin ? FMath::CeilToDouble(InOutputMin.GetValue() / InMinorGridStep) * InMinorGridStep
: FMath::FloorToDouble(ViewSpace.GetOutputMin() / InMinorGridStep) * InMinorGridStep;
const double LastMinorLine = InOutputMax ? FMath::FloorToDouble(InOutputMax.GetValue() / InMinorGridStep) * InMinorGridStep
: FMath::CeilToDouble(ViewSpace.GetOutputMax() / InMinorGridStep) * InMinorGridStep;
FNumberFormattingOptions FormattingOptions;
FormattingOptions.SetMaximumFractionalDigits(6);
// calculate min. distance between labels
const FSlateFontInfo FontInfo = FCoreStyle::Get().GetFontStyle("ToolTip.LargerFont");
const TSharedRef<FSlateFontMeasure> FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
uint16 FontHeight = FontMeasureService->GetMaxCharacterHeight(FontInfo);
const double LabelDist = (1 / ViewSpace.PixelsPerOutput()) * (FontHeight + 3.0); // 3.0 for margin
double LineSkip = FMath::CeilToDouble(LabelDist / MajorGridStep) * MajorGridStep;
LineSkip = FMath::IsNearlyZero(LineSkip) ? KINDA_SMALL_NUMBER : LineSkip; // prevent mod by zero errors
for (double CurrentMinorLine = FirstMinorLine; CurrentMinorLine <= LastMinorLine; CurrentMinorLine += InMinorGridStep)
{
// check if is major grid line
if (FMath::IsNearlyZero(FMath::Fmod(FMath::Abs(CurrentMinorLine), MajorGridStep)))
{
OutMajorGridLines.Add(ViewSpace.ValueToScreen(CurrentMinorLine));
if (OutMajorGridLabels)
{
OutMajorGridLabels->Add(FMath::IsNearlyZero(FMath::Fmod(FMath::Abs(CurrentMinorLine), LineSkip))
? FText::Format(GridLineLabelFormatY, FText::AsNumber(CurrentMinorLine, &FormattingOptions))
: FText());
}
}
else
{
OutMinorGridLines.Add(ViewSpace.ValueToScreen(CurrentMinorLine));
}
}
}
} // namespace CurveEditor