Skip to main content

plasma_prp/material/
layer.rs

1//! plLayer — concrete layer implementation with state, colors, textures.
2//!
3//! C++ ref: plSurface/plLayer.h/.cpp, plLayerInterface.h/.cpp
4
5use std::io::Read;
6
7use anyhow::Result;
8
9use crate::core::synched_object::SynchedObjectData;
10use crate::core::uoid::{Uoid, read_key_uoid};
11use crate::resource::prp::PlasmaRead;
12
13use super::state::MatState;
14
15/// RGBA color.
16#[derive(Debug, Clone, Copy, Default)]
17pub struct Color {
18    pub r: f32,
19    pub g: f32,
20    pub b: f32,
21    pub a: f32,
22}
23
24impl Color {
25    pub fn read(reader: &mut impl Read) -> Result<Self> {
26        Ok(Self {
27            r: reader.read_f32()?,
28            g: reader.read_f32()?,
29            b: reader.read_f32()?,
30            a: reader.read_f32()?,
31        })
32    }
33
34    pub fn white() -> Self {
35        Self {
36            r: 1.0,
37            g: 1.0,
38            b: 1.0,
39            a: 1.0,
40        }
41    }
42}
43
44/// Read an hsMatrix44 from a stream.
45fn read_matrix44(reader: &mut impl Read) -> Result<[f32; 16]> {
46    let flag = reader.read_u8()?;
47    if flag == 0 {
48        return Ok([
49            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,
50        ]);
51    }
52    let mut m = [0f32; 16];
53    for val in &mut m {
54        *val = reader.read_f32()?;
55    }
56    Ok(m)
57}
58
59/// UVW source constants.
60#[allow(dead_code)]
61pub mod uvw_src {
62    pub const PASS_THRU: u32 = 0x00000000;
63    pub const IDX_MASK: u32 = 0x0000FFFF;
64    pub const NORMAL: u32 = 0x00010000;
65    pub const POSITION: u32 = 0x00020000;
66    pub const REFLECT: u32 = 0x00030000;
67}
68
69/// Parsed plLayer data.
70#[derive(Debug, Clone)]
71pub struct LayerData {
72    // plLayerInterface base
73    pub self_key: Option<Uoid>,
74    pub synched: SynchedObjectData,
75    pub underlay: Option<Uoid>,
76
77    // plLayer fields
78    pub state: MatState,
79    pub transform: [f32; 16],
80    pub preshade_color: Color,
81    pub runtime_color: Color,
82    pub ambient_color: Color,
83    pub specular_color: Color,
84    pub uvw_src: u32,
85    pub opacity: f32,
86    pub lod_bias: f32,
87    pub specular_power: f32,
88    pub texture: Option<Uoid>,
89    pub vertex_shader: Option<Uoid>,
90    pub pixel_shader: Option<Uoid>,
91    pub bump_env_xfm: [f32; 16],
92}
93
94impl LayerData {
95    /// Read a plLayer from a stream (after creatable class index).
96    pub fn read(reader: &mut impl Read) -> Result<Self> {
97        // plLayerInterface::Read → plSynchedObject::Read → hsKeyedObject::Read
98        let self_key = read_key_uoid(reader)?;
99        let synched = SynchedObjectData::read(reader)?;
100        // underlay key
101        let underlay = read_key_uoid(reader)?;
102
103        // plLayer::Read
104        let state = MatState::read(reader)?;
105
106        let transform = read_matrix44(reader)?;
107        let preshade_color = Color::read(reader)?;
108        let runtime_color = Color::read(reader)?;
109        let ambient_color = Color::read(reader)?;
110        let specular_color = Color::read(reader)?;
111
112        let uvw_src = reader.read_u32()?;
113        let opacity = reader.read_f32()?;
114        let lod_bias = reader.read_f32()?;
115        let specular_power = reader.read_f32()?;
116
117        let texture = read_key_uoid(reader)?;
118        let vertex_shader = read_key_uoid(reader)?;
119        let pixel_shader = read_key_uoid(reader)?;
120
121        let bump_env_xfm = read_matrix44(reader)?;
122
123        Ok(Self {
124            self_key,
125            synched,
126            underlay,
127            state,
128            transform,
129            preshade_color,
130            runtime_color,
131            ambient_color,
132            specular_color,
133            uvw_src,
134            opacity,
135            lod_bias,
136            specular_power,
137            texture,
138            vertex_shader,
139            pixel_shader,
140            bump_env_xfm,
141        })
142    }
143
144    /// Get the texture name, if any.
145    pub fn texture_name(&self) -> Option<&str> {
146        self.texture.as_ref().map(|u| u.object_name.as_str())
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::core::class_index::ClassIndex;
154    use crate::resource::prp::PrpPage;
155    use std::io::Cursor;
156    use std::path::Path;
157
158    #[test]
159    fn test_parse_cleft_layers() {
160        let path = Path::new("../../Plasma/staging/client/dat/Cleft_District_Cleft.prp");
161        if !path.exists() {
162            eprintln!("Skipping test: {:?} not found", path);
163            return;
164        }
165
166        let page = PrpPage::from_file(path).unwrap();
167        let layer_keys: Vec<_> = page.keys_of_type(ClassIndex::PL_LAYER);
168
169        let mut parsed = 0;
170        let mut with_texture = 0;
171        for key in &layer_keys {
172            if let Some(data) = page.object_data(key) {
173                let mut cursor = Cursor::new(data);
174                let _ = cursor.read_i16().unwrap();
175
176                match LayerData::read(&mut cursor) {
177                    Ok(layer) => {
178                        parsed += 1;
179                        if layer.texture.is_some() {
180                            with_texture += 1;
181                        }
182                        assert_eq!(
183                            layer.self_key.as_ref().unwrap().object_name,
184                            key.object_name
185                        );
186                        // Opacity should be in [0, 1] range
187                        assert!(
188                            layer.opacity >= 0.0 && layer.opacity <= 1.0,
189                            "Invalid opacity {} for {}",
190                            layer.opacity,
191                            key.object_name
192                        );
193                    }
194                    Err(e) => {
195                        panic!("Failed to parse layer '{}': {}", key.object_name, e);
196                    }
197                }
198            }
199        }
200
201        eprintln!(
202            "Parsed {} layers ({} with textures) from Cleft",
203            parsed, with_texture
204        );
205        assert!(parsed > 0);
206        assert!(with_texture > 0);
207    }
208}