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