use alloc::collections::VecDeque;
use alloc::vec::Vec;
use crate::traits::VoxelAccess;
use crate::types::{LightNode, LightUpdate, RemovalNode, DIRECTIONS};
#[cfg(feature = "std")]
type HashMap<K, V> = ahash::AHashMap<K, V>;
#[cfg(not(feature = "std"))]
type HashMap<K, V> = alloc::collections::BTreeMap<K, V>;
pub fn remove(access: &impl VoxelAccess, source: [i32; 3]) -> Vec<LightUpdate> {
let mut removal_queue = VecDeque::with_capacity(512);
let mut propagation_queue = VecDeque::with_capacity(512);
remove_reuse(access, source, &mut removal_queue, &mut propagation_queue)
}
pub(crate) fn remove_reuse(
access: &impl VoxelAccess,
source: [i32; 3],
removal_queue: &mut VecDeque<RemovalNode>,
propagation_queue: &mut VecDeque<LightNode>,
) -> Vec<LightUpdate> {
let Some(source_voxel) = access.get_voxel(source[0], source[1], source[2]) else {
return Vec::new();
};
let old_level = source_voxel.block_light;
if old_level == 0 {
return Vec::new();
}
let mut visited: HashMap<(i32, i32, i32), bool> = HashMap::default();
let mut updates: Vec<LightUpdate> = Vec::new();
let mut relight_sources: Vec<LightNode> = Vec::new();
removal_queue.clear();
removal_queue.push_back(RemovalNode {
x: source[0],
y: source[1],
z: source[2],
old_level,
});
visited.insert((source[0], source[1], source[2]), true);
updates.push(LightUpdate::new(source, 0));
while let Some(node) = removal_queue.pop_front() {
for dir in DIRECTIONS {
let nx = node.x + dir[0];
let ny = node.y + dir[1];
let nz = node.z + dir[2];
if visited.contains_key(&(nx, ny, nz)) {
continue;
}
let Some(voxel) = access.get_voxel(nx, ny, nz) else {
continue;
};
if !voxel.transparent {
continue;
}
let neighbor_light = voxel.block_light;
if neighbor_light != 0 && neighbor_light < node.old_level {
visited.insert((nx, ny, nz), true);
updates.push(LightUpdate::new([nx, ny, nz], 0));
removal_queue.push_back(RemovalNode {
x: nx,
y: ny,
z: nz,
old_level: neighbor_light,
});
} else if neighbor_light >= node.old_level {
visited.insert((nx, ny, nz), true);
relight_sources.push(LightNode {
x: nx,
y: ny,
z: nz,
level: neighbor_light,
});
}
}
}
for update in &updates {
if update.level == 0 {
if let Some(voxel) = access.get_voxel(update.x, update.y, update.z) {
if voxel.emission > 0 && (update.x != source[0] || update.y != source[1] || update.z != source[2]) {
relight_sources.push(LightNode {
x: update.x,
y: update.y,
z: update.z,
level: voxel.emission,
});
}
}
}
}
let zeroed: HashMap<(i32, i32, i32), u8> = updates
.iter()
.map(|u| ((u.x, u.y, u.z), 0u8))
.collect();
let overlay = OverlayAccess {
inner: access,
overlay: &zeroed,
};
propagation_queue.clear();
let mut prop_visited: HashMap<(i32, i32, i32), u8> = HashMap::default();
for src in &relight_sources {
if let Some(&existing) = prop_visited.get(&(src.x, src.y, src.z)) {
if existing >= src.level {
continue;
}
}
prop_visited.insert((src.x, src.y, src.z), src.level);
propagation_queue.push_back(LightNode {
x: src.x,
y: src.y,
z: src.z,
level: src.level,
});
}
while let Some(node) = propagation_queue.pop_front() {
let next_level = node.level.saturating_sub(1);
if next_level == 0 {
continue;
}
for dir in DIRECTIONS {
let nx = node.x + dir[0];
let ny = node.y + dir[1];
let nz = node.z + dir[2];
if let Some(&existing) = prop_visited.get(&(nx, ny, nz)) {
if existing >= next_level {
continue;
}
}
let Some(voxel) = overlay.get_voxel(nx, ny, nz) else {
continue;
};
if !voxel.transparent {
continue;
}
if next_level > voxel.block_light {
prop_visited.insert((nx, ny, nz), next_level);
updates.push(LightUpdate::new([nx, ny, nz], next_level));
propagation_queue.push_back(LightNode {
x: nx,
y: ny,
z: nz,
level: next_level,
});
}
}
}
updates
}
pub(crate) fn block_placed(
access: &impl VoxelAccess,
pos: [i32; 3],
removal_queue: &mut VecDeque<RemovalNode>,
propagation_queue: &mut VecDeque<LightNode>,
) -> Vec<LightUpdate> {
let Some(voxel) = access.get_voxel(pos[0], pos[1], pos[2]) else {
return Vec::new();
};
if voxel.block_light == 0 {
return Vec::new();
}
remove_reuse(access, pos, removal_queue, propagation_queue)
}
struct OverlayAccess<'a, A: VoxelAccess> {
inner: &'a A,
overlay: &'a HashMap<(i32, i32, i32), u8>,
}
impl<A: VoxelAccess> VoxelAccess for OverlayAccess<'_, A> {
fn get_voxel(&self, x: i32, y: i32, z: i32) -> Option<crate::traits::VoxelInfo> {
let mut voxel = self.inner.get_voxel(x, y, z)?;
if let Some(&level) = self.overlay.get(&(x, y, z)) {
voxel.block_light = level;
}
Some(voxel)
}
}