Understanding lerp()
A lerp(a, b, t)
—or linear-interpolation—is a function that returns a new value between two known samples, such that when is , then value of is returned, when is then the value of is returned, and when is then the value equidistant between and is returned.
Consider the following:
const lerp = (a: number, b: number, t: number) => a + t * (b - a);
We can use this function to generate new values between two existing, known values:
const [start, end] = [10, 50];
console.log(lerp(start, end, 0.1)); /* at 10% */
// ❮ 14
console.log(lerp(start, end, 0.5)); /* at 50% */
// ❮ 30
console.log(lerp(start, end, 0.986)); /* at 98.6% */
// ❮ 49.44
But Wait!
There are potential risks involved with the previous implementation due to floating-point arithmetic errors. In this case, due to rounding errors, this method does not guarantee that lerp(a, b, 1.0) === b
. For example, let's try to lerp between one huge value and one very small value, using the previous implementation:
console.log(lerp(1e8, 1e-8, 0.0));
// ✅ 100,000,000
console.log(lerp(1e8, 1e-8, 1.0));
// ❌ 0
Ouchies! Because the difference between and is smaller that what a 64-bit floating point number can represent, it gets rounded out when doing . We end up getting a value of , which is outside the range of . To correct for this, it is sometimes recommended to use this implementation, instead:
const lerp = (a: number, b: number, t: number) => (1 - t) * a + t * b;
Given that is in the range of , this method will precisely start and end with and respectively.
console.log(lerp(1e8, 1e-8, 0.0));
// ✅ 100,000,000
console.log(lerp(1e8, 1e-8, 1));
// ✅ 0.00000001
However, this is not without its own caveat: outside the range of then this function may not be monotonic. Again, this is due to the limitations of floating-point approximations.
Higher-Order Lerps
It is also sometimes useful to lerp between vectors or tuples, this can easily be achieved by lerping over each component of the vector/tuple:
type Vec2 = [number, number];
const vecLerp = (a: Vec2, b: Vec2, t: number): Vec2 => [
lerp(a[0], b[0], t),
lerp(a[1], b[1], t),
];
This can obviously be extended to vectors and tuples of any length.