use super::types::{Lerp, ResolvedTrack};
pub fn find_timestamp_index(timestamps: &[u32], time: f64) -> Option<usize> {
if timestamps.is_empty() {
return None;
}
if timestamps.len() == 1 {
return Some(0);
}
let last_index = timestamps.len() - 1;
if time >= timestamps[last_index] as f64 {
return Some(last_index);
}
let mut low = 0;
let mut high = last_index;
while low < high {
let mid = (low + high).div_ceil(2);
if timestamps[mid] as f64 <= time {
low = mid;
} else {
high = mid - 1;
}
}
Some(low)
}
pub fn interpolate_track<T: Lerp + Clone>(
track: &ResolvedTrack<T>,
animation_index: usize,
time: f64,
global_times: &[f64],
default: T,
) -> T {
let effective_time = if track.uses_global_sequence() {
let gs_index = track.global_sequence as usize;
if gs_index < global_times.len() {
global_times[gs_index]
} else {
time
}
} else {
time
};
let anim_idx = if animation_index < track.timestamps.len() {
animation_index
} else if !track.timestamps.is_empty() {
0
} else {
return default;
};
let timestamps = &track.timestamps[anim_idx];
let values = &track.values[anim_idx];
if timestamps.is_empty() || values.is_empty() {
return default;
}
let Some(time_index) = find_timestamp_index(timestamps, effective_time) else {
return values.first().cloned().unwrap_or(default);
};
if time_index >= timestamps.len() - 1 {
return values.last().cloned().unwrap_or(default);
}
let time1 = timestamps[time_index] as f64;
let time2 = timestamps[time_index + 1] as f64;
let value1 = &values[time_index];
let value2 = &values[time_index + 1];
match track.interpolation_type {
0 => {
value1.clone()
}
1 => {
let t = if time2 > time1 {
((effective_time - time1) / (time2 - time1)) as f32
} else {
0.0
};
value1.lerp(value2, t.clamp(0.0, 1.0))
}
2 | 3 => {
let t = if time2 > time1 {
((effective_time - time1) / (time2 - time1)) as f32
} else {
0.0
};
value1.lerp(value2, t.clamp(0.0, 1.0))
}
_ => value1.clone(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn interpolate_with_blend<T: Lerp + Clone>(
track: &ResolvedTrack<T>,
current_anim: usize,
current_time: f64,
next_anim: Option<usize>,
next_time: f64,
blend_factor: f32,
global_times: &[f64],
default: T,
) -> T {
let current_value = interpolate_track(
track,
current_anim,
current_time,
global_times,
default.clone(),
);
if blend_factor >= 0.999 || next_anim.is_none() {
return current_value;
}
let next_anim_idx = next_anim.unwrap();
let next_value = interpolate_track(track, next_anim_idx, next_time, global_times, default);
current_value.lerp(&next_value, 1.0 - blend_factor)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::animation::types::Vec3;
#[test]
fn test_find_timestamp_index_empty() {
let timestamps: Vec<u32> = vec![];
assert_eq!(find_timestamp_index(×tamps, 0.0), None);
}
#[test]
fn test_find_timestamp_index_single() {
let timestamps = vec![100];
assert_eq!(find_timestamp_index(×tamps, 0.0), Some(0));
assert_eq!(find_timestamp_index(×tamps, 100.0), Some(0));
assert_eq!(find_timestamp_index(×tamps, 200.0), Some(0));
}
#[test]
fn test_find_timestamp_index_multiple() {
let timestamps = vec![0, 100, 200, 300];
assert_eq!(find_timestamp_index(×tamps, 0.0), Some(0));
assert_eq!(find_timestamp_index(×tamps, 50.0), Some(0));
assert_eq!(find_timestamp_index(×tamps, 150.0), Some(1));
assert_eq!(find_timestamp_index(×tamps, 250.0), Some(2));
assert_eq!(find_timestamp_index(×tamps, 100.0), Some(1));
assert_eq!(find_timestamp_index(×tamps, 200.0), Some(2));
assert_eq!(find_timestamp_index(×tamps, 400.0), Some(3));
}
#[test]
fn test_interpolate_linear() {
let track = ResolvedTrack {
interpolation_type: 1, global_sequence: -1,
timestamps: vec![vec![0, 100]],
values: vec![vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0)]],
};
let global_times = vec![];
let v = interpolate_track(&track, 0, 0.0, &global_times, Vec3::ZERO);
assert!((v.x - 0.0).abs() < 0.001);
let v = interpolate_track(&track, 0, 50.0, &global_times, Vec3::ZERO);
assert!((v.x - 5.0).abs() < 0.001);
let v = interpolate_track(&track, 0, 100.0, &global_times, Vec3::ZERO);
assert!((v.x - 10.0).abs() < 0.001);
}
#[test]
fn test_interpolate_step() {
let track = ResolvedTrack {
interpolation_type: 0, global_sequence: -1,
timestamps: vec![vec![0, 100]],
values: vec![vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0)]],
};
let global_times = vec![];
let v = interpolate_track(&track, 0, 50.0, &global_times, Vec3::ZERO);
assert!((v.x - 0.0).abs() < 0.001);
}
#[test]
fn test_interpolate_global_sequence() {
let track = ResolvedTrack {
interpolation_type: 1,
global_sequence: 0, timestamps: vec![vec![0, 100]],
values: vec![vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0)]],
};
let global_times = vec![50.0];
let v = interpolate_track(&track, 0, 0.0, &global_times, Vec3::ZERO);
assert!((v.x - 5.0).abs() < 0.001);
}
}