use crate::io_ext::{ReadExt, WriteExt};
use std::io::{Read, Seek, Write};
use crate::chunks::animation::{M2AnimationBlock, M2AnimationTrack};
use crate::chunks::color_animation::M2Color;
use crate::common::C3Vector;
use crate::error::Result;
use crate::version::M2Version;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct M2LightFlags: u16 {
const DIRECTIONAL = 0x01;
const UNKNOWN_BE_HAIR = 0x02;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum M2LightType {
Directional = 0,
Point = 1,
Spot = 2,
Ambient = 3,
}
impl M2LightType {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Directional),
1 => Some(Self::Point),
2 => Some(Self::Spot),
3 => Some(Self::Ambient),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct M2Light {
pub light_type: M2LightType,
pub bone_index: u16,
pub position: C3Vector,
pub ambient_color_animation: M2AnimationBlock<M2Color>,
pub diffuse_color_animation: M2AnimationBlock<M2Color>,
pub attenuation_start_animation: M2AnimationBlock<f32>,
pub attenuation_end_animation: M2AnimationBlock<f32>,
pub visibility_animation: M2AnimationBlock<f32>,
pub id: u32,
pub flags: M2LightFlags,
}
impl M2Light {
pub fn parse<R: Read + Seek>(reader: &mut R, _version: u32) -> Result<Self> {
let light_type_raw = reader.read_u8()?;
let light_type = M2LightType::from_u8(light_type_raw).unwrap_or(M2LightType::Point);
let bone_index = reader.read_u16_le()?;
reader.read_u8()?;
let position = C3Vector::parse(reader)?;
let ambient_color_animation = M2AnimationBlock::parse(reader)?;
let diffuse_color_animation = M2AnimationBlock::parse(reader)?;
let attenuation_start_animation = M2AnimationBlock::parse(reader)?;
let attenuation_end_animation = M2AnimationBlock::parse(reader)?;
let visibility_animation = M2AnimationBlock::parse(reader)?;
let id = reader.read_u32_le()?;
let flags = M2LightFlags::from_bits_retain(reader.read_u16_le()?);
reader.read_u16_le()?;
Ok(Self {
light_type,
bone_index,
position,
ambient_color_animation,
diffuse_color_animation,
attenuation_start_animation,
attenuation_end_animation,
visibility_animation,
id,
flags,
})
}
pub fn write<W: Write>(&self, writer: &mut W, _version: u32) -> Result<()> {
writer.write_u8(self.light_type as u8)?;
writer.write_u16_le(self.bone_index)?;
writer.write_u8(0)?;
self.position.write(writer)?;
self.ambient_color_animation.write(writer)?;
self.diffuse_color_animation.write(writer)?;
self.attenuation_start_animation.write(writer)?;
self.attenuation_end_animation.write(writer)?;
self.visibility_animation.write(writer)?;
writer.write_u32_le(self.id)?;
writer.write_u16_le(self.flags.bits())?;
writer.write_u16_le(0)?;
Ok(())
}
pub fn convert(&self, _target_version: M2Version) -> Self {
self.clone()
}
pub fn new(light_type: M2LightType, bone_index: u16, id: u32) -> Self {
Self {
light_type,
bone_index,
position: C3Vector {
x: 0.0,
y: 0.0,
z: 0.0,
},
ambient_color_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
diffuse_color_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
attenuation_start_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
attenuation_end_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
visibility_animation: M2AnimationBlock::new(M2AnimationTrack::default()),
id,
flags: match light_type {
M2LightType::Directional => M2LightFlags::DIRECTIONAL,
_ => M2LightFlags::empty(),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_light_parse_write() {
let light = M2Light::new(M2LightType::Point, 1, 0);
let mut data = Vec::new();
light
.write(&mut data, M2Version::Vanilla.to_header_version())
.unwrap();
let mut cursor = Cursor::new(data);
let parsed = M2Light::parse(&mut cursor, M2Version::Vanilla.to_header_version()).unwrap();
assert_eq!(parsed.light_type, M2LightType::Point);
assert_eq!(parsed.bone_index, 1);
assert_eq!(parsed.id, 0);
assert_eq!(parsed.flags, M2LightFlags::empty());
}
#[test]
fn test_light_types() {
assert_eq!(M2LightType::from_u8(0), Some(M2LightType::Directional));
assert_eq!(M2LightType::from_u8(1), Some(M2LightType::Point));
assert_eq!(M2LightType::from_u8(2), Some(M2LightType::Spot));
assert_eq!(M2LightType::from_u8(3), Some(M2LightType::Ambient));
assert_eq!(M2LightType::from_u8(4), None);
}
#[test]
fn test_light_flags() {
let flags = M2LightFlags::DIRECTIONAL;
assert!(flags.contains(M2LightFlags::DIRECTIONAL));
assert!(!flags.contains(M2LightFlags::UNKNOWN_BE_HAIR));
}
}