Skip to main content

voxel_light/
lib.rs

1//! Generic 3D BFS light propagation with two-phase removal for voxel engines.
2//!
3//! Implement [`VoxelAccess`] for your storage, call [`propagate`] / [`remove`],
4//! and apply the returned [`LightUpdate`]s. The library never mutates your data.
5//!
6//! For repeated operations, use [`LightEngine`] which reuses internal buffers.
7
8#![cfg_attr(not(feature = "std"), no_std)]
9
10extern crate alloc;
11
12mod propagate;
13mod remove;
14#[cfg(feature = "sky")]
15pub mod sky;
16#[cfg(feature = "colored")]
17pub mod colored;
18mod traits;
19mod types;
20
21pub use propagate::propagate;
22pub use remove::remove;
23pub use traits::{VoxelAccess, VoxelInfo};
24pub use types::LightUpdate;
25
26pub struct LightEngine {
27    propagation_queue: alloc::collections::VecDeque<types::LightNode>,
28    removal_queue: alloc::collections::VecDeque<types::RemovalNode>,
29}
30
31impl LightEngine {
32    pub fn new() -> Self {
33        Self {
34            propagation_queue: alloc::collections::VecDeque::with_capacity(512),
35            removal_queue: alloc::collections::VecDeque::with_capacity(512),
36        }
37    }
38
39    pub fn place_light(
40        &mut self,
41        access: &impl VoxelAccess,
42        pos: [i32; 3],
43        level: u8,
44    ) -> alloc::vec::Vec<LightUpdate> {
45        propagate::propagate_reuse(access, pos, level, &mut self.propagation_queue)
46    }
47
48    pub fn remove_light(
49        &mut self,
50        access: &impl VoxelAccess,
51        pos: [i32; 3],
52    ) -> alloc::vec::Vec<LightUpdate> {
53        remove::remove_reuse(
54            access,
55            pos,
56            &mut self.removal_queue,
57            &mut self.propagation_queue,
58        )
59    }
60
61    pub fn block_placed(
62        &mut self,
63        access: &impl VoxelAccess,
64        pos: [i32; 3],
65    ) -> alloc::vec::Vec<LightUpdate> {
66        remove::block_placed(access, pos, &mut self.removal_queue, &mut self.propagation_queue)
67    }
68
69    pub fn block_removed(
70        &mut self,
71        access: &impl VoxelAccess,
72        pos: [i32; 3],
73    ) -> alloc::vec::Vec<LightUpdate> {
74        propagate::block_removed(access, pos, &mut self.propagation_queue)
75    }
76
77    /// Clear all light in a cubic area and re-propagate from any emitters found.
78    ///
79    /// Use when an opaque block is placed and you need to recalculate the area
80    /// rather than tracing individual affected paths.
81    pub fn recalculate_area(
82        &mut self,
83        access: &impl VoxelAccess,
84        center: [i32; 3],
85        radius: i32,
86    ) -> alloc::vec::Vec<LightUpdate> {
87        use alloc::vec::Vec;
88
89        let mut updates = Vec::new();
90        let mut emitters: Vec<([i32; 3], u8)> = Vec::new();
91
92        // Phase 1: clear all light, collect emitters
93        for z in (center[2] - radius)..=(center[2] + radius) {
94            for y in (center[1] - radius)..=(center[1] + radius) {
95                for x in (center[0] - radius)..=(center[0] + radius) {
96                    if let Some(voxel) = access.get_voxel(x, y, z) {
97                        if voxel.emission > 0 {
98                            emitters.push(([x, y, z], voxel.emission));
99                        }
100                        if voxel.block_light > 0 {
101                            updates.push(LightUpdate::new([x, y, z], 0));
102                        }
103                    }
104                }
105            }
106        }
107
108        // Phase 2: re-propagate from each emitter against cleared state
109        let mut reprop_updates = Vec::new();
110        {
111            let cleared = ClearedOverlay { inner: access, clears: &updates };
112            for (pos, emission) in emitters {
113                let prop = propagate::propagate_reuse(
114                    &cleared,
115                    pos,
116                    emission,
117                    &mut self.propagation_queue,
118                );
119                reprop_updates.extend(prop);
120            }
121        }
122
123        updates.extend(reprop_updates);
124        updates
125    }
126}
127
128impl Default for LightEngine {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134struct ClearedOverlay<'a, A: VoxelAccess> {
135    inner: &'a A,
136    clears: &'a [LightUpdate],
137}
138
139impl<A: VoxelAccess> VoxelAccess for ClearedOverlay<'_, A> {
140    fn get_voxel(&self, x: i32, y: i32, z: i32) -> Option<VoxelInfo> {
141        let mut voxel = self.inner.get_voxel(x, y, z)?;
142        if self.clears.iter().any(|u| u.x == x && u.y == y && u.z == z) {
143            voxel.block_light = 0;
144        }
145        Some(voxel)
146    }
147}