use num_traits::{FromPrimitive, ToPrimitive};
pub trait Lerp {
fn lerp(&self, y1: &Self, x: f32) -> Self;
}
impl<Convertible> Lerp for Convertible
where
Convertible: FromPrimitive + ToPrimitive,
{
fn lerp(&self, y1: &Self, x: f32) -> Self {
let a = self.to_f32().expect("Start value does not fit in an f32");
let b = y1.to_f32().expect("End value does not fit in an f32");
let result_f32 = a * (1.0 - x) + b * x;
let converted = Convertible::from_f32(result_f32)
.expect("Converted value was outside the valid range for this type.");
if result_f32 - converted.to_f32().unwrap() > 0.5 {
Convertible::from_f32(result_f32 + 0.5)
.expect("Converted value was outside the valid range for this type.")
} else {
converted
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::fmt::Debug;
#[test]
fn lerp_narrow_type_full_range() {
test_lerp(0, 255, 0.0, 0u8);
test_lerp(0, 255, 0.25, 64u8);
test_lerp(0, 255, 0.5, 127u8);
test_lerp(0, 255, 1.0, 255u8);
}
#[test]
fn lerp_narrow_type_partial_range() {
test_lerp(-64, 64, 0.0, -64i8);
test_lerp(-64, 64, 0.25, -32i8);
test_lerp(-64, 64, 0.5, 0i8);
test_lerp(-64, 64, 1.0, 64i8);
}
#[test]
fn lerp_same_type() {
test_lerp(0.0, 1.0, 0.0, 0.0f32);
test_lerp(0.0, 1.0, 0.314, 0.314f32);
test_lerp(0.0, 1.0, 1.0, 1.0f32);
test_lerp(1.25e5, 6.77e5, 0.4, 3.458e5f32)
}
#[test]
fn lerp_wider_type() {
assert_eq!(0.0.lerp(&1.0, 0.0), 0.0f64);
assert_relative_eq!(0.0.lerp(&1.0, 0.314), 0.314f64, epsilon = 0.00001);
assert_eq!(0.0.lerp(&1.0, 1.0), 1.0f64);
assert_relative_eq!(1.25e5.lerp(&6.77e5, 0.4), 3.458e5f64, epsilon = 0.00001);
}
#[test]
fn lerp_empty_range() {
test_lerp(0.5, 0.5, 0.0, 0.5);
test_lerp(0.5, 0.5, 0.123, 0.5);
test_lerp(0.5, 0.5, 1.0, 0.5);
}
fn test_lerp<V: Debug + Lerp + PartialEq>(from: V, to: V, t: f32, expected: V) {
assert_eq!(from.lerp(&to, t), expected);
}
}