use crate::graphics::data::mamodel::{Model, ModelPart};
use crate::graphics::data::maanim::{Animation, AnimModification};
#[derive(Debug)]
pub enum TimelineError {
BufferMismatch,
}
pub fn animate(model: &Model, animation: &Animation, global_frame: f32, state_buffer: &mut [ModelPart]) -> Result<(), TimelineError> {
if state_buffer.len() != model.parts.len() {
return Err(TimelineError::BufferMismatch);
}
state_buffer.clone_from_slice(&model.parts);
for curve in &animation.curves {
if curve.part_id >= state_buffer.len() { continue; }
if curve.keyframes.is_empty() { continue; }
let keyframe_min = curve.keyframes.first().map(|k| k.frame as f32).unwrap_or(0.0);
let keyframe_max = curve.keyframes.last().map(|k| k.frame as f32).unwrap_or(0.0);
let duration = (keyframe_max - keyframe_min).max(1.0);
let mut local_frame = global_frame;
if curve.loop_count != 1 {
local_frame = (global_frame - keyframe_min).rem_euclid(duration) + keyframe_min;
}
let is_discrete = matches!(curve.modification_type, 0 | 1 | 3 | 13 | 14);
let Some(interpolated_value) = interpolate_curve(curve, local_frame, is_discrete) else {
continue;
};
let base_part = &model.parts[curve.part_id];
let part = &mut state_buffer[curve.part_id];
match curve.modification_type {
0 => part.parent_id = interpolated_value as i32,
1 => part.unit_id = interpolated_value as i32,
2 => part.sprite_index = interpolated_value as i32,
3 => part.drawing_layer = interpolated_value as i32,
4 => part.position_x = (base_part.position_x + interpolated_value).trunc(),
5 => part.position_y = (base_part.position_y + interpolated_value).trunc(),
6 => part.pivot_x = (base_part.pivot_x + interpolated_value).trunc(),
7 => part.pivot_y = (base_part.pivot_y + interpolated_value).trunc(),
8 => {
part.scale_x = (base_part.scale_x * interpolated_value / model.scale_unit).trunc();
part.scale_y = (base_part.scale_y * interpolated_value / model.scale_unit).trunc();
},
9 => part.scale_x = (base_part.scale_x * interpolated_value / model.scale_unit).trunc(),
10 => part.scale_y = (base_part.scale_y * interpolated_value / model.scale_unit).trunc(),
11 => part.rotation = (base_part.rotation + interpolated_value).trunc(),
12 => part.alpha = (base_part.alpha * interpolated_value / model.alpha_unit).trunc(),
13 => part.flip_x = interpolated_value != 0.0,
14 => part.flip_y = interpolated_value != 0.0,
_ => {}
}
}
Ok(())
}
fn interpolate_curve(curve: &AnimModification, frame: f32, is_discrete: bool) -> Option<f32> {
if curve.keyframes.is_empty() { return None; }
let first_keyframe = &curve.keyframes[0];
if frame < first_keyframe.frame as f32 {
return None;
}
let mut start_index = 0;
let mut end_index = 0;
let mut is_found = false;
for (index, keyframe) in curve.keyframes.iter().enumerate() {
if (keyframe.frame as f32) > frame {
end_index = index;
start_index = if index > 0 { index - 1 } else { 0 };
is_found = true;
break;
}
}
if !is_found {
let Some(last_keyframe) = curve.keyframes.last() else { return None; };
return Some((last_keyframe.value as f32).trunc());
}
if end_index == 0 {
return Some((curve.keyframes[0].value as f32).trunc());
}
let start_keyframe = &curve.keyframes[start_index];
let end_keyframe = &curve.keyframes[end_index];
if is_discrete { return Some((start_keyframe.value as f32).trunc()); }
if start_keyframe.frame == end_keyframe.frame { return Some((start_keyframe.value as f32).trunc()); }
if start_keyframe.ease_mode == 3 {
let mut points = Vec::new();
let mut backward_index = start_index as isize;
while backward_index >= 0 {
let current_keyframe = &curve.keyframes[backward_index as usize];
if (backward_index as usize) != start_index && current_keyframe.ease_mode != 3 { break; }
points.push((current_keyframe.frame as i64, current_keyframe.value as i64));
backward_index -= 1;
}
points.reverse();
let mut forward_index = end_index;
while forward_index < curve.keyframes.len() {
let current_keyframe = &curve.keyframes[forward_index];
points.push((current_keyframe.frame as i64, current_keyframe.value as i64));
if current_keyframe.ease_mode != 3 { break; }
forward_index += 1;
}
let mut final_result: i64 = 0;
let total_points = points.len();
let frame_int = frame.trunc() as i64;
for outer_index in 0..total_points {
let (frame_j, val_j) = points[outer_index];
let mut lagrange_term = val_j << 12;
for inner_index in 0..total_points {
if outer_index == inner_index { continue; }
let (frame_m, _) = points[inner_index];
if frame_j - frame_m != 0 {
lagrange_term = lagrange_term * (frame_int - frame_m) / (frame_j - frame_m);
}
}
final_result += lagrange_term;
}
return Some((final_result / 4096) as f32);
}
let time_duration = (end_keyframe.frame - start_keyframe.frame) as f32;
let time_current = frame.trunc() - (start_keyframe.frame as f32);
let progress = time_current / time_duration;
let start_value = start_keyframe.value as f32;
let value_change = (end_keyframe.value - start_keyframe.value) as f32;
let interpolated_value = match start_keyframe.ease_mode {
0 => start_value + (value_change * progress).trunc(),
1 => if progress >= 1.0 { end_keyframe.value as f32 } else { start_value },
2 => {
let ease_power = if start_keyframe.ease_power != 0 { start_keyframe.ease_power as f32 } else { 1.0 };
let progress_clamped = progress.clamp(0.0, 1.0);
let ease_factor = if ease_power >= 0.0 {
1.0 - (1.0 - progress_clamped.powf(ease_power)).sqrt()
} else {
(1.0 - (1.0 - progress_clamped).powf(-ease_power)).sqrt()
};
if ease_factor.is_nan() {
start_value + (value_change * progress).trunc()
} else {
start_value + (value_change * ease_factor).trunc()
}
},
_ => start_value + (value_change * progress).trunc()
};
if curve.modification_type == 2 {
if value_change < 0.0 {
return Some(interpolated_value.ceil());
} else {
return Some(interpolated_value.floor());
}
}
Some(interpolated_value.trunc())
}