1use steel_utils::{BlockPos, SectionPos};
2
3use crate::chunk::section::Sections;
4
5use super::{
6 DATA_LAYER_EDGE, DATA_LAYER_SIZE, LightLayer, LightSectionRange, LightSectionRangeError,
7 MAX_LIGHT_LEVEL,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct ChunkLightEmptinessMapLengthError {
13 pub expected: usize,
15 pub actual: usize,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum LightSectionData {
22 Homogeneous(u8),
24 Packed(Box<[u8; DATA_LAYER_SIZE]>),
26}
27
28impl LightSectionData {
29 #[must_use]
31 pub const fn homogeneous(value: u8) -> Self {
32 Self::Homogeneous(value & MAX_LIGHT_LEVEL)
33 }
34
35 #[must_use]
37 pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
38 debug_assert!(x < DATA_LAYER_EDGE);
39 debug_assert!(y < DATA_LAYER_EDGE);
40 debug_assert!(z < DATA_LAYER_EDGE);
41
42 match self {
43 Self::Homogeneous(value) => *value,
44 Self::Packed(data) => Self::get_from_packed(data, Self::index(x, y, z)),
45 }
46 }
47
48 pub fn set(&mut self, x: usize, y: usize, z: usize, value: u8) {
50 debug_assert!(x < DATA_LAYER_EDGE);
51 debug_assert!(y < DATA_LAYER_EDGE);
52 debug_assert!(z < DATA_LAYER_EDGE);
53
54 let index = Self::index(x, y, z);
55 match self {
56 Self::Homogeneous(default_value) => {
57 let mut data = Box::new([Self::pack_filled(*default_value); DATA_LAYER_SIZE]);
58 Self::set_in_packed(&mut data, index, value);
59 *self = Self::Packed(data);
60 }
61 Self::Packed(data) => Self::set_in_packed(data, index, value),
62 }
63 }
64
65 pub fn fill(&mut self, value: u8) {
67 *self = Self::homogeneous(value);
68 }
69
70 #[must_use]
72 pub const fn is_homogeneous(&self) -> bool {
73 matches!(self, Self::Homogeneous(_))
74 }
75
76 #[must_use]
78 pub const fn is_empty(&self) -> bool {
79 matches!(self, Self::Homogeneous(0))
80 }
81
82 #[must_use]
84 pub fn is_all_zero(&self) -> bool {
85 match self {
86 Self::Homogeneous(value) => *value == 0,
87 Self::Packed(data) => data.iter().all(|value| *value == 0),
88 }
89 }
90
91 #[must_use]
93 pub fn to_bytes(&self) -> Box<[u8; DATA_LAYER_SIZE]> {
94 match self {
95 Self::Homogeneous(value) => Box::new([Self::pack_filled(*value); DATA_LAYER_SIZE]),
96 Self::Packed(data) => Box::new(**data),
97 }
98 }
99
100 const fn get_from_packed(data: &[u8; DATA_LAYER_SIZE], index: usize) -> u8 {
101 let packed = data[index >> 1];
102 packed >> ((index & 1) << 2) & MAX_LIGHT_LEVEL
103 }
104
105 const fn set_in_packed(data: &mut [u8; DATA_LAYER_SIZE], index: usize, value: u8) {
106 let byte_index = index >> 1;
107 let shift = (index & 1) << 2;
108 let mask = !(MAX_LIGHT_LEVEL << shift);
109 let value = (value & MAX_LIGHT_LEVEL) << shift;
110 data[byte_index] = data[byte_index] & mask | value;
111 }
112
113 const fn index(x: usize, y: usize, z: usize) -> usize {
114 y << 8 | z << 4 | x
115 }
116
117 const fn pack_filled(value: u8) -> u8 {
118 let value = value & MAX_LIGHT_LEVEL;
119 value | value << 4
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum LightSection {
126 Missing,
128 Visible(LightSectionData),
130 Internal(LightSectionData),
132}
133
134impl LightSection {
135 #[must_use]
137 pub const fn missing() -> Self {
138 Self::Missing
139 }
140
141 #[must_use]
143 pub const fn visible(data: LightSectionData) -> Self {
144 Self::Visible(data)
145 }
146
147 #[must_use]
149 pub const fn internal(data: LightSectionData) -> Self {
150 Self::Internal(data)
151 }
152
153 #[must_use]
155 pub const fn visible_data(&self) -> Option<&LightSectionData> {
156 match self {
157 Self::Visible(data) => Some(data),
158 Self::Missing | Self::Internal(_) => None,
159 }
160 }
161
162 #[must_use]
164 pub const fn is_present(&self) -> bool {
165 matches!(self, Self::Visible(_) | Self::Internal(_))
166 }
167}
168
169#[derive(Debug)]
171pub struct ChunkLightLayerStorage {
172 layer: LightLayer,
173 range: LightSectionRange,
174 chunk_section_count: usize,
175 sections: Box<[LightSection]>,
176 emptiness_map: Option<Box<[bool]>>,
177}
178
179impl ChunkLightLayerStorage {
180 #[must_use]
182 pub fn new(layer: LightLayer, range: LightSectionRange, chunk_section_count: usize) -> Self {
183 let sections = (0..range.section_count())
184 .map(|_| LightSection::missing())
185 .collect();
186 Self {
187 layer,
188 range,
189 chunk_section_count,
190 sections,
191 emptiness_map: None,
192 }
193 }
194
195 #[must_use]
197 pub const fn layer(&self) -> LightLayer {
198 self.layer
199 }
200
201 #[must_use]
203 pub const fn range(&self) -> LightSectionRange {
204 self.range
205 }
206
207 #[must_use]
209 pub fn sections(&self) -> &[LightSection] {
210 &self.sections
211 }
212
213 #[must_use]
215 pub fn sections_mut(&mut self) -> &mut [LightSection] {
216 &mut self.sections
217 }
218
219 #[must_use]
221 pub const fn chunk_section_count(&self) -> usize {
222 self.chunk_section_count
223 }
224
225 #[must_use]
227 pub fn section(&self, section_y: i32) -> Option<&LightSection> {
228 self.range
229 .section_index(section_y)
230 .and_then(|index| self.sections.get(index))
231 }
232
233 pub fn section_mut(&mut self, section_y: i32) -> Option<&mut LightSection> {
235 let index = self.range.section_index(section_y)?;
236 self.sections.get_mut(index)
237 }
238
239 #[must_use]
241 pub fn emptiness_map(&self) -> Option<&[bool]> {
242 self.emptiness_map.as_deref()
243 }
244
245 #[must_use]
247 pub fn section_empty(&self, section_y: i32) -> Option<bool> {
248 let index = self.chunk_section_index(section_y)?;
249 self.emptiness_map
250 .as_deref()
251 .and_then(|emptiness_map| emptiness_map.get(index).copied())
252 }
253
254 #[must_use]
256 pub(crate) fn highest_non_empty_section_y(&self) -> Option<i32> {
257 let emptiness_map = self.emptiness_map.as_deref()?;
258 for (index, empty) in emptiness_map.iter().copied().enumerate().rev() {
259 if !empty {
260 return self.range.chunk_section_y(index);
261 }
262 }
263
264 None
265 }
266
267 pub fn set_emptiness_map(
269 &mut self,
270 emptiness_map: Box<[bool]>,
271 ) -> Result<(), ChunkLightEmptinessMapLengthError> {
272 let actual = emptiness_map.len();
273 if actual != self.chunk_section_count {
274 return Err(ChunkLightEmptinessMapLengthError {
275 expected: self.chunk_section_count,
276 actual,
277 });
278 }
279
280 self.emptiness_map = Some(emptiness_map);
281 Ok(())
282 }
283
284 pub fn refresh_emptiness_map_from_sections(
286 &mut self,
287 sections: &Sections,
288 ) -> Result<(), ChunkLightEmptinessMapLengthError> {
289 self.set_emptiness_map(sections.section_emptiness_map())
290 }
291
292 pub fn set_section_empty(&mut self, section_y: i32, empty: bool) -> Option<bool> {
294 let index = self.chunk_section_index(section_y)?;
295 let emptiness_map = self.emptiness_map.as_deref_mut()?;
296 let previous = *emptiness_map.get(index)?;
297 emptiness_map[index] = empty;
298 Some(previous)
299 }
300
301 pub(crate) fn fill_loaded_missing_sky_sections_below_data_with_zero(&mut self) {
303 if self.layer != LightLayer::Sky {
304 return;
305 }
306
307 let mut below_loaded_data = false;
308 for section in self.sections.iter_mut().rev() {
309 if section.is_present() {
310 below_loaded_data = true;
311 continue;
312 }
313
314 if below_loaded_data {
315 *section = LightSection::visible(LightSectionData::homogeneous(0));
316 }
317 }
318 }
319
320 #[must_use]
322 pub fn get_light_value(&self, block_pos: BlockPos) -> u8 {
323 match self.layer {
324 LightLayer::Sky => self.get_sky_light_value(block_pos),
325 LightLayer::Block => self.get_block_light_value(block_pos),
326 }
327 }
328
329 fn get_block_light_value(&self, block_pos: BlockPos) -> u8 {
330 self.visible_section_value(block_pos).unwrap_or(0)
331 }
332
333 fn get_sky_light_value(&self, block_pos: BlockPos) -> u8 {
334 if let Some(value) = self.visible_section_value(block_pos) {
335 return value;
336 }
337
338 let section_y = SectionPos::block_to_section_coord(block_pos.y());
339 let Some(highest_non_empty_section_y) = self.highest_non_empty_section_y() else {
340 return MAX_LIGHT_LEVEL;
341 };
342 if section_y > highest_non_empty_section_y {
343 return MAX_LIGHT_LEVEL;
344 }
345
346 let local_x = section_relative_coord(block_pos.x());
347 let local_z = section_relative_coord(block_pos.z());
348 let mut search_section_y = section_y.saturating_add(1).max(self.range.min_section_y());
349 while search_section_y < self.range.max_section_y_exclusive() {
350 if let Some(section) = self.section(search_section_y)
351 && let Some(data) = section.visible_data()
352 {
353 return data.get(local_x, 0, local_z);
354 }
355 search_section_y += 1;
356 }
357
358 MAX_LIGHT_LEVEL
359 }
360
361 fn visible_section_value(&self, block_pos: BlockPos) -> Option<u8> {
362 let section_y = SectionPos::block_to_section_coord(block_pos.y());
363 let section = self.section(section_y)?;
364 let data = section.visible_data()?;
365
366 Some(data.get(
367 section_relative_coord(block_pos.x()),
368 section_relative_coord(block_pos.y()),
369 section_relative_coord(block_pos.z()),
370 ))
371 }
372
373 fn chunk_section_index(&self, section_y: i32) -> Option<usize> {
374 let index = self.range.chunk_section_index(section_y)?;
375 (index < self.chunk_section_count).then_some(index)
376 }
377}
378
379const fn section_relative_coord(block_coord: i32) -> usize {
380 (block_coord & 15) as usize
381}
382
383#[derive(Debug)]
385pub struct ChunkLightData {
386 pub block: ChunkLightLayerStorage,
388 pub sky: ChunkLightLayerStorage,
390}
391
392impl ChunkLightData {
393 pub fn new(min_y: i32, height: i32) -> Result<Self, LightSectionRangeError> {
395 let range = LightSectionRange::from_world_height(min_y, height)?;
396 Ok(Self {
397 block: ChunkLightLayerStorage::new(
398 LightLayer::Block,
399 range,
400 range.chunk_section_count(),
401 ),
402 sky: ChunkLightLayerStorage::new(LightLayer::Sky, range, range.chunk_section_count()),
403 })
404 }
405
406 #[must_use]
415 pub fn for_valid_world_height(min_y: i32, height: i32) -> Self {
416 match Self::new(min_y, height) {
417 Ok(data) => data,
418 Err(error) => panic!("invalid world height for chunk light data: {error:?}"),
419 }
420 }
421
422 pub fn refresh_emptiness_maps_from_sections(
424 &mut self,
425 sections: &Sections,
426 ) -> Result<(), ChunkLightEmptinessMapLengthError> {
427 self.block.refresh_emptiness_map_from_sections(sections)?;
428 self.sky.refresh_emptiness_map_from_sections(sections)
429 }
430
431 pub fn set_section_empty(&mut self, section_y: i32, empty: bool) -> bool {
433 let block_changed = self
434 .block
435 .set_section_empty(section_y, empty)
436 .is_some_and(|previous| previous != empty);
437 let sky_changed = self
438 .sky
439 .set_section_empty(section_y, empty)
440 .is_some_and(|previous| previous != empty);
441 block_changed || sky_changed
442 }
443
444 #[must_use]
446 pub fn get_light_value(&self, layer: LightLayer, block_pos: BlockPos) -> u8 {
447 match layer {
448 LightLayer::Sky => self.sky.get_light_value(block_pos),
449 LightLayer::Block => self.block.get_light_value(block_pos),
450 }
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 #[test]
459 fn sky_light_below_range_starts_search_at_min_light_section() {
460 let Ok(range) = LightSectionRange::from_world_height(0, 16) else {
461 panic!("valid test height should create a light section range");
462 };
463 let mut storage =
464 ChunkLightLayerStorage::new(LightLayer::Sky, range, range.chunk_section_count());
465 let Some(section) = storage.section_mut(range.min_section_y()) else {
466 panic!("test range should include its minimum light section");
467 };
468 *section = LightSection::visible(LightSectionData::homogeneous(7));
469 let Ok(()) = storage.set_emptiness_map(vec![false; range.chunk_section_count()].into())
470 else {
471 panic!("test emptiness map should match the range");
472 };
473
474 assert_eq!(storage.get_light_value(BlockPos::new(0, i32::MIN, 0)), 7);
475 }
476}