CelticCraft/Plugins/VoxelFree/Source/Voxel/Public/VoxelUtilities/VoxelRichCurveUtilities.h

143 lines
No EOL
3.6 KiB
C++

// Copyright 2020 Phyronnaz
#pragma once
#include "CoreMinimal.h"
#include "Curves/RichCurve.h"
namespace FVoxelRichCurveUtilities
{
inline float BezierInterp(float P0, float P1, float P2, float P3, float Alpha)
{
const float P01 = FMath::Lerp(P0, P1, Alpha);
const float P12 = FMath::Lerp(P1, P2, Alpha);
const float P23 = FMath::Lerp(P2, P3, Alpha);
const float P012 = FMath::Lerp(P01, P12, Alpha);
const float P123 = FMath::Lerp(P12, P23, Alpha);
const float P0123 = FMath::Lerp(P012, P123, Alpha);
return P0123;
}
inline float EvalForTwoKeys(const FRichCurveKey& Key1, const FRichCurveKey& Key2, const float InTime)
{
const float Diff = Key2.Time - Key1.Time;
if (Diff > 0.f && Key1.InterpMode != RCIM_Constant)
{
const float Alpha = (InTime - Key1.Time) / Diff;
const float P0 = Key1.Value;
const float P3 = Key2.Value;
if (Key1.InterpMode == RCIM_Linear)
{
return FMath::Lerp(P0, P3, Alpha);
}
else
{
const float OneThird = 1.0f / 3.0f;
const float P1 = P0 + (Key1.LeaveTangent * Diff * OneThird);
const float P2 = P3 - (Key2.ArriveTangent * Diff * OneThird);
return BezierInterp(P0, P1, P2, P3, Alpha);
}
}
else
{
return Key1.Value;
}
}
inline float Eval(const FRichCurve& Curve, float InTime) // Inline + doesn't trigger a stat on every call
{
constexpr float InDefaultValue = 0.f;
const int32 NumKeys = Curve.Keys.Num();
// Remap time if extrapolation is present and compute offset value to use if cycling
float CycleValueOffset = 0;
Curve.RemapTimeValue(InTime, CycleValueOffset);
// If the default value hasn't been initialized, use the incoming default value
float InterpVal = Curve.DefaultValue == MAX_flt ? InDefaultValue : Curve.DefaultValue;
if (NumKeys == 0)
{
// If no keys in curve, return the Default value.
}
else if (NumKeys < 2 || (InTime <= Curve.Keys[0].Time))
{
if (Curve.PreInfinityExtrap == RCCE_Linear && NumKeys > 1)
{
const float DT = Curve.Keys[1].Time - Curve.Keys[0].Time;
if (FMath::IsNearlyZero(DT))
{
InterpVal = Curve.Keys[0].Value;
}
else
{
const float DV = Curve.Keys[1].Value - Curve.Keys[0].Value;
const float Slope = DV / DT;
InterpVal = Slope * (InTime - Curve.Keys[0].Time) + Curve.Keys[0].Value;
}
}
else
{
// Otherwise if constant or in a cycle or oscillate, always use the first key value
InterpVal = Curve.Keys[0].Value;
}
}
else if (InTime < Curve.Keys[NumKeys - 1].Time)
{
// perform a lower bound to get the second of the interpolation nodes
int32 first = 1;
int32 last = NumKeys - 1;
int32 count = last - first;
while (count > 0)
{
int32 step = count / 2;
int32 middle = first + step;
if (InTime >= Curve.Keys[middle].Time)
{
first = middle + 1;
count -= step + 1;
}
else
{
count = step;
}
}
InterpVal = EvalForTwoKeys(Curve.Keys[first - 1], Curve.Keys[first], InTime);
}
else
{
if (Curve.PostInfinityExtrap == RCCE_Linear)
{
float DT = Curve.Keys[NumKeys - 2].Time - Curve.Keys[NumKeys - 1].Time;
if (FMath::IsNearlyZero(DT))
{
InterpVal = Curve.Keys[NumKeys - 1].Value;
}
else
{
float DV = Curve.Keys[NumKeys - 2].Value - Curve.Keys[NumKeys - 1].Value;
float Slope = DV / DT;
InterpVal = Slope * (InTime - Curve.Keys[NumKeys - 1].Time) + Curve.Keys[NumKeys - 1].Value;
}
}
else
{
// Otherwise if constant or in a cycle or oscillate, always use the last key value
InterpVal = Curve.Keys[NumKeys - 1].Value;
}
}
return InterpVal + CycleValueOffset;
}
}