Skip to main content

steel_core/chunk/light/
data_layer.rs

1use super::{DATA_LAYER_EDGE, DATA_LAYER_SIZE, MAX_LIGHT_LEVEL};
2
3/// Error returned when packed light data has the wrong length.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct DataLayerLengthError {
6    /// Actual number of bytes provided.
7    pub actual: usize,
8}
9
10/// Packed 4-bit light values for one 16x16x16 light section.
11///
12/// This mirrors vanilla's `DataLayer`: values are indexed as
13/// `y << 8 | z << 4 | x`, with two light nibbles packed into each byte. A
14/// homogeneous layer stores only a default value until bytes are needed.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct DataLayer {
17    data: Option<Box<[u8; DATA_LAYER_SIZE]>>,
18    default_value: u8,
19}
20
21impl DataLayer {
22    /// Creates an empty all-zero layer.
23    #[must_use]
24    pub const fn new() -> Self {
25        Self {
26            data: None,
27            default_value: 0,
28        }
29    }
30
31    /// Creates a homogeneous layer filled with `value`.
32    #[must_use]
33    pub const fn filled(value: u8) -> Self {
34        Self {
35            data: None,
36            default_value: value & MAX_LIGHT_LEVEL,
37        }
38    }
39
40    /// Creates a layer from packed vanilla bytes.
41    pub fn from_bytes(bytes: Box<[u8]>) -> Result<Self, DataLayerLengthError> {
42        let actual = bytes.len();
43        let Ok(data) = bytes.try_into() else {
44            return Err(DataLayerLengthError { actual });
45        };
46
47        Ok(Self::from_packed_data(data))
48    }
49
50    /// Creates a layer from already length-checked packed vanilla bytes.
51    #[must_use]
52    pub(crate) const fn from_packed_data(data: Box<[u8; DATA_LAYER_SIZE]>) -> Self {
53        Self {
54            data: Some(data),
55            default_value: 0,
56        }
57    }
58
59    /// Returns the light value at local section coordinates.
60    #[must_use]
61    pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
62        debug_assert!(x < DATA_LAYER_EDGE);
63        debug_assert!(y < DATA_LAYER_EDGE);
64        debug_assert!(z < DATA_LAYER_EDGE);
65
66        self.get_at_index(Self::index(x, y, z))
67    }
68
69    /// Sets the light value at local section coordinates.
70    pub fn set(&mut self, x: usize, y: usize, z: usize, value: u8) {
71        debug_assert!(x < DATA_LAYER_EDGE);
72        debug_assert!(y < DATA_LAYER_EDGE);
73        debug_assert!(z < DATA_LAYER_EDGE);
74
75        self.set_at_index(Self::index(x, y, z), value);
76    }
77
78    /// Fills the layer with one homogeneous value.
79    pub fn fill(&mut self, value: u8) {
80        self.default_value = value & MAX_LIGHT_LEVEL;
81        self.data = None;
82    }
83
84    /// Returns true when the layer is represented by one homogeneous value.
85    #[must_use]
86    pub const fn is_homogeneous(&self) -> bool {
87        self.data.is_none()
88    }
89
90    /// Returns the homogeneous value when no packed data exists.
91    #[must_use]
92    pub const fn homogeneous_value(&self) -> Option<u8> {
93        if self.data.is_none() {
94            Some(self.default_value)
95        } else {
96            None
97        }
98    }
99
100    /// Returns true when the layer is known to be filled with `value`.
101    #[must_use]
102    pub const fn is_filled_with(&self, value: u8) -> bool {
103        self.data.is_none() && self.default_value == (value & MAX_LIGHT_LEVEL)
104    }
105
106    /// Returns true when this layer is an all-zero homogeneous layer.
107    #[must_use]
108    pub const fn is_empty(&self) -> bool {
109        self.data.is_none() && self.default_value == 0
110    }
111
112    /// Returns true when all packed values are zero.
113    #[must_use]
114    pub fn is_all_zero(&self) -> bool {
115        match &self.data {
116            Some(data) => data.iter().all(|value| *value == 0),
117            None => self.default_value == 0,
118        }
119    }
120
121    /// Returns a deep copy of this layer.
122    #[must_use]
123    pub fn copy(&self) -> Self {
124        self.clone()
125    }
126
127    /// Returns packed bytes without changing the layer representation.
128    #[must_use]
129    pub fn to_bytes(&self) -> Box<[u8; DATA_LAYER_SIZE]> {
130        if let Some(data) = &self.data {
131            Box::new(**data)
132        } else {
133            Box::new([Self::pack_filled(self.default_value); DATA_LAYER_SIZE])
134        }
135    }
136
137    fn get_at_index(&self, index: usize) -> u8 {
138        if let Some(data) = &self.data {
139            let packed = data[Self::byte_index(index)];
140            packed >> (4 * Self::nibble_index(index)) & MAX_LIGHT_LEVEL
141        } else {
142            self.default_value
143        }
144    }
145
146    fn set_at_index(&mut self, index: usize, value: u8) {
147        let data = self.data.get_or_insert_with(|| {
148            Box::new([Self::pack_filled(self.default_value); DATA_LAYER_SIZE])
149        });
150        let byte_index = Self::byte_index(index);
151        let shift = 4 * Self::nibble_index(index);
152        let mask = !(MAX_LIGHT_LEVEL << shift);
153        let value_to_set = (value & MAX_LIGHT_LEVEL) << shift;
154        data[byte_index] = data[byte_index] & mask | value_to_set;
155    }
156
157    const fn index(x: usize, y: usize, z: usize) -> usize {
158        y << 8 | z << 4 | x
159    }
160
161    const fn byte_index(index: usize) -> usize {
162        index >> 1
163    }
164
165    const fn nibble_index(index: usize) -> usize {
166        index & 1
167    }
168
169    const fn pack_filled(value: u8) -> u8 {
170        let value = value & MAX_LIGHT_LEVEL;
171        value | value << 4
172    }
173}
174
175impl Default for DataLayer {
176    fn default() -> Self {
177        Self::new()
178    }
179}