use crate::gf;
use crate::sdf::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum InterpolationType {
Held,
#[default]
Linear,
}
pub fn evaluate(samples: &[(f64, Value)], time: f64, mode: InterpolationType) -> Option<Value> {
if samples.is_empty() {
return None;
}
let (first_t, first_v) = &samples[0];
if time <= *first_t {
return clean(first_v.clone());
}
let (last_t, last_v) = samples.last().unwrap();
if time >= *last_t {
return clean(last_v.clone());
}
let idx = samples.binary_search_by(|(t, _)| t.partial_cmp(&time).unwrap_or(std::cmp::Ordering::Equal));
let (lo, hi) = match idx {
Ok(i) => return clean(samples[i].1.clone()),
Err(i) => (i - 1, i),
};
let (t_lo, v_lo) = &samples[lo];
let (t_hi, v_hi) = &samples[hi];
if is_blocked(v_lo) || is_blocked(v_hi) {
return None;
}
match mode {
InterpolationType::Held => clean(v_lo.clone()),
InterpolationType::Linear => {
let t = (time - t_lo) / (t_hi - t_lo);
lerp_value(v_lo, v_hi, t).or_else(|| clean(v_lo.clone()))
}
}
}
fn is_blocked(v: &Value) -> bool {
matches!(v, Value::ValueBlock | Value::None)
}
fn clean(v: Value) -> Option<Value> {
if is_blocked(&v) {
None
} else {
Some(v)
}
}
fn lerp_value(a: &Value, b: &Value, t: f64) -> Option<Value> {
use crate::sdf::Value as V;
let t32 = t as f32;
Some(match (a, b) {
(V::Half(x), V::Half(y)) => V::Half(gf::lerp_half(*x, *y, t32)),
(V::Float(x), V::Float(y)) => V::Float(gf::lerp(*x, *y, t32)),
(V::Double(x), V::Double(y)) => V::Double(gf::lerp(*x, *y, t)),
(V::TimeCode(x), V::TimeCode(y)) => V::TimeCode(gf::lerp(*x, *y, t)),
(V::Matrix2d(x), V::Matrix2d(y)) => V::Matrix2d(x.lerp(*y, t)),
(V::Matrix3d(x), V::Matrix3d(y)) => V::Matrix3d(x.lerp(*y, t)),
(V::Matrix4d(x), V::Matrix4d(y)) => V::Matrix4d(x.lerp(*y, t)),
(V::Vec2h(x), V::Vec2h(y)) => V::Vec2h(x.lerp(*y, t32)),
(V::Vec2f(x), V::Vec2f(y)) => V::Vec2f(x.lerp(*y, t32)),
(V::Vec2d(x), V::Vec2d(y)) => V::Vec2d(x.lerp(*y, t)),
(V::Vec3h(x), V::Vec3h(y)) => V::Vec3h(x.lerp(*y, t32)),
(V::Vec3f(x), V::Vec3f(y)) => V::Vec3f(x.lerp(*y, t32)),
(V::Vec3d(x), V::Vec3d(y)) => V::Vec3d(x.lerp(*y, t)),
(V::Vec4h(x), V::Vec4h(y)) => V::Vec4h(x.lerp(*y, t32)),
(V::Vec4f(x), V::Vec4f(y)) => V::Vec4f(x.lerp(*y, t32)),
(V::Vec4d(x), V::Vec4d(y)) => V::Vec4d(x.lerp(*y, t)),
(V::Quath(x), V::Quath(y)) => V::Quath(x.slerp(*y, t)),
(V::Quatf(x), V::Quatf(y)) => V::Quatf(x.slerp(*y, t)),
(V::Quatd(x), V::Quatd(y)) => V::Quatd(x.slerp(*y, t)),
(V::HalfVec(x), V::HalfVec(y)) if x.len() == y.len() => {
V::HalfVec(x.iter().zip(y).map(|(a, b)| gf::lerp_half(*a, *b, t32)).collect())
}
(V::FloatVec(x), V::FloatVec(y)) if x.len() == y.len() => {
V::FloatVec(x.iter().zip(y).map(|(a, b)| gf::lerp(*a, *b, t32)).collect())
}
(V::DoubleVec(x), V::DoubleVec(y)) if x.len() == y.len() => {
V::DoubleVec(x.iter().zip(y).map(|(a, b)| gf::lerp(*a, *b, t)).collect())
}
(V::TimeCodeVec(x), V::TimeCodeVec(y)) if x.len() == y.len() => {
V::TimeCodeVec(x.iter().zip(y).map(|(a, b)| gf::lerp(*a, *b, t)).collect())
}
(V::Matrix2dVec(x), V::Matrix2dVec(y)) if x.len() == y.len() => {
V::Matrix2dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::Matrix3dVec(x), V::Matrix3dVec(y)) if x.len() == y.len() => {
V::Matrix3dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::Matrix4dVec(x), V::Matrix4dVec(y)) if x.len() == y.len() => {
V::Matrix4dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::Vec2hVec(x), V::Vec2hVec(y)) if x.len() == y.len() => {
V::Vec2hVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec2fVec(x), V::Vec2fVec(y)) if x.len() == y.len() => {
V::Vec2fVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec2dVec(x), V::Vec2dVec(y)) if x.len() == y.len() => {
V::Vec2dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::Vec3hVec(x), V::Vec3hVec(y)) if x.len() == y.len() => {
V::Vec3hVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec3fVec(x), V::Vec3fVec(y)) if x.len() == y.len() => {
V::Vec3fVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec3dVec(x), V::Vec3dVec(y)) if x.len() == y.len() => {
V::Vec3dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::Vec4hVec(x), V::Vec4hVec(y)) if x.len() == y.len() => {
V::Vec4hVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec4fVec(x), V::Vec4fVec(y)) if x.len() == y.len() => {
V::Vec4fVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t32)).collect())
}
(V::Vec4dVec(x), V::Vec4dVec(y)) if x.len() == y.len() => {
V::Vec4dVec(x.iter().zip(y).map(|(a, b)| a.lerp(*b, t)).collect())
}
(V::QuathVec(x), V::QuathVec(y)) if x.len() == y.len() => {
V::QuathVec(x.iter().zip(y).map(|(a, b)| a.slerp(*b, t)).collect())
}
(V::QuatfVec(x), V::QuatfVec(y)) if x.len() == y.len() => {
V::QuatfVec(x.iter().zip(y).map(|(a, b)| a.slerp(*b, t)).collect())
}
(V::QuatdVec(x), V::QuatdVec(y)) if x.len() == y.len() => {
V::QuatdVec(x.iter().zip(y).map(|(a, b)| a.slerp(*b, t)).collect())
}
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn samples_f64(pairs: &[(f64, f64)]) -> Vec<(f64, Value)> {
pairs.iter().map(|(t, v)| (*t, Value::Double(*v))).collect()
}
#[test]
fn empty_samples_return_none() {
assert!(evaluate(&[], 0.0, InterpolationType::Linear).is_none());
assert!(evaluate(&[], 0.0, InterpolationType::Held).is_none());
}
#[test]
fn before_first_clamps_to_first() {
let s = samples_f64(&[(10.0, 1.0), (20.0, 2.0)]);
assert_eq!(evaluate(&s, 0.0, InterpolationType::Linear), Some(Value::Double(1.0)));
assert_eq!(evaluate(&s, 0.0, InterpolationType::Held), Some(Value::Double(1.0)));
}
#[test]
fn after_last_clamps_to_last() {
let s = samples_f64(&[(10.0, 1.0), (20.0, 2.0)]);
assert_eq!(evaluate(&s, 100.0, InterpolationType::Linear), Some(Value::Double(2.0)));
assert_eq!(evaluate(&s, 100.0, InterpolationType::Held), Some(Value::Double(2.0)));
}
#[test]
fn linear_double_lerps() {
let s = samples_f64(&[(0.0, 0.0), (10.0, 20.0)]);
assert_eq!(evaluate(&s, 5.0, InterpolationType::Linear), Some(Value::Double(10.0)));
}
#[test]
fn held_returns_previous_sample() {
let s = samples_f64(&[(0.0, 0.0), (10.0, 20.0)]);
assert_eq!(evaluate(&s, 5.0, InterpolationType::Held), Some(Value::Double(0.0)));
}
#[test]
fn linear_unsupported_type_falls_back_to_held() {
let s = vec![(0.0, Value::String("a".into())), (10.0, Value::String("b".into()))];
assert_eq!(
evaluate(&s, 5.0, InterpolationType::Linear),
Some(Value::String("a".into()))
);
}
#[test]
fn linear_blocked_sample_returns_none() {
let s = vec![(0.0, Value::Double(1.0)), (10.0, Value::ValueBlock)];
assert_eq!(evaluate(&s, 5.0, InterpolationType::Linear), None);
assert_eq!(evaluate(&s, 10.0, InterpolationType::Linear), None);
}
#[test]
fn linear_vec3f_lerps_componentwise() {
let s = vec![
(0.0, Value::vec3f(0.0_f32, 0.0, 0.0)),
(10.0, Value::vec3f(10.0_f32, 20.0, 30.0)),
];
assert_eq!(
evaluate(&s, 5.0, InterpolationType::Linear),
Some(Value::vec3f(5.0_f32, 10.0, 15.0))
);
}
#[test]
fn linear_matrix4d_lerps_componentwise() {
let mut a = [0.0f64; 16];
let mut b = [0.0f64; 16];
a[0] = 1.0;
b[0] = 3.0;
let s = vec![
(0.0, Value::Matrix4d(gf::Matrix4d(a))),
(10.0, Value::Matrix4d(gf::Matrix4d(b))),
];
let out = evaluate(&s, 5.0, InterpolationType::Linear).unwrap();
if let Value::Matrix4d(m) = out {
assert!((m[(0, 0)] - 2.0).abs() < 1e-9);
} else {
panic!("expected Matrix4d, got {out:?}");
}
}
#[test]
fn linear_vec3f_array_lerps_per_element() {
let s = vec![
(
0.0,
Value::Vec3fVec(vec![gf::vec3f(0.0, 0.0, 0.0), gf::vec3f(10.0, 0.0, 0.0)]),
),
(
10.0,
Value::Vec3fVec(vec![gf::vec3f(10.0, 0.0, 0.0), gf::vec3f(10.0, 20.0, 0.0)]),
),
];
let out = evaluate(&s, 5.0, InterpolationType::Linear).unwrap();
assert_eq!(
out,
Value::Vec3fVec(vec![gf::vec3f(5.0, 0.0, 0.0), gf::vec3f(10.0, 10.0, 0.0)])
);
}
#[test]
fn linear_quatf_array_slerps_per_element() {
let s = vec![
(0.0, Value::QuatfVec(vec![gf::quatf(1.0, 0.0, 0.0, 0.0)])),
(10.0, Value::QuatfVec(vec![gf::quatf(0.0, 1.0, 0.0, 0.0)])),
];
let out = evaluate(&s, 5.0, InterpolationType::Linear).unwrap();
if let Value::QuatfVec(v) = out {
let expected_w = std::f32::consts::FRAC_PI_4.cos();
let expected_x = std::f32::consts::FRAC_PI_4.sin();
assert!((v[0].w - expected_w).abs() < 1e-5);
assert!((v[0].x - expected_x).abs() < 1e-5);
} else {
panic!("expected QuatfVec, got {out:?}");
}
}
#[test]
fn linear_array_length_mismatch_falls_back_to_held() {
let s = vec![
(0.0, Value::FloatVec(vec![1.0, 2.0, 3.0])),
(10.0, Value::FloatVec(vec![4.0, 5.0])),
];
let out = evaluate(&s, 5.0, InterpolationType::Linear).unwrap();
assert_eq!(out, Value::FloatVec(vec![1.0, 2.0, 3.0]));
}
#[test]
fn middle_bracket() {
let s = samples_f64(&[(0.0, 0.0), (10.0, 20.0), (20.0, 30.0)]);
assert_eq!(evaluate(&s, 15.0, InterpolationType::Linear), Some(Value::Double(25.0)));
assert_eq!(evaluate(&s, 15.0, InterpolationType::Held), Some(Value::Double(20.0)));
}
#[test]
fn exact_time_hit() {
let s = samples_f64(&[(0.0, 0.0), (10.0, 20.0), (20.0, 30.0)]);
assert_eq!(evaluate(&s, 10.0, InterpolationType::Linear), Some(Value::Double(20.0)));
assert_eq!(evaluate(&s, 10.0, InterpolationType::Held), Some(Value::Double(20.0)));
}
}