diff --git a/ReadMe.md b/ReadMe.md index 93318fd..7b87781 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -12,6 +12,7 @@ which makes a bunch of things better: * [Debug visualisation](https://www.stevestreeting.com/2021/09/14/ue4-editor-visualisation-helper/) * [Better DataTable Row References](https://www.stevestreeting.com/2023/10/06/a-better-unreal-datatable-row-picker/) * [Light Flicker](doc/LightFlicker.md) +* [Easing Functions](Source/StevesUEHelpers/Public/StevesEasings.h) * [Replicated Physics Actor](Source/StevesUEHelpers/Public/StevesReplicatedPhysicsActor.h) * Halton Sequence based random stream diff --git a/Source/StevesUEHelpers/Private/StevesEasings.cpp b/Source/StevesUEHelpers/Private/StevesEasings.cpp new file mode 100644 index 0000000..ecc7c39 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesEasings.cpp @@ -0,0 +1,114 @@ +#include "StevesEasings.h" + +float UStevesEasings::EaseAlpha(float InAlpha, EStevesEaseFunction Func) +{ + constexpr float BackC1 = 1.70158f; + constexpr float BackC2 = BackC1 * 1.525f; + constexpr float BackC3 = BackC1 + 1.f; + constexpr float ElasticC4 = UE_TWO_PI / 3.f; + constexpr float ElasticC5 = UE_TWO_PI / 4.5; + + switch(Func) + { + default: + case EStevesEaseFunction::Linear: + return InAlpha; + case EStevesEaseFunction::EaseIn_Sine: + return 1.f - FMath::Cos(InAlpha * UE_HALF_PI); + case EStevesEaseFunction::EaseOut_Sine: + return FMath::Sin(InAlpha * UE_HALF_PI); + case EStevesEaseFunction::EaseInOut_Sine: + return -(FMath::Cos(UE_PI * InAlpha) - 1.f) / 2.f; + case EStevesEaseFunction::EaseIn_Quad: + return InAlpha*InAlpha; + case EStevesEaseFunction::EaseOut_Quad: + return 1.f - (1.f - InAlpha) * (1.f - InAlpha); + case EStevesEaseFunction::EaseInOut_Quad: + return InAlpha < 0.5f ? 2.f * InAlpha * InAlpha : 1.f - FMath::Pow(-2.f * InAlpha + 2.f, 2.f) / 2.f; + case EStevesEaseFunction::EaseIn_Cubic: + return FMath::Pow(InAlpha, 3); + case EStevesEaseFunction::EaseOut_Cubic: + return 1.f - FMath::Pow(1.f - InAlpha, 3); + case EStevesEaseFunction::EaseInOut_Cubic: + return InAlpha < 0.5f ? 4.f * FMath::Pow(InAlpha, 3) : 1.f - FMath::Pow(-2.f * InAlpha + 2.f, 3) / 2.f; + case EStevesEaseFunction::EaseIn_Quart: + return FMath::Pow(InAlpha, 4); + case EStevesEaseFunction::EaseOut_Quart: + return 1 - FMath::Pow(1.f - InAlpha, 4.f); + case EStevesEaseFunction::EaseInOut_Quart: + return InAlpha < 0.5f ? 8.f * FMath::Pow(InAlpha, 4) : 1.f - FMath::Pow(-2.f * InAlpha + 2.f, 4) / 2.f; + case EStevesEaseFunction::EaseIn_Quint: + return FMath::Pow(InAlpha, 5); + case EStevesEaseFunction::EaseOut_Quint: + return 1 - FMath::Pow(1.f - InAlpha, 5); + case EStevesEaseFunction::EaseInOut_Quint: + return InAlpha < 0.5f ? 16.f * FMath::Pow(InAlpha, 5) : 1.f - FMath::Pow(-2.f * InAlpha + 2.f, 5) / 2.f; + case EStevesEaseFunction::EaseIn_Expo: + return InAlpha <= 0 ? 0 : FMath::Pow(2.f, 10.f * InAlpha - 10.f); + case EStevesEaseFunction::EaseOut_Expo: + return InAlpha >= 1.f ? 1.f : 1.f - FMath::Pow(2.f, -10.f * InAlpha); + case EStevesEaseFunction::EaseInOut_Expo: + if (InAlpha <= 0.f) + return 0; + if (InAlpha >= 1.f) + return 1; + return InAlpha < 0.5f + ? FMath::Pow(2.f, 20.f * InAlpha - 10.f) / 2.f + : (2.f - FMath::Pow(2.f, -20.f * InAlpha + 10.f)) / 2.f; + case EStevesEaseFunction::EaseIn_Circ: + return 1.f - FMath::Sqrt(1.f - FMath::Pow(InAlpha, 2)); + case EStevesEaseFunction::EaseOut_Circ: + return FMath::Sqrt(1.f - FMath::Pow(InAlpha - 1.f, 2)); + case EStevesEaseFunction::EaseInOut_Circ: + return InAlpha < 0.5f + ? (1.f - FMath::Sqrt(1.f - FMath::Pow(2.f * InAlpha, 2))) / 2.f + : (FMath::Sqrt(1.f - FMath::Pow(-2.f * InAlpha + 2.f, 2)) + 1.f) / 2.f; + case EStevesEaseFunction::EaseIn_Back: + return BackC3 * FMath::Pow(InAlpha, 3) - BackC1 * InAlpha * InAlpha; + case EStevesEaseFunction::EaseOut_Back: + return 1.f + BackC3 * FMath::Pow(InAlpha - 1.f, 3) + BackC1 * FMath::Pow(InAlpha - 1.f, 2.f); + case EStevesEaseFunction::EaseInOut_Back: + return InAlpha < 0.5f + ? (FMath::Pow(2.f * InAlpha, 2) * ((BackC2 + 1.f) * 2.f * InAlpha - BackC2)) / 2.f + : (FMath::Pow(2.f * InAlpha - 2.f, 2) * ((BackC2 + 1) * (InAlpha * 2.f - 2.f) + BackC2) + 2.f) / 2.f; + case EStevesEaseFunction::EaseIn_Elastic: + if (InAlpha <= 0.f) + return 0; + + if (InAlpha >= 1.f) + return 1; + return -FMath::Pow(2.f, 10.f * InAlpha - 10.f) * FMath::Sin((InAlpha * 10.f - 10.75f) * ElasticC4); + case EStevesEaseFunction::EaseOut_Elastic: + if (InAlpha <= 0.f) + return 0; + if (InAlpha >= 1.f) + return 1; + return FMath::Pow(2.f, -10.f * InAlpha) * FMath::Sin((InAlpha * 10.f - 0.75f) * ElasticC4) + 1.f; + case EStevesEaseFunction::EaseInOut_Elastic: + if (InAlpha <= 0.f) + return 0; + if (InAlpha >= 1.f) + return 1; + return InAlpha < 0.5f + ? -(FMath::Pow(2.f, 20.f * InAlpha - 10.f) * FMath::Sin((20.f * InAlpha - 11.125f) * ElasticC5)) / + 2.f + : (FMath::Pow(2.f, -20.f * InAlpha + 10.f) * FMath::Sin((20.f * InAlpha - 11.125f) * ElasticC5)) / + 2.f + 1.f; + case EStevesEaseFunction::EaseIn_Bounce: + return 1 - EaseAlpha(1 - InAlpha, EStevesEaseFunction::EaseOut_Bounce); + case EStevesEaseFunction::EaseOut_Bounce: + { + constexpr float n1 = 7.5625f; + constexpr float d1 = 2.75f; + + if (InAlpha < 1.f / d1) { return n1 * InAlpha * InAlpha; } + else if (InAlpha < 2.f / d1) { return n1 * (InAlpha -= 1.5f / d1) * InAlpha + 0.75f; } + else if (InAlpha < 2.5f / d1) { return n1 * (InAlpha -= 2.25f / d1) * InAlpha + 0.9375f; } + else { return n1 * (InAlpha -= 2.625f / d1) * InAlpha + 0.984375f; } + } + case EStevesEaseFunction::EaseInOut_Bounce: + return InAlpha < 0.5f + ? (1.f - EaseAlpha(1.f - 2.f * InAlpha, EStevesEaseFunction::EaseOut_Bounce)) / 2.f + : (1.f + EaseAlpha(2.f * InAlpha - 1.f, EStevesEaseFunction::EaseOut_Bounce)) / 2.f; + } +} diff --git a/Source/StevesUEHelpers/Public/StevesEasings.h b/Source/StevesUEHelpers/Public/StevesEasings.h new file mode 100644 index 0000000..c94fdfd --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesEasings.h @@ -0,0 +1,134 @@ +#pragma once + +#include "CoreMinimal.h" +#include "StevesEasings.generated.h" + +/// Easing functions +/// See https://easings.net/ +/// Could have used UE EEasingFunc but it's missing some nice options like back/elastic +UENUM(BlueprintType) +enum class EStevesEaseFunction : uint8 +{ + Linear, + EaseIn_Sine, + EaseOut_Sine, + EaseInOut_Sine, + EaseIn_Quad, + EaseOut_Quad, + EaseInOut_Quad, + EaseIn_Cubic, + EaseOut_Cubic, + EaseInOut_Cubic, + EaseIn_Quart, + EaseOut_Quart, + EaseInOut_Quart, + EaseIn_Quint, + EaseOut_Quint, + EaseInOut_Quint, + EaseIn_Expo, + EaseOut_Expo, + EaseInOut_Expo, + EaseIn_Circ, + EaseOut_Circ, + EaseInOut_Circ, + EaseIn_Back, + EaseOut_Back, + EaseInOut_Back, + EaseIn_Elastic, + EaseOut_Elastic, + EaseInOut_Elastic, + EaseIn_Bounce, + EaseOut_Bounce, + EaseInOut_Bounce +}; + +UCLASS() +class STEVESUEHELPERS_API UStevesEasings : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + /** + * Convert a linear alpha value into an eased alpha value using an easing function + * @param InAlpha The input linear alpha + * @param Func The easing function + * @return The eased version of the alpha + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static float EaseAlpha(float InAlpha, EStevesEaseFunction Func); + + /** + * Interpolate with easing function support + * @param A Input Value from + * @param B Input Value to + * @param Alpha Value between 0 and 1 + * @param Func Easing function + * @return Interpolated value + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static float EaseFloat(float A, float B, float Alpha, EStevesEaseFunction Func) + { + return A + EaseAlpha(Alpha, Func) * (B - A); + } + /** + * Interpolate with easing function support + * @param A Input Value from + * @param B Input Value to + * @param Alpha Value between 0 and 1 + * @param Func Easing function + * @return Interpolated value + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static FVector EaseVector(const FVector& A, const FVector& B, float Alpha, EStevesEaseFunction Func) + { + return A + EaseAlpha(Alpha, Func) * (B - A); + } + /** + * Interpolate with easing function support + * @param A Input Value from + * @param B Input Value to + * @param Alpha Value between 0 and 1 + * @param Func Easing function + * @return Interpolated value + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static FRotator EaseRotator(const FRotator& A, const FRotator& B, float Alpha, EStevesEaseFunction Func, bool bShortest) + { + if (bShortest) + return EaseQuat(FQuat(A), FQuat(B), Alpha, Func).Rotator(); + + return A + EaseAlpha(Alpha, Func) * (B - A); + } + /** + * Interpolate with easing function support + * @param A Input Value from + * @param B Input Value to + * @param Alpha Value between 0 and 1 + * @param Func Easing function + * @return Interpolated value + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static FQuat EaseQuat(const FQuat& A, const FQuat& B, float Alpha, EStevesEaseFunction Func) + { + return FQuat::Slerp(A, B, EaseAlpha(Alpha, Func)); + } + /** + * Interpolate with easing function support + * @param A Input Value from + * @param B Input Value to + * @param Alpha Value between 0 and 1 + * @param Func Easing function + * @return Interpolated value + */ + UFUNCTION(BlueprintCallable, Category="StevesEaseMath") + static FTransform EaseTransform(const FTransform& A, const FTransform& B, float Alpha, EStevesEaseFunction Func) + { + return FTransform( + EaseQuat(A.GetRotation(), B.GetRotation(), Alpha, Func), + EaseVector(A.GetLocation(), B.GetLocation(), Alpha, Func), + EaseVector(A.GetScale3D(), B.GetScale3D(), Alpha, Func)); + } + + +};