wow_m2/chunks/
texture_animation.rs

1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Seek, Write};
3
4use crate::chunks::animation::{M2AnimationBlock, M2AnimationTrack};
5use crate::error::Result;
6use crate::version::M2Version;
7
8/// Texture animation type enum
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum M2TextureAnimationType {
11    /// No animation
12    None = 0,
13    /// Scroll animation
14    Scroll = 1,
15    /// Rotate animation
16    Rotate = 2,
17    /// Scale animation
18    Scale = 3,
19    /// Key frame animation
20    KeyFrame = 4,
21}
22
23impl M2TextureAnimationType {
24    /// Parse from integer value
25    pub fn from_u16(value: u16) -> Option<Self> {
26        match value {
27            0 => Some(Self::None),
28            1 => Some(Self::Scroll),
29            2 => Some(Self::Rotate),
30            3 => Some(Self::Scale),
31            4 => Some(Self::KeyFrame),
32            _ => None,
33        }
34    }
35}
36
37/// Texture animation structure
38#[derive(Debug, Clone)]
39pub struct M2TextureAnimation {
40    /// Animation type
41    pub animation_type: M2TextureAnimationType,
42    /// Animation for U coordinate
43    pub translation_u: M2AnimationBlock<f32>,
44    /// Animation for V coordinate
45    pub translation_v: M2AnimationBlock<f32>,
46    /// Rotation animation
47    pub rotation: M2AnimationBlock<f32>,
48    /// Scale U animation
49    pub scale_u: M2AnimationBlock<f32>,
50    /// Scale V animation
51    pub scale_v: M2AnimationBlock<f32>,
52}
53
54impl M2TextureAnimation {
55    /// Parse a texture animation from a reader
56    pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
57        let type_raw = reader.read_u16_le()?;
58        let animation_type =
59            M2TextureAnimationType::from_u16(type_raw).unwrap_or(M2TextureAnimationType::None);
60
61        // Skip 2 bytes of padding
62        reader.read_u16_le()?;
63
64        let translation_u = M2AnimationBlock::parse(reader)?;
65        let translation_v = M2AnimationBlock::parse(reader)?;
66        let rotation = M2AnimationBlock::parse(reader)?;
67        let scale_u = M2AnimationBlock::parse(reader)?;
68        let scale_v = M2AnimationBlock::parse(reader)?;
69
70        Ok(Self {
71            animation_type,
72            translation_u,
73            translation_v,
74            rotation,
75            scale_u,
76            scale_v,
77        })
78    }
79
80    /// Write a texture animation to a writer
81    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
82        writer.write_u16_le(self.animation_type as u16)?;
83
84        // Write 2 bytes of padding
85        writer.write_u16_le(0)?;
86
87        self.translation_u.write(writer)?;
88        self.translation_v.write(writer)?;
89        self.rotation.write(writer)?;
90        self.scale_u.write(writer)?;
91        self.scale_v.write(writer)?;
92
93        Ok(())
94    }
95
96    /// Convert this texture animation to a different version (no version differences yet)
97    pub fn convert(&self, _target_version: M2Version) -> Self {
98        self.clone()
99    }
100
101    /// Create a new texture animation with default values
102    pub fn new(animation_type: M2TextureAnimationType) -> Self {
103        Self {
104            animation_type,
105            translation_u: M2AnimationBlock::new(M2AnimationTrack::default()),
106            translation_v: M2AnimationBlock::new(M2AnimationTrack::default()),
107            rotation: M2AnimationBlock::new(M2AnimationTrack::default()),
108            scale_u: M2AnimationBlock::new(M2AnimationTrack::default()),
109            scale_v: M2AnimationBlock::new(M2AnimationTrack::default()),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use std::io::Cursor;
118
119    #[test]
120    fn test_texture_animation_parse_write() {
121        let mut data = Vec::new();
122
123        // Animation type (Scroll)
124        data.extend_from_slice(&1u16.to_le_bytes());
125
126        // Padding
127        data.extend_from_slice(&0u16.to_le_bytes());
128
129        // Translation U animation track
130        data.extend_from_slice(&1u16.to_le_bytes()); // Interpolation type (Linear)
131        data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
132        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
133        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
134        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
135        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
136        data.extend_from_slice(&0u32.to_le_bytes()); // Values count
137        data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
138
139        // Translation V animation track
140        data.extend_from_slice(&1u16.to_le_bytes()); // Interpolation type (Linear)
141        data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
142        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
143        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
144        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
145        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
146        data.extend_from_slice(&0u32.to_le_bytes()); // Values count
147        data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
148
149        // Rotation animation track
150        data.extend_from_slice(&1u16.to_le_bytes()); // Interpolation type (Linear)
151        data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
152        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
153        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
154        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
155        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
156        data.extend_from_slice(&0u32.to_le_bytes()); // Values count
157        data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
158
159        // Scale U animation track
160        data.extend_from_slice(&1u16.to_le_bytes()); // Interpolation type (Linear)
161        data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
162        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
163        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
164        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
165        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
166        data.extend_from_slice(&0u32.to_le_bytes()); // Values count
167        data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
168
169        // Scale V animation track
170        data.extend_from_slice(&1u16.to_le_bytes()); // Interpolation type (Linear)
171        data.extend_from_slice(&(-1i16).to_le_bytes()); // Global sequence
172        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges count
173        data.extend_from_slice(&0u32.to_le_bytes()); // Interpolation ranges offset
174        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps count
175        data.extend_from_slice(&0u32.to_le_bytes()); // Timestamps offset
176        data.extend_from_slice(&0u32.to_le_bytes()); // Values count
177        data.extend_from_slice(&0u32.to_le_bytes()); // Values offset
178
179        let mut cursor = Cursor::new(data);
180        let tex_anim = M2TextureAnimation::parse(&mut cursor).unwrap();
181
182        assert_eq!(tex_anim.animation_type, M2TextureAnimationType::Scroll);
183
184        // Test write
185        let mut output = Vec::new();
186        tex_anim.write(&mut output).unwrap();
187
188        // Check output size (should be the same as input)
189        assert_eq!(output.len(), cursor.get_ref().len());
190    }
191}