wow_m2/animation/
state.rs

1//! Animation state tracking for M2 models
2
3/// Represents the current state of an animation
4#[derive(Debug, Clone)]
5pub struct AnimationState {
6    /// Index of the current animation sequence
7    pub animation_index: Option<usize>,
8    /// Number of times to repeat the current animation
9    pub repeat_times: i32,
10    /// Current time within the animation (milliseconds)
11    pub animation_time: f64,
12    /// Index of the main variation (for animation loops)
13    pub main_variation_index: usize,
14}
15
16impl AnimationState {
17    /// Create a new animation state
18    pub fn new(animation_index: Option<usize>) -> Self {
19        Self {
20            animation_index,
21            repeat_times: 0,
22            animation_time: 0.0,
23            main_variation_index: animation_index.unwrap_or(0),
24        }
25    }
26
27    /// Create an empty/inactive animation state
28    pub fn none() -> Self {
29        Self {
30            animation_index: None,
31            repeat_times: 0,
32            animation_time: 0.0,
33            main_variation_index: 0,
34        }
35    }
36
37    /// Check if this state has an active animation
38    pub fn is_active(&self) -> bool {
39        self.animation_index.is_some()
40    }
41
42    /// Reset the animation time to the beginning
43    pub fn reset_time(&mut self) {
44        self.animation_time = 0.0;
45    }
46}
47
48impl Default for AnimationState {
49    fn default() -> Self {
50        Self::none()
51    }
52}
53
54/// Simple linear congruential generator for deterministic randomness
55/// Used for animation variation selection (matches noclip behavior)
56#[derive(Debug, Clone)]
57pub struct LcgRng {
58    state: u32,
59}
60
61impl LcgRng {
62    /// Create a new RNG with the given seed
63    pub fn new(seed: u32) -> Self {
64        Self { state: seed }
65    }
66
67    /// Generate next random u16
68    pub fn next_u16(&mut self) -> u16 {
69        self.state = self.state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
70        self.state %= 1 << 31;
71        self.state as u16
72    }
73
74    /// Generate next random f32 in [0.0, 1.0)
75    pub fn next_f32(&mut self) -> f32 {
76        self.next_u16() as f32 / u16::MAX as f32
77    }
78}
79
80impl Default for LcgRng {
81    fn default() -> Self {
82        Self::new(1312) // Default seed
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_animation_state_new() {
92        let state = AnimationState::new(Some(5));
93        assert_eq!(state.animation_index, Some(5));
94        assert_eq!(state.repeat_times, 0);
95        assert_eq!(state.animation_time, 0.0);
96        assert_eq!(state.main_variation_index, 5);
97        assert!(state.is_active());
98    }
99
100    #[test]
101    fn test_animation_state_none() {
102        let state = AnimationState::none();
103        assert_eq!(state.animation_index, None);
104        assert!(!state.is_active());
105    }
106
107    #[test]
108    fn test_lcg_rng_deterministic() {
109        let mut rng1 = LcgRng::new(42);
110        let mut rng2 = LcgRng::new(42);
111
112        for _ in 0..10 {
113            assert_eq!(rng1.next_u16(), rng2.next_u16());
114        }
115    }
116
117    #[test]
118    fn test_lcg_rng_range() {
119        let mut rng = LcgRng::new(42);
120        for _ in 0..100 {
121            let f = rng.next_f32();
122            assert!((0.0..1.0).contains(&f));
123        }
124    }
125}