wow_m2/chunks/
material.rs

1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Write};
3
4use crate::error::Result;
5use crate::version::M2Version;
6
7bitflags::bitflags! {
8    /// Render flags as defined in the M2 format
9    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10    pub struct M2RenderFlags: u16 {
11        /// Unlit
12        const UNLIT = 0x01;
13        /// Unfogged
14        const UNFOGGED = 0x02;
15        /// No backface culling
16        const NO_BACKFACE_CULLING = 0x04;
17        /// No z-buffer
18        const NO_ZBUFFER = 0x08;
19        /// Affeceted by projection
20        const AFFECTED_BY_PROJECTION = 0x10;
21        /// Depth test
22        const DEPTH_TEST = 0x20;
23        /// Depth write
24        const DEPTH_WRITE = 0x40;
25        /// Unused in code
26        const UNUSED = 0x80;
27        /// Shadow batch related 1
28        const SHADOW_BATCH_1 = 0x100;
29        /// Shadow batch related 2
30        const SHADOW_BATCH_2 = 0x200;
31        /// Unknown
32        const UNKNOWN_400 = 0x400;
33        /// Unknown
34        const UNKNOWN_800 = 0x800;
35    }
36}
37
38bitflags::bitflags! {
39    /// Blend modes as defined in the M2 format
40    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
41    pub struct M2BlendMode: u16 {
42        /// Blend mode: opaque
43        const OPAQUE = 0;
44        /// Blend mode: alpha key
45        const ALPHA_KEY = 1;
46        /// Blend mode: alpha
47        const ALPHA = 2;
48        /// Blend mode: no alpha add
49        const NO_ALPHA_ADD = 3;
50        /// Blend mode: add
51        const ADD = 4;
52        /// Blend mode: mod
53        const MOD = 5;
54        /// Blend mode: mod2x
55        const MOD2X = 6;
56        /// Blend mode for MoP and later: blend add
57        const BLEND_ADD = 7;
58    }
59}
60
61/// Material texture transformations
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum M2TexTransformType {
64    /// No texture transform
65    None = 0,
66    /// Scroll texture
67    Scroll = 1,
68    /// Rotate texture
69    Rotate = 2,
70    /// Scale texture
71    Scale = 3,
72    /// Stretch texture based on time
73    Stretch = 4,
74    /// Transform texture based on camera
75    Camera = 5,
76}
77
78impl M2TexTransformType {
79    /// Parse from integer value
80    pub fn from_u16(value: u16) -> Option<Self> {
81        match value {
82            0 => Some(Self::None),
83            1 => Some(Self::Scroll),
84            2 => Some(Self::Rotate),
85            3 => Some(Self::Scale),
86            4 => Some(Self::Stretch),
87            5 => Some(Self::Camera),
88            _ => None,
89        }
90    }
91}
92
93/// Represents a material layer (render flags) in an M2 model
94/// This corresponds to the ModelRenderFlagsM2 structure in WMVx
95#[derive(Debug, Clone)]
96pub struct M2Material {
97    /// Render flags
98    pub flags: M2RenderFlags,
99    /// Blend mode
100    pub blend_mode: M2BlendMode,
101}
102
103impl M2Material {
104    /// Parse a material from a reader based on the M2 version
105    pub fn parse<R: Read>(reader: &mut R, _version: u32) -> Result<Self> {
106        let flags = M2RenderFlags::from_bits_retain(reader.read_u16_le()?);
107        let blend_mode_raw = reader.read_u16_le()?;
108        let blend_mode = M2BlendMode::from_bits_retain(blend_mode_raw);
109
110        Ok(Self { flags, blend_mode })
111    }
112
113    /// Write a material to a writer based on the M2 version
114    pub fn write<W: Write>(&self, writer: &mut W, _version: u32) -> Result<()> {
115        writer.write_u16_le(self.flags.bits())?;
116        writer.write_u16_le(self.blend_mode.bits())?;
117        Ok(())
118    }
119
120    /// Convert this material to a different version
121    pub fn convert(&self, _target_version: M2Version) -> Self {
122        // Materials have the same structure across all versions
123        self.clone()
124    }
125
126    /// Create a new material with default values
127    pub fn new(blend_mode: M2BlendMode) -> Self {
128        Self {
129            flags: M2RenderFlags::DEPTH_TEST | M2RenderFlags::DEPTH_WRITE,
130            blend_mode,
131        }
132    }
133
134    /// Calculate the size of this material in bytes for a specific version
135    pub fn size_in_bytes(_version: M2Version) -> usize {
136        4 // flags (2) + blend_mode (2)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use std::io::Cursor;
144
145    #[test]
146    fn test_material_parse() {
147        let mut data = Vec::new();
148
149        // Flags (DEPTH_TEST | DEPTH_WRITE)
150        data.extend_from_slice(
151            &(M2RenderFlags::DEPTH_TEST | M2RenderFlags::DEPTH_WRITE)
152                .bits()
153                .to_le_bytes(),
154        );
155
156        // Blend mode (ALPHA)
157        data.extend_from_slice(&M2BlendMode::ALPHA.bits().to_le_bytes());
158
159        let mut cursor = Cursor::new(data);
160        let material =
161            M2Material::parse(&mut cursor, M2Version::Classic.to_header_version()).unwrap();
162
163        assert_eq!(
164            material.flags,
165            M2RenderFlags::DEPTH_TEST | M2RenderFlags::DEPTH_WRITE
166        );
167        assert_eq!(material.blend_mode, M2BlendMode::ALPHA);
168    }
169
170    #[test]
171    fn test_material_write() {
172        let material = M2Material {
173            flags: M2RenderFlags::DEPTH_TEST | M2RenderFlags::DEPTH_WRITE,
174            blend_mode: M2BlendMode::ALPHA,
175        };
176
177        let mut data = Vec::new();
178        material
179            .write(&mut data, M2Version::Classic.to_header_version())
180            .unwrap();
181
182        // Should be 4 bytes total
183        assert_eq!(data.len(), 4);
184
185        // Check the written data
186        assert_eq!(
187            data[0..2],
188            (M2RenderFlags::DEPTH_TEST | M2RenderFlags::DEPTH_WRITE)
189                .bits()
190                .to_le_bytes()
191        );
192        assert_eq!(data[2..4], M2BlendMode::ALPHA.bits().to_le_bytes());
193    }
194
195    #[test]
196    fn test_material_size() {
197        assert_eq!(M2Material::size_in_bytes(M2Version::Classic), 4);
198        assert_eq!(M2Material::size_in_bytes(M2Version::Cataclysm), 4);
199        assert_eq!(M2Material::size_in_bytes(M2Version::WoD), 4);
200    }
201}