wow_m2/chunks/
light.rs

1use crate::io_ext::{ReadExt, WriteExt};
2use std::io::{Read, Seek, Write};
3
4use crate::chunks::animation::{M2AnimationBlock, M2AnimationTrack};
5use crate::chunks::color_animation::M2Color;
6use crate::common::C3Vector;
7use crate::error::Result;
8use crate::version::M2Version;
9
10bitflags::bitflags! {
11    /// Light flags as defined in the M2 format
12    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
13    pub struct M2LightFlags: u16 {
14        /// Light is directional (otherwise it's a point light)
15        const DIRECTIONAL = 0x01;
16        /// Unknown flag from Blood Elf "BE_hairSynthesizer.m2"
17        const UNKNOWN_BE_HAIR = 0x02;
18    }
19}
20
21/// Light type enum
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum M2LightType {
24    /// Directional light (like the sun)
25    Directional = 0,
26    /// Point light (emits light in all directions)
27    Point = 1,
28    /// Spot light (emits light in a cone)
29    Spot = 2,
30    /// Ambient light (global illumination)
31    Ambient = 3,
32}
33
34impl M2LightType {
35    /// Parse from integer value
36    pub fn from_u8(value: u8) -> Option<Self> {
37        match value {
38            0 => Some(Self::Directional),
39            1 => Some(Self::Point),
40            2 => Some(Self::Spot),
41            3 => Some(Self::Ambient),
42            _ => None,
43        }
44    }
45}
46
47/// Represents a light in an M2 model
48#[derive(Debug, Clone)]
49pub struct M2Light {
50    /// Light type
51    pub light_type: M2LightType,
52    /// Bone to attach the light to
53    pub bone_index: u16,
54    /// Light position
55    pub position: C3Vector,
56    /// Ambient color animation
57    pub ambient_color_animation: M2AnimationBlock<M2Color>,
58    /// Diffuse color animation
59    pub diffuse_color_animation: M2AnimationBlock<M2Color>,
60    /// Attenuation start animation (where light begins to fade)
61    pub attenuation_start_animation: M2AnimationBlock<f32>,
62    /// Attenuation end animation (where light fully fades)
63    pub attenuation_end_animation: M2AnimationBlock<f32>,
64    /// Visibility animation
65    pub visibility_animation: M2AnimationBlock<f32>,
66    /// Light ID
67    pub id: u32,
68    /// Light flags
69    pub flags: M2LightFlags,
70}
71
72impl M2Light {
73    /// Parse a light from a reader based on the M2 version
74    pub fn parse<R: Read + Seek>(reader: &mut R, _version: u32) -> Result<Self> {
75        let light_type_raw = reader.read_u8()?;
76        let light_type = M2LightType::from_u8(light_type_raw).unwrap_or(M2LightType::Point);
77
78        let bone_index = reader.read_u16_le()?;
79        reader.read_u8()?; // Skip padding
80
81        let position = C3Vector::parse(reader)?;
82
83        let ambient_color_animation = M2AnimationBlock::parse(reader)?;
84        let diffuse_color_animation = M2AnimationBlock::parse(reader)?;
85        let attenuation_start_animation = M2AnimationBlock::parse(reader)?;
86        let attenuation_end_animation = M2AnimationBlock::parse(reader)?;
87        let visibility_animation = M2AnimationBlock::parse(reader)?;
88
89        let id = reader.read_u32_le()?;
90
91        // 2 bytes for flags, 2 bytes of padding
92        let flags = M2LightFlags::from_bits_retain(reader.read_u16_le()?);
93        reader.read_u16_le()?; // Skip padding
94
95        Ok(Self {
96            light_type,
97            bone_index,
98            position,
99            ambient_color_animation,
100            diffuse_color_animation,
101            attenuation_start_animation,
102            attenuation_end_animation,
103            visibility_animation,
104            id,
105            flags,
106        })
107    }
108
109    /// Write a light to a writer based on the M2 version
110    pub fn write<W: Write>(&self, writer: &mut W, _version: u32) -> Result<()> {
111        writer.write_u8(self.light_type as u8)?;
112        writer.write_u16_le(self.bone_index)?;
113        writer.write_u8(0)?; // Write padding
114
115        self.position.write(writer)?;
116
117        self.ambient_color_animation.write(writer)?;
118        self.diffuse_color_animation.write(writer)?;
119        self.attenuation_start_animation.write(writer)?;
120        self.attenuation_end_animation.write(writer)?;
121        self.visibility_animation.write(writer)?;
122
123        writer.write_u32_le(self.id)?;
124
125        // 2 bytes for flags, 2 bytes of padding
126        writer.write_u16_le(self.flags.bits())?;
127        writer.write_u16_le(0)?; // Write padding
128
129        Ok(())
130    }
131
132    /// Convert this light to a different version (no version differences for lights)
133    pub fn convert(&self, _target_version: M2Version) -> Self {
134        self.clone()
135    }
136
137    /// Create a new light with default values
138    pub fn new(light_type: M2LightType, bone_index: u16, id: u32) -> Self {
139        Self {
140            light_type,
141            bone_index,
142            position: C3Vector {
143                x: 0.0,
144                y: 0.0,
145                z: 0.0,
146            },
147            ambient_color_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
148            diffuse_color_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
149            attenuation_start_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
150            attenuation_end_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
151            visibility_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
152            id,
153            flags: match light_type {
154                M2LightType::Directional => M2LightFlags::DIRECTIONAL,
155                _ => M2LightFlags::empty(),
156            },
157        }
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use std::io::Cursor;
165
166    #[test]
167    fn test_light_parse_write() {
168        let light = M2Light::new(M2LightType::Point, 1, 0);
169
170        // Test write
171        let mut data = Vec::new();
172        light
173            .write(&mut data, M2Version::Vanilla.to_header_version())
174            .unwrap();
175
176        // Test parse
177        let mut cursor = Cursor::new(data);
178        let parsed = M2Light::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
179
180        assert_eq!(parsed.light_type, M2LightType::Point);
181        assert_eq!(parsed.bone_index, 1);
182        assert_eq!(parsed.id, 0);
183        assert_eq!(parsed.flags, M2LightFlags::empty());
184    }
185
186    #[test]
187    fn test_light_types() {
188        assert_eq!(M2LightType::from_u8(0), Some(M2LightType::Directional));
189        assert_eq!(M2LightType::from_u8(1), Some(M2LightType::Point));
190        assert_eq!(M2LightType::from_u8(2), Some(M2LightType::Spot));
191        assert_eq!(M2LightType::from_u8(3), Some(M2LightType::Ambient));
192        assert_eq!(M2LightType::from_u8(4), None);
193    }
194
195    #[test]
196    fn test_light_flags() {
197        let flags = M2LightFlags::DIRECTIONAL;
198        assert!(flags.contains(M2LightFlags::DIRECTIONAL));
199        assert!(!flags.contains(M2LightFlags::UNKNOWN_BE_HAIR));
200    }
201}