1use super::{
2 BlockPos, BlockStateExt, ChunkPos, Direction, LIGHT_BLOCKED, LightAxisDirection,
3 LightDirectionSet, LightQueueFlags, MAX_LIGHT_LEVEL, PackedLightQueueEntry, SectionPos,
4 SkyLightChunkEdgeChecks, SkyLightPropagationContext, get_light_block_into, get_light_opacity,
5};
6
7impl SkyLightPropagationContext<'_, '_, '_> {
8 pub fn light_chunk(&mut self, chunk_pos: ChunkPos, edge_checks: SkyLightChunkEdgeChecks) {
10 self.light.rewrite_missing_sections_for_skylight();
11 self.missing_section_checked.fill(false);
12
13 let min_section = self.layout.range().min_chunk_section_y();
14 let mut highest_non_empty_section = self.layout.range().max_chunk_section_y_exclusive() - 1;
15
16 loop {
17 let section_pos =
18 SectionPos::new(chunk_pos.0.x, highest_non_empty_section, chunk_pos.0.y);
19 if highest_non_empty_section != min_section - 1
20 && self.sections.has_non_empty_section(section_pos)
21 {
22 break;
23 }
24
25 self.check_missing_section(chunk_pos, highest_non_empty_section, false);
26 self.propagate_full_empty_section_edges(chunk_pos, highest_non_empty_section);
27
28 if highest_non_empty_section == min_section - 1 {
29 highest_non_empty_section -= 1;
30 break;
31 }
32 highest_non_empty_section -= 1;
33 }
34
35 if highest_non_empty_section >= min_section {
36 self.propagate_sky_sources_from_top(chunk_pos, highest_non_empty_section);
37 }
38
39 match edge_checks {
40 SkyLightChunkEdgeChecks::Required => {
41 self.perform_light_increase();
42 for section_y in
43 (self.layout.range().min_section_y()..=highest_non_empty_section).rev()
44 {
45 self.check_missing_section(chunk_pos, section_y, false);
46 }
47 self.check_chunk_edges(
48 chunk_pos,
49 self.layout.range().min_section_y(),
50 highest_non_empty_section,
51 );
52 }
53 SkyLightChunkEdgeChecks::Skipped => {
54 for section_y in
55 (self.layout.range().min_section_y()..=highest_non_empty_section).rev()
56 {
57 self.check_missing_section(chunk_pos, section_y, false);
58 }
59 self.propagate_neighbor_levels(
60 chunk_pos,
61 self.layout.range().min_section_y(),
62 highest_non_empty_section,
63 );
64 self.perform_light_increase();
65 }
66 }
67 }
68
69 pub fn check_block(&mut self, block_pos: BlockPos) -> bool {
71 let Some(cached_block) = self.layout.cached_block(block_pos) else {
72 return false;
73 };
74
75 let current_level = self.light.get(cached_block);
76 if current_level == MAX_LIGHT_LEVEL {
77 self.enqueue_increase(
78 block_pos,
79 current_level,
80 LightDirectionSet::all(),
81 LightQueueFlags::EMPTY.with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS),
82 );
83 } else {
84 self.light.set(cached_block, 0);
85 }
86
87 self.enqueue_decrease(
88 block_pos,
89 current_level,
90 LightDirectionSet::all(),
91 LightQueueFlags::EMPTY,
92 );
93 true
94 }
95
96 pub fn propagate_block_changes(&mut self, positions: &[BlockPos]) {
98 self.light.rewrite_missing_sections_for_skylight();
99 self.missing_section_checked.fill(false);
100
101 let chunk_pos = self.layout.center_chunk();
102 self.initialize_changed_sections(chunk_pos, positions);
103
104 let mut changed_column_max_y = [i32::MIN; 16 * 16];
105 for position in positions {
106 if SectionPos::block_to_section_coord(position.x()) != chunk_pos.0.x
107 || SectionPos::block_to_section_coord(position.z()) != chunk_pos.0.y
108 {
109 continue;
110 }
111
112 let index = ((position.x() & 15) | ((position.z() & 15) << 4)) as usize;
113 changed_column_max_y[index] = changed_column_max_y[index].max(position.y());
114 }
115
116 let mut delayed_increases = Vec::new();
117 let mut delayed_decreases = Vec::new();
118 for (index, max_y) in changed_column_max_y.into_iter().enumerate() {
119 if max_y == i32::MIN {
120 continue;
121 }
122
123 let x = (chunk_pos.0.x << 4) | (index as i32 & 15);
124 let z = (chunk_pos.0.y << 4) | ((index as i32 >> 4) & 15);
125 let max_propagation_y =
126 self.try_propagate_skylight_delayed(x, max_y, z, true, &mut delayed_increases);
127 self.remove_sky_sources_below(x, max_propagation_y, z, &mut delayed_decreases);
128 }
129
130 self.process_delayed_increases(&delayed_increases);
131 self.process_delayed_decreases(&delayed_decreases);
132
133 for position in positions {
134 self.check_block(*position);
135 }
136
137 self.perform_light_decrease();
138 }
139
140 #[must_use]
142 pub fn calculate_light_value(&self, block_pos: BlockPos, expect: u8) -> Option<u8> {
143 if expect == MAX_LIGHT_LEVEL {
144 return Some(expect);
145 }
146
147 let cached_block = self.layout.cached_block(block_pos)?;
148 let center_state = self.sections.get_block_state(cached_block);
149 let opacity = get_light_opacity(center_state);
150 let mut level = 0;
151
152 for axis_direction in LightAxisDirection::ALL {
153 let neighbor_pos = Self::offset(block_pos, axis_direction);
154 let Some(neighbor_block) = self.layout.cached_block(neighbor_pos) else {
155 continue;
156 };
157 let neighbor_level = self.light.get(neighbor_block);
158 if neighbor_level.saturating_sub(1) <= level {
159 continue;
160 }
161
162 let neighbor_state = self.sections.get_block_state(neighbor_block);
163 if get_light_block_into(
164 neighbor_state,
165 center_state,
166 axis_direction.opposite().direction(),
167 opacity,
168 ) == LIGHT_BLOCKED
169 {
170 continue;
171 }
172
173 level = level.max(neighbor_level.saturating_sub(opacity));
174 if level > expect {
175 return Some(level);
176 }
177 }
178
179 Some(level)
180 }
181
182 pub(super) fn init_light_section(
183 &mut self,
184 section_pos: SectionPos,
185 extrude: bool,
186 init_removed: bool,
187 ) {
188 if self.layout.section_slot(section_pos).is_none()
189 || (!self.light.has_cached_section(section_pos)
190 && (!init_removed || !self.light.materialize_removed_missing_section(section_pos)))
191 {
192 return;
193 }
194 if !self.light.is_section_missing(section_pos) {
195 return;
196 }
197
198 let mut highest_non_empty_section = self.layout.range().min_section_y() - 1;
199 for section_y in (self.layout.range().min_chunk_section_y()
200 ..self.layout.range().max_chunk_section_y_exclusive())
201 .rev()
202 {
203 let candidate = SectionPos::new(section_pos.x(), section_y, section_pos.z());
204 if self.section_is_non_empty(candidate) {
205 highest_non_empty_section = section_y;
206 break;
207 }
208 }
209
210 if section_pos.y() > highest_non_empty_section {
211 self.light.set_section_non_missing(section_pos);
212 self.light.fill_section(section_pos, MAX_LIGHT_LEVEL);
213 } else if extrude {
214 self.light
215 .extrude_lower_from_first_section_above(section_pos);
216 } else {
217 self.light.set_section_non_missing(section_pos);
218 }
219 }
220
221 pub(super) fn section_is_non_empty(&self, section_pos: SectionPos) -> bool {
222 if let Some(empty) = self.sections.section_empty(section_pos) {
223 return !empty;
224 }
225
226 if let Some(empty) = self.light.section_empty(section_pos) {
227 return !empty;
228 }
229
230 self.sections.has_non_empty_section(section_pos)
231 }
232
233 pub(super) fn check_missing_section(
234 &mut self,
235 chunk_pos: ChunkPos,
236 section_y: i32,
237 extrude_initialized: bool,
238 ) -> bool {
239 let Some(section_index) = self.layout.range().section_index(section_y) else {
240 return false;
241 };
242 if self.missing_section_checked[section_index] {
243 return false;
244 }
245 self.missing_section_checked[section_index] = true;
246
247 let center_section_pos = SectionPos::new(chunk_pos.0.x, section_y, chunk_pos.0.y);
248 let mut need_init_neighbors = self.light.has_non_missing_section(center_section_pos);
249 if !need_init_neighbors {
250 'neighbor_search: for offset_z in -1..=1 {
251 for offset_x in -1..=1 {
252 let section_pos = SectionPos::new(
253 chunk_pos.0.x + offset_x,
254 section_y,
255 chunk_pos.0.y + offset_z,
256 );
257 if self.light.has_non_missing_section(section_pos) {
258 need_init_neighbors = true;
259 break 'neighbor_search;
260 }
261 }
262 }
263 }
264
265 if need_init_neighbors {
266 for offset_z in -1..=1 {
267 for offset_x in -1..=1 {
268 self.init_light_section(
269 SectionPos::new(
270 chunk_pos.0.x + offset_x,
271 section_y,
272 chunk_pos.0.y + offset_z,
273 ),
274 if (offset_x | offset_z) == 0 {
275 extrude_initialized
276 } else {
277 true
278 },
279 true,
280 );
281 }
282 }
283 }
284
285 need_init_neighbors
286 }
287
288 fn propagate_full_empty_section_edges(&mut self, chunk_pos: ChunkPos, section_y: i32) {
289 for direction in LightAxisDirection::HORIZONTAL {
290 let (neighbor_offset_x, _, neighbor_offset_z) = direction.offset();
291 let neighbor_section_pos = SectionPos::new(
292 chunk_pos.0.x + neighbor_offset_x,
293 section_y,
294 chunk_pos.0.y + neighbor_offset_z,
295 );
296 if !self.light.has_non_missing_section(neighbor_section_pos) {
297 continue;
298 }
299
300 let (increment_x, increment_z, start_x, start_z) =
301 Self::current_edge_scan(chunk_pos, direction);
302 let directions = LightDirectionSet::only(direction);
303 let min_y = section_y << 4;
304 let max_y = min_y | 15;
305 for y in min_y..=max_y {
306 let mut x = start_x;
307 let mut z = start_z;
308 for _ in 0..16 {
309 self.enqueue_increase(
310 BlockPos::new(x, y, z),
311 MAX_LIGHT_LEVEL,
312 directions,
313 LightQueueFlags::EMPTY,
314 );
315 x += increment_x;
316 z += increment_z;
317 }
318 }
319 }
320 }
321
322 fn propagate_sky_sources_from_top(&mut self, chunk_pos: ChunkPos, highest_section: i32) {
323 let section_min_x = chunk_pos.0.x << 4;
324 let section_min_z = chunk_pos.0.y << 4;
325 let start_y = (highest_section << 4) | 15;
326
327 for z in 0..super::super::CHUNK_EDGE {
328 for x in 0..super::super::CHUNK_EDGE {
329 self.try_propagate_skylight_inner(
330 section_min_x + x as i32,
331 start_y + 1,
332 section_min_z + z as i32,
333 false,
334 None,
335 );
336 }
337 }
338 }
339
340 fn try_propagate_skylight_delayed(
341 &mut self,
342 x: i32,
343 y: i32,
344 z: i32,
345 extrude_initialized: bool,
346 delayed_increases: &mut Vec<PackedLightQueueEntry>,
347 ) -> i32 {
348 self.try_propagate_skylight_inner(x, y, z, extrude_initialized, Some(delayed_increases))
349 }
350
351 fn try_propagate_skylight_inner(
352 &mut self,
353 x: i32,
354 mut y: i32,
355 z: i32,
356 extrude_initialized: bool,
357 mut delayed_increases: Option<&mut Vec<PackedLightQueueEntry>>,
358 ) -> i32 {
359 if self.get_light_level_extruded(BlockPos::new(x, y + 1, z)) != MAX_LIGHT_LEVEL {
360 return y;
361 }
362
363 self.check_missing_section(
364 ChunkPos::new(
365 SectionPos::block_to_section_coord(x),
366 SectionPos::block_to_section_coord(z),
367 ),
368 SectionPos::block_to_section_coord(y),
369 extrude_initialized,
370 );
371
372 let mut above_state = self.block_state(BlockPos::new(x, y + 1, z));
373 while y >= (self.layout.range().min_section_y() << 4) {
374 if (y & 15) == 15 {
375 self.check_missing_section(
376 ChunkPos::new(
377 SectionPos::block_to_section_coord(x),
378 SectionPos::block_to_section_coord(z),
379 ),
380 SectionPos::block_to_section_coord(y),
381 extrude_initialized,
382 );
383 }
384
385 let current_pos = BlockPos::new(x, y, z);
386 let current_state = self.block_state(current_pos);
387 let opacity = current_state.get_light_dampening();
388 if get_light_block_into(above_state, current_state, Direction::Down, opacity)
389 == LIGHT_BLOCKED
390 || opacity > 0
391 {
392 break;
393 }
394
395 let section_pos = SectionPos::from_block_pos(current_pos);
396 if self.light.has_non_missing_section(section_pos) {
397 let Some(cached_block) = self.layout.cached_block(current_pos) else {
398 break;
399 };
400 let increase_entry = self.enqueue_increase(
401 current_pos,
402 MAX_LIGHT_LEVEL,
403 LightDirectionSet::all_except(LightAxisDirection::PositiveY),
404 Self::shape_flags(current_state),
405 );
406 above_state = current_state;
407
408 if let Some(delayed_increases) = delayed_increases.as_deref_mut() {
409 if let Some(entry) = increase_entry {
410 delayed_increases.push(entry);
411 }
412 } else {
413 self.light.set(cached_block, MAX_LIGHT_LEVEL);
414 }
415 } else {
416 y &= !15;
417 above_state = Self::air();
418 }
419
420 y -= 1;
421 }
422
423 y
424 }
425
426 fn initialize_changed_sections(&mut self, chunk_pos: ChunkPos, positions: &[BlockPos]) {
427 let mut section_ys = Vec::new();
428 for position in positions {
429 if SectionPos::block_to_section_coord(position.x()) != chunk_pos.0.x
430 || SectionPos::block_to_section_coord(position.z()) != chunk_pos.0.y
431 {
432 continue;
433 }
434
435 let section_y = SectionPos::block_to_section_coord(position.y());
436 if !section_ys.contains(§ion_y) {
437 section_ys.push(section_y);
438 }
439 }
440
441 for section_y in section_ys {
442 let section_pos = SectionPos::new(chunk_pos.0.x, section_y, chunk_pos.0.y);
443 if !self.sections.has_non_empty_section(section_pos) {
444 continue;
445 }
446
447 for offset_z in -1..=1 {
448 for offset_x in -1..=1 {
449 for offset_y in (-1..=1).rev() {
450 self.init_light_section(
451 SectionPos::new(
452 chunk_pos.0.x + offset_x,
453 section_y + offset_y,
454 chunk_pos.0.y + offset_z,
455 ),
456 true,
457 false,
458 );
459 }
460 }
461 }
462 }
463 }
464
465 fn remove_sky_sources_below(
466 &mut self,
467 x: i32,
468 mut y: i32,
469 z: i32,
470 delayed_decreases: &mut Vec<PackedLightQueueEntry>,
471 ) {
472 if self.get_light_level_extruded(BlockPos::new(x, y, z)) != MAX_LIGHT_LEVEL {
473 return;
474 }
475
476 let min_y = self.layout.range().min_section_y() << 4;
477 while y >= min_y {
478 if (y & 15) == 15 {
479 self.check_missing_section(
480 ChunkPos::new(
481 SectionPos::block_to_section_coord(x),
482 SectionPos::block_to_section_coord(z),
483 ),
484 SectionPos::block_to_section_coord(y),
485 true,
486 );
487 }
488
489 let current_pos = BlockPos::new(x, y, z);
490 let section_pos = SectionPos::from_block_pos(current_pos);
491 if !self.light.has_non_missing_section(section_pos) {
492 y &= !15;
493 y -= 1;
494 continue;
495 }
496
497 let Some(cached_block) = self.layout.cached_block(current_pos) else {
498 break;
499 };
500 if self.light.get(cached_block) != MAX_LIGHT_LEVEL {
501 break;
502 }
503
504 if let Some(entry) = self.enqueue_decrease(
505 current_pos,
506 MAX_LIGHT_LEVEL,
507 LightDirectionSet::all_except(LightAxisDirection::PositiveY),
508 LightQueueFlags::EMPTY,
509 ) {
510 delayed_decreases.push(entry);
511 }
512 y -= 1;
513 }
514 }
515}