use super::interpolation::interpolate_with_blend;
use super::state::{AnimationState, LcgRng};
use super::types::{Lerp, Quat, ResolvedTrack, Vec3};
#[derive(Debug, Clone)]
pub struct AnimSequence {
pub id: u16,
pub sub_id: u16,
pub duration: u32,
pub movement_speed: f32,
pub flags: u32,
pub frequency: u16,
pub replay_min: u32,
pub replay_max: u32,
pub blend_time: u32,
pub variation_next: i16,
pub alias_next: u16,
}
impl AnimSequence {
pub fn calculate_repeats(&self, rng: &mut LcgRng) -> i32 {
if self.replay_max <= self.replay_min {
return self.replay_min as i32;
}
let range = (self.replay_max - self.replay_min) as f32;
self.replay_min as i32 + (range * rng.next_f32()) as i32
}
pub fn is_alias(&self) -> bool {
(self.flags & 0x40) != 0 && (self.flags & 0x20) == 0
}
}
#[derive(Debug, Clone)]
pub struct ResolvedBone {
pub bone_id: i32,
pub flags: u32,
pub parent_bone: i16,
pub translation: ResolvedTrack<Vec3>,
pub rotation: ResolvedTrack<Quat>,
pub scale: ResolvedTrack<Vec3>,
pub pivot: Vec3,
}
#[derive(Debug, Clone)]
pub struct AnimationManager {
global_sequence_durations: Vec<u32>,
global_sequence_times: Vec<f64>,
sequences: Vec<AnimSequence>,
bones: Vec<ResolvedBone>,
current_animation: AnimationState,
next_animation: AnimationState,
blend_factor: f32,
rng: LcgRng,
}
impl AnimationManager {
pub fn new(
global_sequence_durations: Vec<u32>,
sequences: Vec<AnimSequence>,
bones: Vec<ResolvedBone>,
) -> Self {
let global_sequence_times = vec![0.0; global_sequence_durations.len()];
let stand_index = sequences.iter().position(|s| s.id == 0);
let mut rng = LcgRng::default();
let mut current_animation = AnimationState::new(stand_index);
if let Some(idx) = stand_index {
current_animation.repeat_times = sequences[idx].calculate_repeats(&mut rng);
}
Self {
global_sequence_durations,
global_sequence_times,
sequences,
bones,
current_animation,
next_animation: AnimationState::none(),
blend_factor: 1.0,
rng,
}
}
pub fn empty() -> Self {
Self {
global_sequence_durations: Vec::new(),
global_sequence_times: Vec::new(),
sequences: Vec::new(),
bones: Vec::new(),
current_animation: AnimationState::none(),
next_animation: AnimationState::none(),
blend_factor: 1.0,
rng: LcgRng::default(),
}
}
pub fn update(&mut self, delta_time_ms: f64) {
self.current_animation.animation_time += delta_time_ms;
for (i, time) in self.global_sequence_times.iter_mut().enumerate() {
*time += delta_time_ms;
if self.global_sequence_durations[i] > 0 {
*time %= self.global_sequence_durations[i] as f64;
}
}
self.update_animation_transitions();
}
fn update_animation_transitions(&mut self) {
let Some(current_idx) = self.current_animation.animation_index else {
return;
};
if current_idx >= self.sequences.len() {
return;
}
let main_variation = &self.sequences[self.current_animation.main_variation_index];
if self.next_animation.animation_index.is_none()
&& main_variation.variation_next > -1
&& self.current_animation.repeat_times <= 0
{
self.select_next_variation();
} else if self.current_animation.repeat_times > 0 {
self.next_animation = self.current_animation.clone();
self.next_animation.repeat_times -= 1;
}
let current_seq = &self.sequences[current_idx];
let time_left = current_seq.duration as f64 - self.current_animation.animation_time;
if let Some(next_idx) = self.next_animation.animation_index {
let next_seq = &self.sequences[next_idx];
let blend_time = next_seq.blend_time as f64;
if blend_time > 0.0 && time_left < blend_time {
self.next_animation.animation_time =
(blend_time - time_left) % next_seq.duration as f64;
self.blend_factor = (time_left / blend_time) as f32;
} else {
self.blend_factor = 1.0;
}
}
if self.current_animation.animation_time >= current_seq.duration as f64 {
self.current_animation.repeat_times -= 1;
if let Some(next_idx) = self.next_animation.animation_index {
let resolved_idx = self.resolve_alias(next_idx);
self.next_animation.animation_index = Some(resolved_idx);
self.current_animation = self.next_animation.clone();
self.next_animation = AnimationState::none();
self.blend_factor = 1.0;
} else if current_seq.duration > 0 {
self.current_animation.animation_time %= current_seq.duration as f64;
}
}
}
fn select_next_variation(&mut self) {
let main_idx = self.current_animation.main_variation_index;
let probability = (self.rng.next_f32() * 0x7fff as f32) as u16;
let mut calc_prob = 0u16;
let mut next_index = main_idx;
loop {
let seq = &self.sequences[next_index];
calc_prob = calc_prob.saturating_add(seq.frequency);
if calc_prob >= probability || seq.variation_next < 0 {
break;
}
let potential_next = seq.variation_next as usize;
if potential_next >= self.sequences.len() {
break;
}
if Some(potential_next) != self.current_animation.animation_index {
next_index = potential_next;
} else if seq.variation_next >= 0 {
next_index = seq.variation_next as usize;
}
}
self.next_animation.animation_index = Some(next_index);
self.next_animation.animation_time = 0.0;
self.next_animation.main_variation_index = main_idx;
self.next_animation.repeat_times =
self.sequences[next_index].calculate_repeats(&mut self.rng);
}
fn resolve_alias(&self, index: usize) -> usize {
let mut current = index;
let mut iterations = 0;
while iterations < 100 {
if current >= self.sequences.len() {
return index;
}
let seq = &self.sequences[current];
if !seq.is_alias() {
return current;
}
current = seq.alias_next as usize;
iterations += 1;
}
index
}
pub fn set_animation_id(&mut self, id: u16) {
let index = self.sequences.iter().position(|s| s.id == id);
if let Some(idx) = index {
self.current_animation = AnimationState::new(Some(idx));
self.current_animation.repeat_times =
self.sequences[idx].calculate_repeats(&mut self.rng);
self.next_animation = AnimationState::none();
self.blend_factor = 1.0;
}
}
pub fn set_animation_index(&mut self, index: usize) {
if index < self.sequences.len() {
self.current_animation = AnimationState::new(Some(index));
self.current_animation.repeat_times =
self.sequences[index].calculate_repeats(&mut self.rng);
self.next_animation = AnimationState::none();
self.blend_factor = 1.0;
}
}
pub fn get_animation_ids(&self) -> Vec<u16> {
self.sequences.iter().map(|s| s.id).collect()
}
pub fn current_time(&self) -> f64 {
self.current_animation.animation_time
}
pub fn current_animation_index(&self) -> Option<usize> {
self.current_animation.animation_index
}
pub fn bone_count(&self) -> usize {
self.bones.len()
}
pub fn sequence_count(&self) -> usize {
self.sequences.len()
}
pub fn get_current_value<T: Lerp + Clone + Default>(&self, track: &ResolvedTrack<T>) -> T {
self.get_current_value_with_default(track, T::default())
}
pub fn get_current_value_with_default<T: Lerp + Clone>(
&self,
track: &ResolvedTrack<T>,
default: T,
) -> T {
let Some(current_idx) = self.current_animation.animation_index else {
return default;
};
interpolate_with_blend(
track,
current_idx,
self.current_animation.animation_time,
self.next_animation.animation_index,
self.next_animation.animation_time,
self.blend_factor,
&self.global_sequence_times,
default,
)
}
pub fn get_bone_translation(&self, bone_index: usize) -> Vec3 {
if bone_index >= self.bones.len() {
return Vec3::ZERO;
}
self.get_current_value_with_default(&self.bones[bone_index].translation, Vec3::ZERO)
}
pub fn get_bone_rotation(&self, bone_index: usize) -> Quat {
if bone_index >= self.bones.len() {
return Quat::IDENTITY;
}
self.get_current_value_with_default(&self.bones[bone_index].rotation, Quat::IDENTITY)
}
pub fn get_bone_scale(&self, bone_index: usize) -> Vec3 {
if bone_index >= self.bones.len() {
return Vec3::ONE;
}
self.get_current_value_with_default(&self.bones[bone_index].scale, Vec3::ONE)
}
pub fn bones(&self) -> &[ResolvedBone] {
&self.bones
}
pub fn sequences(&self) -> &[AnimSequence] {
&self.sequences
}
pub fn global_times(&self) -> &[f64] {
&self.global_sequence_times
}
pub fn blend_factor(&self) -> f32 {
self.blend_factor
}
}
pub struct AnimationManagerBuilder;
impl AnimationManagerBuilder {
pub fn from_model(model: &crate::M2Model, data: &[u8]) -> crate::Result<AnimationManager> {
use std::io::Cursor;
let global_sequence_durations: Vec<u32> = model.global_sequences.to_vec();
let sequences: Vec<AnimSequence> = model
.animations
.iter()
.map(|seq| {
let duration = seq
.end_timestamp
.map(|end| end.saturating_sub(seq.start_timestamp))
.unwrap_or(seq.start_timestamp);
let (replay_min, replay_max) = seq
.replay
.map(|r| (r.minimum as u32, r.maximum as u32))
.unwrap_or((0, 0));
AnimSequence {
id: seq.animation_id,
sub_id: seq.sub_animation_id,
duration,
movement_speed: seq.movement_speed,
flags: seq.flags,
frequency: seq.frequency as u16,
replay_min,
replay_max,
blend_time: 150, variation_next: seq.next_animation.unwrap_or(-1),
alias_next: seq.aliasing.unwrap_or(0),
}
})
.collect();
let num_sequences = sequences.len();
let mut cursor = Cursor::new(data);
let mut bones = Vec::with_capacity(model.bones.len());
for bone in &model.bones {
let translation =
Self::resolve_vec3_track(&bone.translation, &mut cursor, num_sequences)?;
let rotation = Self::resolve_quat_track(&bone.rotation, &mut cursor, num_sequences)?;
let scale = Self::resolve_vec3_track(&bone.scale, &mut cursor, num_sequences)?;
bones.push(ResolvedBone {
bone_id: bone.bone_id,
flags: bone.flags.bits(),
parent_bone: bone.parent_bone,
translation,
rotation,
scale,
pivot: Vec3::new(bone.pivot.x, bone.pivot.y, bone.pivot.z),
});
}
Ok(AnimationManager::new(
global_sequence_durations,
sequences,
bones,
))
}
fn resolve_vec3_track<R: std::io::Read + std::io::Seek>(
track: &crate::chunks::m2_track::M2TrackVec3,
reader: &mut R,
num_sequences: usize,
) -> crate::Result<ResolvedTrack<Vec3>> {
use crate::chunks::m2_track_resolver::M2TrackVec3Ext;
if !track.has_data() {
return Ok(ResolvedTrack::empty());
}
let (timestamps_flat, values_flat, ranges) = track.resolve_data(reader)?;
let values_vec3: Vec<Vec3> = values_flat
.into_iter()
.map(|v| Vec3::new(v.x, v.y, v.z))
.collect();
let global_sequence = if track.base.global_sequence == 0xFFFF {
-1i16
} else {
track.base.global_sequence as i16
};
if global_sequence >= 0 {
return Ok(ResolvedTrack {
interpolation_type: track.base.interpolation_type as u16,
global_sequence,
timestamps: vec![timestamps_flat],
values: vec![values_vec3],
});
}
let (timestamps, values) = Self::split_by_ranges(
timestamps_flat,
values_vec3,
ranges.as_deref(),
num_sequences,
);
Ok(ResolvedTrack {
interpolation_type: track.base.interpolation_type as u16,
global_sequence,
timestamps,
values,
})
}
fn resolve_quat_track<R: std::io::Read + std::io::Seek>(
track: &crate::chunks::m2_track::M2TrackQuat,
reader: &mut R,
num_sequences: usize,
) -> crate::Result<ResolvedTrack<Quat>> {
use crate::chunks::m2_track_resolver::M2TrackQuatExt;
if !track.has_data() {
return Ok(ResolvedTrack::empty());
}
let (timestamps_flat, values_flat, ranges) = track.resolve_data(reader)?;
let values_quat: Vec<Quat> = values_flat
.into_iter()
.map(|q| {
let (x, y, z, w) = q.to_float_quaternion();
Quat::new(x, y, z, w).normalize()
})
.collect();
let global_sequence = if track.base.global_sequence == 0xFFFF {
-1i16
} else {
track.base.global_sequence as i16
};
if global_sequence >= 0 {
return Ok(ResolvedTrack {
interpolation_type: track.base.interpolation_type as u16,
global_sequence,
timestamps: vec![timestamps_flat],
values: vec![values_quat],
});
}
let (timestamps, values) = Self::split_by_ranges(
timestamps_flat,
values_quat,
ranges.as_deref(),
num_sequences,
);
Ok(ResolvedTrack {
interpolation_type: track.base.interpolation_type as u16,
global_sequence,
timestamps,
values,
})
}
fn split_by_ranges<T: Clone>(
timestamps_flat: Vec<u32>,
values_flat: Vec<T>,
ranges: Option<&[u32]>,
num_sequences: usize,
) -> (Vec<Vec<u32>>, Vec<Vec<T>>) {
if let Some(ranges) = ranges {
let mut timestamps = Vec::with_capacity(num_sequences);
let mut values = Vec::with_capacity(num_sequences);
for i in 0..num_sequences {
let range_idx = i * 2;
if range_idx + 1 < ranges.len() {
let start = ranges[range_idx] as usize;
let end = ranges[range_idx + 1] as usize;
if start <= end && end <= timestamps_flat.len() && end <= values_flat.len() {
timestamps.push(timestamps_flat[start..end].to_vec());
values.push(values_flat[start..end].to_vec());
} else {
timestamps.push(Vec::new());
values.push(Vec::new());
}
} else {
timestamps.push(Vec::new());
values.push(Vec::new());
}
}
(timestamps, values)
} else {
let mut timestamps = Vec::with_capacity(num_sequences);
let mut values = Vec::with_capacity(num_sequences);
if !timestamps_flat.is_empty() {
timestamps.push(timestamps_flat);
values.push(values_flat);
}
while timestamps.len() < num_sequences {
timestamps.push(Vec::new());
values.push(Vec::new());
}
(timestamps, values)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_sequence(id: u16, duration: u32) -> AnimSequence {
AnimSequence {
id,
sub_id: 0,
duration,
movement_speed: 0.0,
flags: 0,
frequency: 0x7fff,
replay_min: 0,
replay_max: 0,
blend_time: 0,
variation_next: -1,
alias_next: 0,
}
}
#[test]
fn test_animation_manager_empty() {
let manager = AnimationManager::empty();
assert_eq!(manager.bone_count(), 0);
assert_eq!(manager.sequence_count(), 0);
assert_eq!(manager.current_animation_index(), None);
}
#[test]
fn test_animation_manager_basic() {
let sequences = vec![
create_test_sequence(0, 1000), create_test_sequence(4, 500), ];
let manager = AnimationManager::new(vec![], sequences, vec![]);
assert_eq!(manager.current_animation_index(), Some(0));
assert_eq!(manager.sequence_count(), 2);
}
#[test]
fn test_animation_update() {
let sequences = vec![create_test_sequence(0, 1000)];
let mut manager = AnimationManager::new(vec![], sequences, vec![]);
manager.update(500.0);
assert!((manager.current_time() - 500.0).abs() < 0.001);
manager.update(600.0);
assert!(manager.current_time() < 1000.0);
}
#[test]
fn test_set_animation() {
let sequences = vec![create_test_sequence(0, 1000), create_test_sequence(4, 500)];
let mut manager = AnimationManager::new(vec![], sequences, vec![]);
manager.set_animation_id(4);
assert_eq!(manager.current_animation_index(), Some(1));
assert!((manager.current_time() - 0.0).abs() < 0.001);
}
#[test]
fn test_global_sequences() {
let sequences = vec![create_test_sequence(0, 1000)];
let global_durations = vec![500, 1000];
let mut manager = AnimationManager::new(global_durations, sequences, vec![]);
manager.update(250.0);
let times = manager.global_times();
assert!((times[0] - 250.0).abs() < 0.001);
assert!((times[1] - 250.0).abs() < 0.001);
manager.update(300.0); let times = manager.global_times();
assert!((times[0] - 50.0).abs() < 0.001); assert!((times[1] - 550.0).abs() < 0.001); }
#[test]
fn test_bone_interpolation() {
let bone = ResolvedBone {
bone_id: 0,
flags: 0,
parent_bone: -1,
translation: ResolvedTrack {
interpolation_type: 1, global_sequence: -1,
timestamps: vec![vec![0, 100]],
values: vec![vec![Vec3::ZERO, Vec3::new(10.0, 0.0, 0.0)]],
},
rotation: ResolvedTrack::empty(),
scale: ResolvedTrack::empty(),
pivot: Vec3::ZERO,
};
let sequences = vec![create_test_sequence(0, 1000)];
let mut manager = AnimationManager::new(vec![], sequences, vec![bone]);
let trans = manager.get_bone_translation(0);
assert!((trans.x - 0.0).abs() < 0.001);
manager.update(50.0);
let trans = manager.get_bone_translation(0);
assert!((trans.x - 5.0).abs() < 0.001);
manager.update(50.0);
let trans = manager.get_bone_translation(0);
assert!((trans.x - 10.0).abs() < 0.001);
}
}