plasma_prp/lighting/
mod.rs1use std::io::Read;
6
7use anyhow::Result;
8
9use crate::core::class_index::ClassIndex;
10use crate::core::scene_object::ObjInterfaceData;
11use crate::core::uoid::{Uoid, read_key_uoid};
12use crate::material::layer::Color;
13use crate::resource::prp::PlasmaRead;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum LightType {
18 Directional,
19 Omni,
20 Spot,
21 LimitedDir,
22}
23
24#[derive(Debug, Clone)]
26pub struct LightProperties {
27 pub ambient: Color,
28 pub diffuse: Color,
29 pub specular: Color,
30 pub soft_volume: Option<Uoid>,
31}
32
33#[derive(Debug, Clone)]
35pub struct LightInfoData {
36 pub base: ObjInterfaceData,
37 pub light_type: LightType,
38 pub props: LightProperties,
39 pub light_to_world: [f32; 16],
42
43 pub attenuation: [f32; 3], pub cutoff_distance: f32,
47
48 pub inner_cone: f32,
50 pub outer_cone: f32,
51 pub falloff: f32,
52}
53
54impl LightInfoData {
55 pub fn read(reader: &mut impl Read, light_type: LightType) -> Result<Self> {
58 let base = ObjInterfaceData::read(reader)?;
59
60 let ambient = Color::read(reader)?;
62 let diffuse = Color::read(reader)?;
63 let specular = Color::read(reader)?;
64
65 let _ = read_matrix44(reader)?; let _ = read_matrix44(reader)?; let light_to_world = read_matrix44(reader)?;
69 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()?;
79 for _ in 0..num_vis {
80 let _ = read_key_uoid(reader)?;
81 }
82
83 let mut attenuation = [1.0, 0.0, 0.0];
84 let mut cutoff_distance = 0.0;
85 let mut inner_cone = 0.0;
86 let mut outer_cone = 0.0;
87 let mut falloff = 1.0;
88
89 match light_type {
90 LightType::Omni | LightType::Spot => {
91 attenuation[0] = reader.read_f32()?;
93 attenuation[1] = reader.read_f32()?;
94 attenuation[2] = reader.read_f32()?;
95 cutoff_distance = reader.read_f32()?;
96
97 if light_type == LightType::Spot {
98 falloff = reader.read_f32()?;
101 inner_cone = reader.read_f32()?;
102 outer_cone = reader.read_f32()?;
103 }
104 }
105 LightType::LimitedDir => {
106 let _width = reader.read_f32()?;
110 let _height = reader.read_f32()?;
111 let _depth = reader.read_f32()?;
112 }
113 LightType::Directional => {}
114 }
115
116 Ok(Self {
117 base,
118 light_type,
119 props: LightProperties {
120 ambient,
121 diffuse,
122 specular,
123 soft_volume,
124 },
125 light_to_world,
126 attenuation,
127 cutoff_distance,
128 inner_cone,
129 outer_cone,
130 falloff,
131 })
132 }
133}
134
135fn read_matrix44(reader: &mut impl Read) -> Result<[f32; 16]> {
136 let flag = reader.read_u8()?;
137 if flag == 0 {
138 return Ok([
139 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,
140 ]);
141 }
142 let mut m = [0f32; 16];
143 for val in &mut m {
144 *val = reader.read_f32()?;
145 }
146 Ok(m)
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::core::class_index::ClassIndex;
153 use crate::resource::prp::PrpPage;
154 use std::io::Cursor;
155 use std::path::Path;
156
157 #[test]
158 fn test_parse_cleft_lights() {
159 let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
160 if !path.exists() {
161 eprintln!("Skipping test: {:?} not found", path);
162 return;
163 }
164
165 let page = PrpPage::from_file(path).unwrap();
166 let mut parsed = 0;
167
168 for key in page.keys_of_type(ClassIndex::PL_DIRECTIONAL_LIGHT_INFO) {
170 if let Some(data) = page.object_data(key) {
171 let mut cursor = Cursor::new(data);
172 let _ = cursor.read_i16().unwrap();
173
174 match LightInfoData::read(&mut cursor, LightType::Directional) {
175 Ok(light) => {
176 parsed += 1;
177 eprintln!(
178 " DirectionalLight '{}': diffuse=({:.2},{:.2},{:.2})",
179 key.object_name,
180 light.props.diffuse.r,
181 light.props.diffuse.g,
182 light.props.diffuse.b,
183 );
184 }
185 Err(e) => {
186 eprintln!("Failed to parse directional light '{}': {}", key.object_name, e);
187 }
188 }
189 }
190 }
191
192 for key in page.keys_of_type(ClassIndex::PL_OMNI_LIGHT_INFO) {
194 if let Some(data) = page.object_data(key) {
195 let mut cursor = Cursor::new(data);
196 let _ = cursor.read_i16().unwrap();
197
198 match LightInfoData::read(&mut cursor, LightType::Omni) {
199 Ok(_) => parsed += 1,
200 Err(e) => {
201 eprintln!("Failed to parse omni light '{}': {}", key.object_name, e);
202 }
203 }
204 }
205 }
206
207 eprintln!("Parsed {} lights from Cleft", parsed);
208 assert!(parsed >= 11, "Cleft should have at least 11 lights, got {}", parsed);
210 }
211}