Skip to main content

wow_m2/chunks/
m2_track_resolver.rs

1use crate::{
2    Result,
3    chunks::m2_track::{M2CompQuat, M2Track, M2TrackQuat, M2TrackVec3},
4    common::{C3Vector, read_array},
5    io_ext::ReadExt,
6};
7use std::io::{Read, Seek};
8
9/// Resolver for M2Track animation data
10/// This loads the actual keyframe data from M2Array offsets
11pub struct M2TrackResolver;
12
13impl M2TrackResolver {
14    /// Resolve timestamp data for a track
15    pub fn resolve_timestamps<R: Read + Seek>(
16        reader: &mut R,
17        track: &M2Track<impl Clone>,
18    ) -> Result<Vec<u32>> {
19        if track.timestamps.is_empty() {
20            return Ok(Vec::new());
21        }
22
23        read_array(reader, &track.timestamps, |r| Ok(r.read_u32_le()?))
24    }
25
26    /// Resolve Vec3 track values (translation/scale)
27    pub fn resolve_vec3_values<R: Read + Seek>(
28        reader: &mut R,
29        track: &M2TrackVec3,
30    ) -> Result<Vec<C3Vector>> {
31        if track.values.is_empty() {
32            return Ok(Vec::new());
33        }
34
35        read_array(reader, &track.values, |r| C3Vector::parse(r))
36    }
37
38    /// Resolve quaternion track values (rotation)
39    pub fn resolve_quat_values<R: Read + Seek>(
40        reader: &mut R,
41        track: &M2TrackQuat,
42    ) -> Result<Vec<M2CompQuat>> {
43        if track.values.is_empty() {
44            return Ok(Vec::new());
45        }
46
47        read_array(reader, &track.values, |r| M2CompQuat::parse(r))
48    }
49
50    /// Resolve range data for pre-WotLK tracks
51    /// Ranges are stored as pairs of u32 values (start, end)
52    pub fn resolve_ranges<R: Read + Seek>(
53        reader: &mut R,
54        track: &M2Track<impl Clone>,
55    ) -> Result<Option<Vec<u32>>> {
56        if let Some(ref ranges) = track.ranges {
57            if ranges.is_empty() {
58                return Ok(Some(Vec::new()));
59            }
60
61            // Read ranges as raw u32 values (pairs of start/end)
62            let range_data = read_array(reader, ranges, |r| Ok(r.read_u32_le()?))?;
63
64            Ok(Some(range_data))
65        } else {
66            Ok(None)
67        }
68    }
69}
70
71// Type aliases to reduce complexity
72type Vec3TrackData = (Vec<u32>, Vec<C3Vector>, Option<Vec<u32>>);
73type QuatTrackData = (Vec<u32>, Vec<M2CompQuat>, Option<Vec<u32>>);
74
75/// Extension trait for M2TrackVec3 to resolve animation data
76pub trait M2TrackVec3Ext {
77    /// Load the actual animation data from the file
78    fn resolve_data<R: Read + Seek>(&self, reader: &mut R) -> Result<Vec3TrackData>;
79}
80
81impl M2TrackVec3Ext for M2TrackVec3 {
82    fn resolve_data<R: Read + Seek>(&self, reader: &mut R) -> Result<Vec3TrackData> {
83        let timestamps = M2TrackResolver::resolve_timestamps(reader, self)?;
84        let values = M2TrackResolver::resolve_vec3_values(reader, self)?;
85        let ranges = M2TrackResolver::resolve_ranges(reader, self)?;
86        Ok((timestamps, values, ranges))
87    }
88}
89
90/// Extension trait for M2TrackQuat to resolve animation data  
91pub trait M2TrackQuatExt {
92    /// Load the actual animation data from the file
93    fn resolve_data<R: Read + Seek>(&self, reader: &mut R) -> Result<QuatTrackData>;
94}
95
96impl M2TrackQuatExt for M2TrackQuat {
97    fn resolve_data<R: Read + Seek>(&self, reader: &mut R) -> Result<QuatTrackData> {
98        let timestamps = M2TrackResolver::resolve_timestamps(reader, self)?;
99        let values = M2TrackResolver::resolve_quat_values(reader, self)?;
100        let ranges = M2TrackResolver::resolve_ranges(reader, self)?;
101        Ok((timestamps, values, ranges))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::chunks::m2_track::M2TrackBase;
109    use crate::common::M2Array;
110    use std::io::Cursor;
111
112    #[test]
113    fn test_resolve_empty_track() {
114        let track = M2TrackVec3 {
115            base: M2TrackBase {
116                interpolation_type: crate::chunks::animation::M2InterpolationType::None,
117                global_sequence: 0xFFFF,
118            },
119            ranges: None,
120            timestamps: M2Array::new(0, 0),
121            values: M2Array::new(0, 0),
122        };
123
124        let data = vec![0u8; 100];
125        let mut cursor = Cursor::new(data);
126
127        let (timestamps, values, ranges) = track.resolve_data(&mut cursor).unwrap();
128        assert!(timestamps.is_empty());
129        assert!(values.is_empty());
130        assert_eq!(ranges, None);
131    }
132
133    #[test]
134    fn test_resolve_track_with_data() {
135        // Create test data with timestamps and values
136        let mut data = vec![0u8; 1000];
137
138        // Write timestamps at offset 100 (3 timestamps)
139        let timestamp_offset = 100;
140        data[timestamp_offset..timestamp_offset + 4].copy_from_slice(&10u32.to_le_bytes());
141        data[timestamp_offset + 4..timestamp_offset + 8].copy_from_slice(&20u32.to_le_bytes());
142        data[timestamp_offset + 8..timestamp_offset + 12].copy_from_slice(&30u32.to_le_bytes());
143
144        // Write Vec3 values at offset 200 (3 vectors)
145        let value_offset = 200;
146        data[value_offset..value_offset + 4].copy_from_slice(&1.0f32.to_le_bytes());
147        data[value_offset + 4..value_offset + 8].copy_from_slice(&2.0f32.to_le_bytes());
148        data[value_offset + 8..value_offset + 12].copy_from_slice(&3.0f32.to_le_bytes());
149
150        data[value_offset + 12..value_offset + 16].copy_from_slice(&4.0f32.to_le_bytes());
151        data[value_offset + 16..value_offset + 20].copy_from_slice(&5.0f32.to_le_bytes());
152        data[value_offset + 20..value_offset + 24].copy_from_slice(&6.0f32.to_le_bytes());
153
154        data[value_offset + 24..value_offset + 28].copy_from_slice(&7.0f32.to_le_bytes());
155        data[value_offset + 28..value_offset + 32].copy_from_slice(&8.0f32.to_le_bytes());
156        data[value_offset + 32..value_offset + 36].copy_from_slice(&9.0f32.to_le_bytes());
157
158        let track = M2TrackVec3 {
159            base: M2TrackBase {
160                interpolation_type: crate::chunks::animation::M2InterpolationType::None,
161                global_sequence: 0xFFFF,
162            },
163            ranges: None,
164            timestamps: M2Array::new(3, timestamp_offset as u32),
165            values: M2Array::new(3, value_offset as u32),
166        };
167
168        let mut cursor = Cursor::new(data);
169        let (timestamps, values, _ranges) = track.resolve_data(&mut cursor).unwrap();
170
171        assert_eq!(timestamps, vec![10, 20, 30]);
172        assert_eq!(values.len(), 3);
173        assert_eq!(
174            values[0],
175            C3Vector {
176                x: 1.0,
177                y: 2.0,
178                z: 3.0
179            }
180        );
181        assert_eq!(
182            values[1],
183            C3Vector {
184                x: 4.0,
185                y: 5.0,
186                z: 6.0
187            }
188        );
189        assert_eq!(
190            values[2],
191            C3Vector {
192                x: 7.0,
193                y: 8.0,
194                z: 9.0
195            }
196        );
197    }
198}