use std::io::Read;
use anyhow::Result;
use crate::core::class_index::ClassIndex;
use crate::core::scene_object::ObjInterfaceData;
use crate::core::uoid::{Uoid, read_key_uoid};
use crate::material::layer::Color;
use crate::resource::prp::PlasmaRead;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LightType {
Directional,
Omni,
Spot,
LimitedDir,
}
#[derive(Debug, Clone)]
pub struct LightProperties {
pub ambient: Color,
pub diffuse: Color,
pub specular: Color,
pub soft_volume: Option<Uoid>,
}
#[derive(Debug, Clone)]
pub struct LightInfoData {
pub base: ObjInterfaceData,
pub light_type: LightType,
pub props: LightProperties,
pub light_to_world: [f32; 16],
pub attenuation: [f32; 3], pub cutoff_distance: f32,
pub inner_cone: f32,
pub outer_cone: f32,
pub falloff: f32,
}
impl LightInfoData {
pub fn read(reader: &mut impl Read, light_type: LightType) -> Result<Self> {
let base = ObjInterfaceData::read(reader)?;
let ambient = Color::read(reader)?;
let diffuse = Color::read(reader)?;
let specular = Color::read(reader)?;
let _ = read_matrix44(reader)?; let _ = read_matrix44(reader)?; let light_to_world = read_matrix44(reader)?;
let _ = read_matrix44(reader)?;
let _projection = read_key_uoid(reader)?; let soft_volume = read_key_uoid(reader)?; let _scene_node = read_key_uoid(reader)?;
let num_vis = reader.read_u32()?;
for _ in 0..num_vis {
let _ = read_key_uoid(reader)?;
}
let mut attenuation = [1.0, 0.0, 0.0];
let mut cutoff_distance = 0.0;
let mut inner_cone = 0.0;
let mut outer_cone = 0.0;
let mut falloff = 1.0;
match light_type {
LightType::Omni | LightType::Spot => {
attenuation[0] = reader.read_f32()?;
attenuation[1] = reader.read_f32()?;
attenuation[2] = reader.read_f32()?;
cutoff_distance = reader.read_f32()?;
if light_type == LightType::Spot {
falloff = reader.read_f32()?;
inner_cone = reader.read_f32()?;
outer_cone = reader.read_f32()?;
}
}
LightType::LimitedDir => {
let _width = reader.read_f32()?;
let _height = reader.read_f32()?;
let _depth = reader.read_f32()?;
}
LightType::Directional => {}
}
Ok(Self {
base,
light_type,
props: LightProperties {
ambient,
diffuse,
specular,
soft_volume,
},
light_to_world,
attenuation,
cutoff_distance,
inner_cone,
outer_cone,
falloff,
})
}
}
fn read_matrix44(reader: &mut impl Read) -> Result<[f32; 16]> {
let flag = reader.read_u8()?;
if flag == 0 {
return Ok([
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
]);
}
let mut m = [0f32; 16];
for val in &mut m {
*val = reader.read_f32()?;
}
Ok(m)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::class_index::ClassIndex;
use crate::resource::prp::PrpPage;
use std::io::Cursor;
use std::path::Path;
#[test]
fn test_parse_cleft_lights() {
let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
if !path.exists() {
eprintln!("Skipping test: {:?} not found", path);
return;
}
let page = PrpPage::from_file(path).unwrap();
let mut parsed = 0;
for key in page.keys_of_type(ClassIndex::PL_DIRECTIONAL_LIGHT_INFO) {
if let Some(data) = page.object_data(key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
match LightInfoData::read(&mut cursor, LightType::Directional) {
Ok(light) => {
parsed += 1;
eprintln!(
" DirectionalLight '{}': diffuse=({:.2},{:.2},{:.2})",
key.object_name,
light.props.diffuse.r,
light.props.diffuse.g,
light.props.diffuse.b,
);
}
Err(e) => {
eprintln!("Failed to parse directional light '{}': {}", key.object_name, e);
}
}
}
}
for key in page.keys_of_type(ClassIndex::PL_OMNI_LIGHT_INFO) {
if let Some(data) = page.object_data(key) {
let mut cursor = Cursor::new(data);
let _ = cursor.read_i16().unwrap();
match LightInfoData::read(&mut cursor, LightType::Omni) {
Ok(_) => parsed += 1,
Err(e) => {
eprintln!("Failed to parse omni light '{}': {}", key.object_name, e);
}
}
}
}
eprintln!("Parsed {} lights from Cleft", parsed);
assert!(parsed >= 11, "Cleft should have at least 11 lights, got {}", parsed);
}
}