wow_m2/
common.rs

1use crate::error::{M2Error, Result};
2use crate::io_ext::{ReadExt, WriteExt};
3use std::io::{Read, Seek, 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)]
164pub struct FixedString {
165    pub data: Vec<u8>,
166}
167
168impl FixedString {
169    /// Parse a fixed-width string from a reader
170    pub fn parse<R: Read>(reader: &mut R, len: usize) -> Result<Self> {
171        let mut data = vec![0u8; len];
172        reader.read_exact(&mut data)?;
173
174        // Find null terminator
175        let null_pos = data.iter().position(|&b| b == 0).unwrap_or(len);
176        data.truncate(null_pos);
177
178        Ok(Self { data })
179    }
180
181    /// Write a fixed-width string to a writer
182    pub fn write<W: Write>(&self, writer: &mut W, len: usize) -> Result<()> {
183        let mut data = self.data.clone();
184        data.resize(len, 0);
185        writer.write_all(&data)?;
186
187        Ok(())
188    }
189
190    /// Convert to a string, lossy UTF-8 conversion
191    pub fn to_string_lossy(&self) -> String {
192        String::from_utf8_lossy(&self.data).to_string()
193    }
194}
195
196/// A quaternion for rotations
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub struct Quaternion {
199    pub x: f32,
200    pub y: f32,
201    pub z: f32,
202    pub w: f32,
203}
204
205impl Quaternion {
206    /// Parse a quaternion from a reader
207    pub fn parse<R: Read>(reader: &mut R) -> Result<Self> {
208        let x = reader.read_f32_le()?;
209        let y = reader.read_f32_le()?;
210        let z = reader.read_f32_le()?;
211        let w = reader.read_f32_le()?;
212
213        Ok(Self { x, y, z, w })
214    }
215
216    /// Write a quaternion to a writer
217    pub fn write<W: Write>(&self, writer: &mut W) -> Result<()> {
218        writer.write_f32_le(self.x)?;
219        writer.write_f32_le(self.y)?;
220        writer.write_f32_le(self.z)?;
221        writer.write_f32_le(self.w)?;
222
223        Ok(())
224    }
225
226    /// Convert to a glam quaternion for easier math operations
227    pub fn to_glam(&self) -> glam::Quat {
228        glam::Quat::from_xyzw(self.x, self.y, self.z, self.w)
229    }
230
231    /// Create from a glam quaternion
232    pub fn from_glam(q: glam::Quat) -> Self {
233        Self {
234            x: q.x,
235            y: q.y,
236            z: q.z,
237            w: q.w,
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use std::io::Cursor;
246
247    #[test]
248    fn test_m2array_parse() {
249        let data = [
250            0x05, 0x00, 0x00, 0x00, // count = 5
251            0x20, 0x30, 0x00, 0x00, // offset = 0x3020
252        ];
253
254        let mut cursor = Cursor::new(data);
255        let array = M2Array::<u32>::parse(&mut cursor).unwrap();
256
257        assert_eq!(array.count, 5);
258        assert_eq!(array.offset, 0x3020);
259    }
260
261    #[test]
262    fn test_m2array_write() {
263        let array = M2Array::<u32>::new(5, 0x3020);
264        let mut cursor = Cursor::new(Vec::new());
265
266        array.write(&mut cursor).unwrap();
267
268        let data = cursor.into_inner();
269        assert_eq!(
270            data,
271            [
272                0x05, 0x00, 0x00, 0x00, // count = 5
273                0x20, 0x30, 0x00, 0x00, // offset = 0x3020
274            ]
275        );
276    }
277
278    #[test]
279    fn test_c3vector_parse() {
280        let data = [
281            0x00, 0x00, 0x80, 0x3F, // x = 1.0
282            0x00, 0x00, 0x00, 0x40, // y = 2.0
283            0x00, 0x00, 0x40, 0x40, // z = 3.0
284        ];
285
286        let mut cursor = Cursor::new(data);
287        let vector = C3Vector::parse(&mut cursor).unwrap();
288
289        assert_eq!(vector.x, 1.0);
290        assert_eq!(vector.y, 2.0);
291        assert_eq!(vector.z, 3.0);
292    }
293
294    #[test]
295    fn test_c2vector_parse() {
296        let data = [
297            0x00, 0x00, 0x80, 0x3F, // x = 1.0
298            0x00, 0x00, 0x00, 0x40, // y = 2.0
299        ];
300
301        let mut cursor = Cursor::new(data);
302        let vector = C2Vector::parse(&mut cursor).unwrap();
303
304        assert_eq!(vector.x, 1.0);
305        assert_eq!(vector.y, 2.0);
306    }
307
308    #[test]
309    fn test_fixed_string_parse() {
310        let data = [b'T', b'e', b's', b't', 0, 0, 0, 0];
311
312        let mut cursor = Cursor::new(data);
313        let string = FixedString::parse(&mut cursor, 8).unwrap();
314
315        assert_eq!(string.data, b"Test");
316        assert_eq!(string.to_string_lossy(), "Test");
317    }
318}