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 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
13 pub struct M2LightFlags: u16 {
14 const DIRECTIONAL = 0x01;
16 const UNKNOWN_BE_HAIR = 0x02;
18 }
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum M2LightType {
24 Directional = 0,
26 Point = 1,
28 Spot = 2,
30 Ambient = 3,
32}
33
34impl M2LightType {
35 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#[derive(Debug, Clone)]
49pub struct M2Light {
50 pub light_type: M2LightType,
52 pub bone_index: u16,
54 pub position: C3Vector,
56 pub ambient_color_animation: M2AnimationBlock<M2Color>,
58 pub diffuse_color_animation: M2AnimationBlock<M2Color>,
60 pub attenuation_start_animation: M2AnimationBlock<f32>,
62 pub attenuation_end_animation: M2AnimationBlock<f32>,
64 pub visibility_animation: M2AnimationBlock<f32>,
66 pub id: u32,
68 pub flags: M2LightFlags,
70}
71
72impl M2Light {
73 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()?; 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 let flags = M2LightFlags::from_bits_retain(reader.read_u16_le()?);
93 reader.read_u16_le()?; 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 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)?; 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 writer.write_u16_le(self.flags.bits())?;
127 writer.write_u16_le(0)?; Ok(())
130 }
131
132 pub fn convert(&self, _target_version: M2Version) -> Self {
134 self.clone()
135 }
136
137 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 let mut data = Vec::new();
172 light
173 .write(&mut data, M2Version::Vanilla.to_header_version())
174 .unwrap();
175
176 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}