Skip to main content

steel_core/chunk/light/
section_storage.rs

1use steel_utils::{SectionPos, codec::BitSet};
2
3use super::LIGHT_SECTION_PADDING;
4
5/// Error returned when a world height cannot produce a valid light-section range.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct LightSectionRangeError {
8    /// Minimum build height used to create the range.
9    pub min_y: i32,
10    /// Build height used to create the range.
11    pub height: i32,
12}
13
14/// Inclusive/exclusive vertical range of light sections for a level.
15///
16/// Vanilla's `LevelLightEngine` exposes this as `getMinLightSection()`,
17/// `getMaxLightSection()`, and `getLightSectionCount()`. The range is padded by
18/// one section below and one section above the real chunk sections.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct LightSectionRange {
21    min_section_y: i32,
22    section_count: i32,
23}
24
25impl LightSectionRange {
26    /// Creates the vanilla padded light-section range for a world height.
27    pub const fn from_world_height(
28        min_y: i32,
29        height: i32,
30    ) -> Result<Self, LightSectionRangeError> {
31        if height <= 0 {
32            return Err(LightSectionRangeError { min_y, height });
33        }
34
35        let Some(max_y) = min_y.checked_add(height - 1) else {
36            return Err(LightSectionRangeError { min_y, height });
37        };
38
39        let min_chunk_section_y = SectionPos::block_to_section_coord(min_y);
40        let max_chunk_section_y = SectionPos::block_to_section_coord(max_y);
41        let section_count =
42            max_chunk_section_y - min_chunk_section_y + 1 + LIGHT_SECTION_PADDING * 2;
43
44        Ok(Self {
45            min_section_y: min_chunk_section_y - LIGHT_SECTION_PADDING,
46            section_count,
47        })
48    }
49
50    /// Returns the first light section Y coordinate.
51    #[must_use]
52    pub const fn min_section_y(self) -> i32 {
53        self.min_section_y
54    }
55
56    /// Returns the section Y coordinate one past the last light section.
57    #[must_use]
58    pub const fn max_section_y_exclusive(self) -> i32 {
59        self.min_section_y + self.section_count
60    }
61
62    /// Returns the number of light sections in this range.
63    #[must_use]
64    pub const fn section_count(self) -> usize {
65        self.section_count as usize
66    }
67
68    /// Returns the first real chunk section Y coordinate.
69    #[must_use]
70    pub const fn min_chunk_section_y(self) -> i32 {
71        self.min_section_y + LIGHT_SECTION_PADDING
72    }
73
74    /// Returns the real chunk section Y coordinate one past the last section.
75    #[must_use]
76    pub const fn max_chunk_section_y_exclusive(self) -> i32 {
77        self.max_section_y_exclusive() - LIGHT_SECTION_PADDING
78    }
79
80    /// Returns the number of real chunk sections inside this light range.
81    #[must_use]
82    pub const fn chunk_section_count(self) -> usize {
83        (self.section_count - LIGHT_SECTION_PADDING * 2) as usize
84    }
85
86    /// Converts a packet light-section index to a section Y coordinate.
87    #[must_use]
88    pub const fn section_y(self, section_index: usize) -> Option<i32> {
89        if section_index >= self.section_count() {
90            return None;
91        }
92
93        Some(self.min_section_y + section_index as i32)
94    }
95
96    /// Converts a section Y coordinate to a packet light-section index.
97    #[must_use]
98    pub const fn section_index(self, section_y: i32) -> Option<usize> {
99        if section_y < self.min_section_y || section_y >= self.max_section_y_exclusive() {
100            return None;
101        }
102
103        Some((section_y - self.min_section_y) as usize)
104    }
105
106    /// Converts a real chunk-section index to a section Y coordinate.
107    #[must_use]
108    pub const fn chunk_section_y(self, section_index: usize) -> Option<i32> {
109        if section_index >= self.chunk_section_count() {
110            return None;
111        }
112
113        Some(self.min_chunk_section_y() + section_index as i32)
114    }
115
116    /// Converts a real chunk section Y coordinate to an emptiness-map index.
117    #[must_use]
118    pub const fn chunk_section_index(self, section_y: i32) -> Option<usize> {
119        if section_y < self.min_chunk_section_y()
120            || section_y >= self.max_chunk_section_y_exclusive()
121        {
122            return None;
123        }
124
125        Some((section_y - self.min_chunk_section_y()) as usize)
126    }
127
128    #[must_use]
129    pub(super) fn empty_bit_set(self) -> BitSet {
130        BitSet(vec![0; self.section_count().div_ceil(64)].into_boxed_slice())
131    }
132}