2 Min. read

Quick Tip: Lerping Custom Data Types In UE5

The Unreal math library provides support for linear interpolation of values via the FMath::Lerp method. That works great with many built-in types, but doesn’t know how to deal with custom data types:

float Result = FMath::Lerp(0.0f, 1.0f, 0.5f);
FVector Result2 = FMath::Lerp(FVector::ZeroVector, FVector::OneVector, 0.5f);
FRotator Result3 = FMath::Lerp(FRotator::ZeroRotator, FRotator(0, 90, 0), 0.5f);
 
struct FMyData
{
	FVector Location;
	FRotator Rotation;
};
 
// This doesn't work:
const FMyData FirstValue { FVector::ZeroVector, FRotator::ZeroRotator };
const FMyData SecondValue { FVector::OneVector, FRotator(0, 90, 0) };
FMyData Result4 = FMath::Lerp(FirstValue, SecondValue, 0.5f);

A solution could be to manually call FMath::Lerp on each member, or add a static Lerp method to the structure, but personally I’d prefer to use FMath::Lerp directly as I find it more readable and consistent.

TCustomLerp is a new templated helper in UE5 that can be specialized for any type we want to interpolate:

template<>
struct TCustomLerp<FMyData>
{
	// Required to make FMath::Lerp use our custom function below
	enum { Value = true };
 
	template<typename U>
	static FMyData Lerp(const FMyData& A, const FMyData& B, const U& Alpha)
	{
		return {
			FMath::Lerp(A.Location, B.Location, Alpha),
			FMath::Lerp(A.Rotation, B.Rotation, Alpha),
		};
	}
};
 
// The following is now possible:
const FMyData FirstValue { FVector::ZeroVector, FRotator::ZeroRotator };
const FMyData SecondValue { FVector::OneVector, FRotator(0, 90, 0) };
FMyData Result = FMath::Lerp(FirstValue, SecondValue, 0.5f);

TCustomLerp is also used internally by the engine to make FMath::Lerp support types for which interpolation is non-trivial (such as FRotator and FQuat).

Bonus: lerp any engine struct

TCustomLerp can be used for any data type, including built-in ones such as FMargin that aren’t supported by FMath::Lerp out-of-the-box:

template<>
struct TCustomLerp<FMargin>
{
	enum { Value = true };
 
	template<typename U>
	static FMargin Lerp(const FMargin& A, const FMargin& B, const U& Alpha)
	{
		return FMargin(
			FMath::Lerp(A.Left, B.Left, Alpha),
			FMath::Lerp(A.Top, B.Top, Alpha),
			FMath::Lerp(A.Right, B.Right, Alpha),
			FMath::Lerp(A.Bottom, B.Bottom, Alpha)
		);
	}
};
 
FMargin Result = FMath::Lerp(FMargin(0), FMargin(10), 0.5f);

Bonus 2: BiLerp and CubicInterp

FMath::BiLerp and FMath::CubicInterp are also supported:

template<>
struct TCustomLerp<FMyData>
{
	enum { Value = true };
 
	template<typename U>
	static FMyData BiLerp(const FMyData& P00, const FMyData& P10, const FMyData& P01, const FMyData& P11, const U& FracX, const U& FracY)
	{
		// ...
	}
 
	template<typename U>
	static FMyData CubicInterp(const FMyData& P0, const FMyData& T0, const FMyData& P1, const FMyData& T1, const U& A)
	{
		// ...
	}
};