wow_m2/
common.rs

1use crate::error::{M2Error, Result};
2use crate::io_ext::{ReadExt, WriteExt};
3use std::io::{Read, Seek, SeekFrom, Write};
4
5/// A reference to an array in the M2 file format
6#[derive(Debug, Clone, Copy, Default, PartialEq)]
7pub struct M2Array<T> {
8    /// Number of elements in the array
9    pub count: u32,
10    /// Offset from the start of the file to the array
11    pub offset: u32,
12    /// Phantom data to associate with the type T
13    _phantom: std::marker::PhantomData<T>,
14}
15
16impl<T> M2Array<T> {
17    /// Create a new array reference
18    pub fn new(count: u32, offset: u32) -> Self {
19        Self {
20            count,
21            offset,
22            _phantom: std::marker::PhantomData,
23        }
24    }
25
26    /// Parse an array reference from a reader
27    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
28        let count = reader.read_u32_le()?;
29        let offset = reader.read_u32_le()?;
30
31        Ok(Self {
32            count,
33            offset,
34            _phantom: std::marker::PhantomData,
35        })
36    }
37
38    /// Write an array reference to a writer
39    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
40        writer.write_u32_le(self.count)?;
41        writer.write_u32_le(self.offset)?;
42
43        Ok(())
44    }
45
46    /// Check if the array is empty
47    pub fn is_empty(&self) -> bool {
48        self.count == 0
49    }
50
51    /// Convert this reference to a reference of another type
52    pub fn convert<U>(&self) -> M2Array<U> {
53        M2Array {
54            count: self.count,
55            offset: self.offset,
56            _phantom: std::marker::PhantomData,
57        }
58    }
59}
60
61/// Reads data at an array reference location
62pub fn read_array<T, R, F>(reader: &mut R, array: &M2Array<T>, parse_fn: F) -> Result<Vec<T>>
63where
64    R: Read + Seek,
65    F: Fn(&mut R) -> Result<T>,
66{
67    if array.is_empty() {
68        return Ok(Vec::new());
69    }
70
71    // Seek to the array data
72    reader
73        .seek(std::io::SeekFrom::Start(array.offset as u64))
74        .map_err(M2Error::Io)?;
75
76    // Read each element
77    let mut result = Vec::with_capacity(array.count as usize);
78    for _ in 0..array.count {
79        result.push(parse_fn(reader)?);
80    }
81
82    Ok(result)
83}
84
85/// A vector in 3D space
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct C3Vector {
88    pub x: f32,
89    pub y: f32,
90    pub z: f32,
91}
92
93impl C3Vector {
94    /// Parse a C3Vector from a reader
95    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
96        let x = reader.read_f32_le()?;
97        let y = reader.read_f32_le()?;
98        let z = reader.read_f32_le()?;
99
100        Ok(Self { x, y, z })
101    }
102
103    /// Write a C3Vector to a writer
104    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
105        writer.write_f32_le(self.x)?;
106        writer.write_f32_le(self.y)?;
107        writer.write_f32_le(self.z)?;
108
109        Ok(())
110    }
111
112    /// Convert to a glam vector for easier math operations
113    pub fn to_glam(&self) -> glam::Vec3 {
114        glam::Vec3::new(self.x, self.y, self.z)
115    }
116
117    /// Create from a glam vector
118    pub fn from_glam(v: glam::Vec3) -> Self {
119        Self {
120            x: v.x,
121            y: v.y,
122            z: v.z,
123        }
124    }
125}
126
127/// A vector in 2D space
128#[derive(Debug, Clone, Copy, PartialEq)]
129pub struct C2Vector {
130    pub x: f32,
131    pub y: f32,
132}
133
134impl C2Vector {
135    /// Parse a C2Vector from a reader
136    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
137        let x = reader.read_f32_le()?;
138        let y = reader.read_f32_le()?;
139
140        Ok(Self { x, y })
141    }
142
143    /// Write a C2Vector to a writer
144    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
145        writer.write_f32_le(self.x)?;
146        writer.write_f32_le(self.y)?;
147
148        Ok(())
149    }
150
151    /// Convert to a glam vector for easier math operations
152    pub fn to_glam(&self) -> glam::Vec2 {
153        glam::Vec2::new(self.x, self.y)
154    }
155
156    /// Create from a glam vector
157    pub fn from_glam(v: glam::Vec2) -> Self {
158        Self { x: v.x, y: v.y }
159    }
160}
161
162/// A fixed-width string with a specified maximum length
163#[derive(Debug, Clone, PartialEq, Default)]
164pub struct FixedString {
165    pub data: Vec<u8>,
166}
167
168impl FixedString {
169    pub fn len(&self) -> usize {
170        self.data.len()
171    }
172
173    /// Check if the string is empty
174    pub fn is_empty(&self) -> bool {
175        self.data.is_empty()
176    }
177
178    /// Parse a fixed-width string from a reader
179    pub fn parse<R: Read + Seek>(reader: &mut R, len: usize) -> Result<Self> {
180        let mut data = vec![0u8; len];
181        reader.read_exact(&mut data)?;
182
183        // Find null terminator
184        let null_pos = data.iter().position(|&b| b == 0).unwrap_or(len);
185        data.truncate(null_pos);
186
187        Ok(Self { data })
188    }
189
190    /// Write a fixed-width string to a writer
191    pub fn write<W: Write>(&self, writer: &mut W, len: usize) -> Result<()> {
192        let mut data = self.data.clone();
193        data.resize(len, 0);
194        writer.write_all(&data)?;
195
196        Ok(())
197    }
198
199    /// Convert to a string, lossy UTF-8 conversion
200    pub fn to_string_lossy(&self) -> String {
201        String::from_utf8_lossy(&self.data).to_string()
202    }
203}
204
205#[derive(Debug, Clone, PartialEq, Default)]
206pub struct M2ArrayString {
207    pub string: FixedString,
208    pub array: M2Array<u8>,
209}
210
211impl M2ArrayString {
212    pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self> {
213        let array = M2Array::<u8>::parse(reader)?;
214        let current_pos = reader.stream_position()?;
215        reader.seek(SeekFrom::Start(array.offset as u64))?;
216        let string = FixedString::parse(reader, array.count as usize)?;
217        reader.seek(SeekFrom::Start(current_pos))?;
218        Ok(Self { string, array })
219    }
220
221    pub fn is_empty(&self) -> bool {
222        self.array.is_empty()
223    }
224
225    /// Write a reference to our array to a writer
226    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
227        writer.write_u32_le(self.array.count)?;
228        writer.write_u32_le(self.array.offset)?;
229
230        Ok(())
231    }
232}
233
234/// A quaternion for rotations
235#[derive(Debug, Clone, Copy, PartialEq)]
236pub struct Quaternion {
237    pub x: f32,
238    pub y: f32,
239    pub z: f32,
240    pub w: f32,
241}
242
243impl Quaternion {
244    /// Parse a quaternion from a reader
245    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
246        let x = reader.read_f32_le()?;
247        let y = reader.read_f32_le()?;
248        let z = reader.read_f32_le()?;
249        let w = reader.read_f32_le()?;
250
251        Ok(Self { x, y, z, w })
252    }
253
254    /// Write a quaternion to a writer
255    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
256        writer.write_f32_le(self.x)?;
257        writer.write_f32_le(self.y)?;
258        writer.write_f32_le(self.z)?;
259        writer.write_f32_le(self.w)?;
260
261        Ok(())
262    }
263
264    /// Convert to a glam quaternion for easier math operations
265    pub fn to_glam(&self) -> glam::Quat {
266        glam::Quat::from_xyzw(self.x, self.y, self.z, self.w)
267    }
268
269    /// Create from a glam quaternion
270    pub fn from_glam(q: glam::Quat) -> Self {
271        Self {
272            x: q.x,
273            y: q.y,
274            z: q.z,
275            w: q.w,
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use std::io::Cursor;
284
285    #[test]
286    fn test_m2array_parse() {
287        let data = [
288            0x05, 0x00, 0x00, 0x00, // count = 5
289            0x20, 0x30, 0x00, 0x00, // offset = 0x3020
290        ];
291
292        let mut cursor = Cursor::new(data);
293        let array = M2Array::<u32>::parse(&mut cursor).unwrap();
294
295        assert_eq!(array.count, 5);
296        assert_eq!(array.offset, 0x3020);
297    }
298
299    #[test]
300    fn test_m2array_write() {
301        let array = M2Array::<u32>::new(5, 0x3020);
302        let mut cursor = Cursor::new(Vec::new());
303
304        array.write(&mut cursor).unwrap();
305
306        let data = cursor.into_inner();
307        assert_eq!(
308            data,
309            [
310                0x05, 0x00, 0x00, 0x00, // count = 5
311                0x20, 0x30, 0x00, 0x00, // offset = 0x3020
312            ]
313        );
314    }
315
316    #[test]
317    fn test_c3vector_parse() {
318        let data = [
319            0x00, 0x00, 0x80, 0x3F, // x = 1.0
320            0x00, 0x00, 0x00, 0x40, // y = 2.0
321            0x00, 0x00, 0x40, 0x40, // z = 3.0
322        ];
323
324        let mut cursor = Cursor::new(data);
325        let vector = C3Vector::parse(&mut cursor).unwrap();
326
327        assert_eq!(vector.x, 1.0);
328        assert_eq!(vector.y, 2.0);
329        assert_eq!(vector.z, 3.0);
330    }
331
332    #[test]
333    fn test_c2vector_parse() {
334        let data = [
335            0x00, 0x00, 0x80, 0x3F, // x = 1.0
336            0x00, 0x00, 0x00, 0x40, // y = 2.0
337        ];
338
339        let mut cursor = Cursor::new(data);
340        let vector = C2Vector::parse(&mut cursor).unwrap();
341
342        assert_eq!(vector.x, 1.0);
343        assert_eq!(vector.y, 2.0);
344    }
345
346    #[test]
347    fn test_fixed_string_parse() {
348        let data = [b'T', b'e', b's', b't', 0, 0, 0, 0];
349
350        let mut cursor = Cursor::new(data);
351        let string = FixedString::parse(&mut cursor, 8).unwrap();
352
353        assert_eq!(string.data, b"Test");
354        assert_eq!(string.to_string_lossy(), "Test");
355    }
356}