Skip to main content

plasma_prp/core/
load_mask.rs

1//! plLoadMask — quality/capability LOD filtering.
2//!
3//! Two quality bytes (one per capability level 0..1), packed into a single byte
4//! for serialization. "Always" = both 0xFF (no filtering).
5//!
6//! C++ ref: CoreLib/plLoadMask.h, plLoadMask.cpp
7
8use std::io::{Read, Write};
9
10use anyhow::Result;
11
12use crate::resource::prp::PlasmaRead;
13
14/// plLoadMask — determines whether an object should be loaded based on
15/// quality and capability settings.
16#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub struct LoadMask {
18    /// Quality mask for capability level 0 (low-end hardware).
19    pub quality_lo: u8,
20    /// Quality mask for capability level 1 (high-end hardware).
21    pub quality_hi: u8,
22}
23
24impl LoadMask {
25    pub const ALWAYS: LoadMask = LoadMask {
26        quality_lo: 0xFF,
27        quality_hi: 0xFF,
28    };
29
30    pub const NEVER: LoadMask = LoadMask {
31        quality_lo: 0,
32        quality_hi: 0,
33    };
34
35    /// Create from packed byte (same encoding as PRP file).
36    pub fn from_byte(byte: u8) -> Self {
37        Self {
38            quality_lo: ((byte & 0xF0) >> 4) | 0xF0,
39            quality_hi: (byte & 0x0F) | 0xF0,
40        }
41    }
42
43    pub fn new(quality_lo: u8, quality_hi: u8) -> Self {
44        Self {
45            quality_lo,
46            quality_hi,
47        }
48    }
49
50    /// Construct from the packed single-byte serialization format.
51    /// C++ ref: plLoadMask::Read (CoreLib/plLoadMask.cpp:81-92)
52    pub fn from_packed(byte: u8) -> Self {
53        Self {
54            quality_lo: ((byte & 0xF0) >> 4) | 0xF0,
55            quality_hi: (byte & 0x0F) | 0xF0,
56        }
57    }
58
59    /// Returns true if this mask actually filters (is not "always load").
60    pub fn is_used(&self) -> bool {
61        self.quality_lo != 0xFF || self.quality_hi != 0xFF
62    }
63
64    /// Returns true if this mask would never load anything.
65    pub fn never_loads(&self) -> bool {
66        self.quality_lo == 0 && self.quality_hi == 0
67    }
68
69    /// Read from stream: 1 packed byte.
70    /// Upper nibble = quality[0] lower 4 bits, lower nibble = quality[1] lower 4 bits.
71    /// On read, OR in 0xF0 to reconstruct full byte.
72    pub fn read(reader: &mut impl Read) -> Result<Self> {
73        let byte = reader.read_u8()?;
74        let quality_lo = ((byte & 0xF0) >> 4) | 0xF0;
75        let quality_hi = (byte & 0x0F) | 0xF0;
76        Ok(Self {
77            quality_lo,
78            quality_hi,
79        })
80    }
81
82    /// Write to stream: 1 packed byte.
83    pub fn write(&self, writer: &mut impl Write) -> Result<()> {
84        let byte = (self.quality_lo << 4) | (self.quality_hi & 0x0F);
85        writer.write_all(&[byte])?;
86        Ok(())
87    }
88}
89
90impl Default for LoadMask {
91    fn default() -> Self {
92        Self::ALWAYS
93    }
94}
95
96impl std::fmt::Debug for LoadMask {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        if *self == Self::ALWAYS {
99            write!(f, "LoadMask(Always)")
100        } else {
101            write!(
102                f,
103                "LoadMask(lo:{:#04x} hi:{:#04x})",
104                self.quality_lo, self.quality_hi
105            )
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use std::io::Cursor;
114
115    #[test]
116    fn test_always() {
117        let m = LoadMask::ALWAYS;
118        assert!(!m.is_used());
119        assert!(!m.never_loads());
120    }
121
122    #[test]
123    fn test_never() {
124        let m = LoadMask::NEVER;
125        assert!(m.is_used());
126        assert!(m.never_loads());
127    }
128
129    #[test]
130    fn test_round_trip() {
131        let m = LoadMask::new(0xF3, 0xF5);
132        let mut buf = Vec::new();
133        m.write(&mut buf).unwrap();
134        assert_eq!(buf.len(), 1);
135
136        let mut cursor = Cursor::new(&buf);
137        let m2 = LoadMask::read(&mut cursor).unwrap();
138        assert_eq!(m, m2);
139    }
140
141    #[test]
142    fn test_always_round_trip() {
143        let m = LoadMask::ALWAYS;
144        let mut buf = Vec::new();
145        m.write(&mut buf).unwrap();
146
147        let mut cursor = Cursor::new(&buf);
148        let m2 = LoadMask::read(&mut cursor).unwrap();
149        assert_eq!(m, m2);
150    }
151}