Skip to main content

steel_core/chunk/light/sky_propagation/
context.rs

1use super::{
2    ChunkPos, LightCacheLayout, LightLayer, LightLayerEdit, LightSectionReadCache,
3    PackedLightPropagationQueues, SectionPos, SkyLightPropagationContextError,
4};
5
6/// ScalableLux-style sky-light propagation over scoped Steel light caches.
7pub struct SkyLightPropagationContext<'a, 'sections, 'light> {
8    pub(super) layout: LightCacheLayout,
9    pub(super) sections: &'a LightSectionReadCache<'sections>,
10    pub(super) light: &'a mut LightLayerEdit<'light>,
11    pub(super) queues: &'a mut PackedLightPropagationQueues,
12    pub(super) missing_section_checked: Vec<bool>,
13}
14
15impl<'a, 'sections, 'light> SkyLightPropagationContext<'a, 'sections, 'light> {
16    /// Creates a sky-light propagation context from matching scoped caches.
17    pub fn new(
18        sections: &'a LightSectionReadCache<'sections>,
19        light: &'a mut LightLayerEdit<'light>,
20        queues: &'a mut PackedLightPropagationQueues,
21    ) -> Result<Self, SkyLightPropagationContextError> {
22        if light.layer() != LightLayer::Sky {
23            return Err(SkyLightPropagationContextError::WrongLayer {
24                layer: light.layer(),
25            });
26        }
27
28        if sections.layout() != light.layout() {
29            return Err(SkyLightPropagationContextError::layout_mismatch(
30                sections.layout(),
31                light.layout(),
32            ));
33        }
34
35        let layout = light.layout();
36        let section_count = layout.range().section_count();
37
38        Ok(Self {
39            layout,
40            sections,
41            light,
42            queues,
43            missing_section_checked: vec![false; section_count],
44        })
45    }
46
47    /// Initializes the sky sections required around non-empty center sections.
48    pub fn handle_unlit_empty_section_changes(&mut self, chunk_pos: ChunkPos) {
49        self.initialize_chunk_sections(chunk_pos, true);
50        self.deinit_and_lazy_init_empty_sections(chunk_pos, true);
51    }
52
53    /// Synchronizes sky sections for an already-lit loaded chunk without resetting light data.
54    pub fn handle_loaded_empty_section_changes(&mut self, chunk_pos: ChunkPos) {
55        self.initialize_chunk_sections(chunk_pos, false);
56        self.deinit_and_lazy_init_empty_sections(chunk_pos, false);
57    }
58
59    fn initialize_chunk_sections(&mut self, chunk_pos: ChunkPos, unlit: bool) {
60        for section_y in (self.layout.range().min_chunk_section_y()
61            ..self.layout.range().max_chunk_section_y_exclusive())
62            .rev()
63        {
64            let section_pos = SectionPos::new(chunk_pos.0.x, section_y, chunk_pos.0.y);
65            if !self.section_is_non_empty(section_pos) {
66                continue;
67            }
68
69            for offset_z in -1..=1 {
70                for offset_x in -1..=1 {
71                    let extrude = (offset_x | offset_z) != 0 || !unlit;
72                    for offset_y in (-1..=1).rev() {
73                        self.init_light_section(
74                            SectionPos::new(
75                                chunk_pos.0.x + offset_x,
76                                section_y + offset_y,
77                                chunk_pos.0.y + offset_z,
78                            ),
79                            extrude,
80                            false,
81                        );
82                    }
83                }
84            }
85        }
86    }
87
88    pub(super) fn deinit_and_lazy_init_empty_sections(&mut self, chunk_pos: ChunkPos, unlit: bool) {
89        for offset_z in -1..=1 {
90            for offset_x in -1..=1 {
91                let target_chunk =
92                    ChunkPos::new(chunk_pos.0.x + offset_x, chunk_pos.0.y + offset_z);
93
94                for section_y in (self.layout.range().min_section_y()
95                    ..self.layout.range().max_section_y_exclusive())
96                    .rev()
97                {
98                    let section_pos =
99                        SectionPos::new(target_chunk.0.x, section_y, target_chunk.0.y);
100                    match self.section_neighborhood_all_empty_if_known(target_chunk, section_y) {
101                        Some(true) => {
102                            self.light.set_section_missing(section_pos);
103                        }
104                        Some(false) => {
105                            self.init_light_section(
106                                section_pos,
107                                (offset_x | offset_z) != 0 || !unlit,
108                                false,
109                            );
110                        }
111                        None => {
112                            if !self.section_neighborhood_all_empty(target_chunk, section_y) {
113                                self.init_light_section(
114                                    section_pos,
115                                    (offset_x | offset_z) != 0 || !unlit,
116                                    false,
117                                );
118                            }
119                        }
120                    }
121                }
122            }
123        }
124    }
125
126    fn section_neighborhood_all_empty(&self, chunk_pos: ChunkPos, section_y: i32) -> bool {
127        for offset_y in -1..=1 {
128            let neighbor_y = section_y + offset_y;
129            if neighbor_y < self.layout.range().min_chunk_section_y()
130                || neighbor_y >= self.layout.range().max_chunk_section_y_exclusive()
131            {
132                continue;
133            }
134
135            for offset_z in -1..=1 {
136                for offset_x in -1..=1 {
137                    let section_pos = SectionPos::new(
138                        chunk_pos.0.x + offset_x,
139                        neighbor_y,
140                        chunk_pos.0.y + offset_z,
141                    );
142                    if let Some(empty) = self.sections.section_empty(section_pos) {
143                        if !empty {
144                            return false;
145                        }
146                    } else if let Some(empty) = self.light.section_empty(section_pos) {
147                        if !empty {
148                            return false;
149                        }
150                    } else if self.sections.has_non_empty_section(section_pos) {
151                        return false;
152                    }
153                }
154            }
155        }
156
157        true
158    }
159
160    fn section_neighborhood_all_empty_if_known(
161        &self,
162        chunk_pos: ChunkPos,
163        section_y: i32,
164    ) -> Option<bool> {
165        for offset_y in -1..=1 {
166            let neighbor_y = section_y + offset_y;
167            if neighbor_y < self.layout.range().min_chunk_section_y()
168                || neighbor_y >= self.layout.range().max_chunk_section_y_exclusive()
169            {
170                continue;
171            }
172
173            for offset_z in -1..=1 {
174                for offset_x in -1..=1 {
175                    let section_pos = SectionPos::new(
176                        chunk_pos.0.x + offset_x,
177                        neighbor_y,
178                        chunk_pos.0.y + offset_z,
179                    );
180                    let empty = self.sections.section_empty(section_pos)?;
181                    if !empty {
182                        return Some(false);
183                    }
184                }
185            }
186        }
187
188        Some(true)
189    }
190
191    /// Resets the center chunk to `ScalableLux`'s fresh all-missing lighting state.
192    pub fn reset_center_chunk_sections(&mut self) {
193        self.light
194            .reset_chunk_sections_to_missing(self.layout.center_chunk());
195    }
196}