From 488010ba19e582b77a6518b3d094fa9586a21f7a Mon Sep 17 00:00:00 2001 From: Imbris Date: Sun, 12 Jan 2020 00:50:58 -0500 Subject: [PATCH] Propagate light via queue to avoid block lookups --- voxygen/src/mesh/terrain.rs | 303 ++++++++++++++++++++++++------------ 1 file changed, 203 insertions(+), 100 deletions(-) diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index f279cff85c..c88541f5d6 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -5,9 +5,9 @@ use crate::{ use common::{ terrain::Block, vol::{ReadVol, RectRasterableVol, Vox}, - volumes::vol_grid_2d::VolGrid2d, + volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, }; -use std::fmt::Debug; +use std::{collections::VecDeque, fmt::Debug}; use vek::*; type TerrainVertex = ::Vertex; @@ -33,10 +33,12 @@ fn calc_light + ReadVol + Debug>( bounds: Aabb, vol: &VolGrid2d, ) -> impl Fn(Vec3) -> f32 { - const NOT_VOID: u8 = 255; + const UNKOWN: u8 = 255; + const OPAQUE: u8 = 254; const SUNLIGHT: u8 = 24; let outer = Aabb { + // TODO: subtract 1 from sunlight here min: bounds.min - Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1), max: bounds.max + Vec3::new(SUNLIGHT as i32, SUNLIGHT as i32, 1), }; @@ -45,109 +47,166 @@ fn calc_light + ReadVol + Debug>( // Voids are voxels that that contain air or liquid that are protected from direct rays by blocks // above them - // - let mut voids = vec![NOT_VOID; outer.size().product() as usize]; - let void_idx = { - let (_, h, d) = outer.clone().size().into_tuple(); - move |x, y, z| (x * h * d + y * d + z) as usize + let mut light_map = vec![UNKOWN; outer.size().product() as usize]; + // TODO: would a morton curve be more efficient? + let lm_idx = { + let (w, h, _) = outer.clone().size().into_tuple(); + move |x, y, z| (z * h * w + x * h + y) as usize }; - // List of voids for efficient iteration - let mut voids_list = vec![]; - // Rays are cast down - // Vec<(highest non air block, lowest non air block)> - let mut rays = vec![(outer.size().d, 0); (outer.size().w * outer.size().h) as usize]; + // Light propagation queue + let mut prop_que = VecDeque::new(); + // Start rays + // TODO: how much would it cost to clone the whole sample into a flat array? for x in 0..outer.size().w { for y in 0..outer.size().h { - let mut outside = true; - for z in (0..outer.size().d).rev() { - let block = vol_cached - .get(outer.min + Vec3::new(x, y, z)) + let z = outer.size().d - 1; + let is_air = vol_cached + .get(outer.min + Vec3::new(x, y, z)) + .ok() + .map_or(false, |b| b.is_air()); + + light_map[lm_idx(x, y, z)] = if is_air { + if vol_cached + .get(outer.min + Vec3::new(x, y, z - 1)) .ok() - .copied() - .unwrap_or(Block::empty()); - - if !block.is_air() { - if outside { - rays[(outer.size().w * y + x) as usize].0 = z; - outside = false; - } - rays[(outer.size().w * y + x) as usize].1 = z; + .map_or(false, |b| b.is_air()) + { + light_map[lm_idx(x, y, z - 1)] = SUNLIGHT; + // TODO: access efficiency of using less space to store pos + prop_que.push_back(Vec3::new(x, y, z - 1)); } - - if (block.is_air() || block.is_fluid()) && !outside { - voids_list.push(Vec3::new(x, y, z)); - voids[void_idx(x, y, z)] = 0; - } - } + SUNLIGHT + } else { + OPAQUE + }; } } - // Propagate light into voids adjacent to rays - let mut opens = Vec::new(); - 'voids: for pos in &mut voids_list { - let void_idx = void_idx(pos.x, pos.y, pos.z); - for dir in &DIRS { - let col = Vec2::::from(*pos) + dir; - // If above highest non air block (ray passes by) - if pos.z - > *rays - .get(((outer.size().w * col.y) + col.x) as usize) - .map(|(ray, _)| ray) - .unwrap_or(&0) - { - voids[void_idx] = SUNLIGHT - 1; - opens.push(*pos); - continue 'voids; - } - } - - // Ray hits directly (occurs for liquids) - if pos.z - >= *rays - .get(((outer.size().w * pos.y) + pos.x) as usize) - .map(|(ray, _)| ray) - .unwrap_or(&0) - { - voids[void_idx] = SUNLIGHT - 1; - opens.push(*pos); - } - } - - while opens.len() > 0 { - let mut new_opens = Vec::new(); - for open in &opens { - let parent_l = voids[void_idx(open.x, open.y, open.z)]; - for dir in &DIRS_3D { - let other = *open + *dir; - if let Some(l) = voids.get_mut(void_idx(other.x, other.y, other.z)) { - if *l < parent_l - 1 { - new_opens.push(other); - *l = parent_l - 1; + // Determines light propagation + let propagate = |src: u8, + dest: &mut u8, + pos: Vec3, + prop_que: &mut VecDeque<_>, + vol: &mut CachedVolGrid2d| { + if *dest != OPAQUE { + if *dest == UNKOWN { + if vol + .get(outer.min + pos) + .ok() + .map_or(false, |b| b.is_air() || b.is_fluid()) + { + *dest = src - 1; + // Can't propagate further + if *dest > 1 { + prop_que.push_back(pos); } + } else { + *dest = OPAQUE; + } + } else if *dest < src - 1 { + *dest = src - 1; + // Can't propagate further + if *dest > 1 { + prop_que.push_back(pos); } } } - opens = new_opens; + }; + + // Propage light + while let Some(pos) = prop_que.pop_front() { + // TODO: access efficiency of storing current light level in queue + // TODO: access efficiency of storing originating direction index in queue so that dir + // doesn't need to be checked + let light = light_map[lm_idx(pos.x, pos.y, pos.z)]; + + // If ray propagate downwards at full strength + if light == SUNLIGHT { + // Down is special cased and we know up is a ray + // Special cased ray propagation + let pos = Vec3::new(pos.x, pos.y, pos.z - 1); + let (is_air, is_fluid) = vol_cached + .get(outer.min + pos) + .ok() + .map_or((false, false), |b| (b.is_air(), b.is_fluid())); + light_map[lm_idx(pos.x, pos.y, pos.z)] = if is_air { + prop_que.push_back(pos); + SUNLIGHT + } else if is_fluid { + prop_que.push_back(pos); + SUNLIGHT - 1 + } else { + OPAQUE + } + } else { + // Up + // Bounds checking + // TODO: check if propagated light level can ever reach area of interest + if pos.z + 1 < outer.size().d { + propagate( + light, + light_map.get_mut(lm_idx(pos.x, pos.y, pos.z + 1)).unwrap(), + Vec3::new(pos.x, pos.y, pos.z + 1), + &mut prop_que, + &mut vol_cached, + ) + } + // Down + if pos.z > 0 { + propagate( + light, + light_map.get_mut(lm_idx(pos.x, pos.y, pos.z - 1)).unwrap(), + Vec3::new(pos.x, pos.y, pos.z - 1), + &mut prop_que, + &mut vol_cached, + ) + } + } + // The XY directions + if pos.y + 1 < outer.size().h { + propagate( + light, + light_map.get_mut(lm_idx(pos.x, pos.y + 1, pos.z)).unwrap(), + Vec3::new(pos.x, pos.y + 1, pos.z), + &mut prop_que, + &mut vol_cached, + ) + } + if pos.y > 0 { + propagate( + light, + light_map.get_mut(lm_idx(pos.x, pos.y - 1, pos.z)).unwrap(), + Vec3::new(pos.x, pos.y - 1, pos.z), + &mut prop_que, + &mut vol_cached, + ) + } + if pos.x + 1 < outer.size().w { + propagate( + light, + light_map.get_mut(lm_idx(pos.x + 1, pos.y, pos.z)).unwrap(), + Vec3::new(pos.x + 1, pos.y, pos.z), + &mut prop_que, + &mut vol_cached, + ) + } + if pos.x > 0 { + propagate( + light, + light_map.get_mut(lm_idx(pos.x - 1, pos.y, pos.z)).unwrap(), + Vec3::new(pos.x - 1, pos.y, pos.z), + &mut prop_que, + &mut vol_cached, + ) + } } move |wpos| { let pos = wpos - outer.min; - rays.get(((outer.size().w * pos.y) + pos.x) as usize) - .and_then(|(ray, deep)| { - if pos.z > *ray { - Some(1.0) - } else if pos.z < *deep { - Some(0.0) - } else { - None - } - }) - .or_else(|| { - voids - .get(void_idx(pos.x, pos.y, pos.z)) - .filter(|l| **l != NOT_VOID) - .map(|l| *l as f32 / SUNLIGHT as f32) - }) + light_map + .get(lm_idx(pos.x, pos.y, pos.z)) + .filter(|l| **l != OPAQUE && **l != UNKOWN) + .map(|l| *l as f32 / SUNLIGHT as f32) .unwrap_or(0.0) } } @@ -168,16 +227,46 @@ impl + ReadVol + Debug> Meshable b, + None => panic!("x {} y {} z {} d {} h {}"), + } + } + }; + + for x in 1..range.size().w - 1 { + for y in 1..range.size().w - 1 { let mut lights = [[[0.0; 3]; 3]; 3]; for i in 0..3 { for j in 0..3 { for k in 0..3 { lights[k][j][i] = light( - Vec3::new(x, y, range.min.z) + Vec3::new(x + range.min.x, y + range.min.y, range.min.z) + Vec3::new(i as i32, j as i32, k as i32) - 1, ); @@ -198,23 +287,23 @@ impl + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable + ReadVol + Debug> Meshable for VolGrid3d { } } */ + +fn interleave_i32_with_zeros(mut x: i32) -> i64 { + x = (x ^ (x << 16)) & 0x0000ffff0000ffff; + x = (x ^ (x << 8)) & 0x00ff00ff00ff00ff; + x = (x ^ (x << 4)) & 0x0f0f0f0f0f0f0f0f; + x = (x ^ (x << 2)) & 0x3333333333333333; + x = (x ^ (x << 1)) & 0x5555555555555555; + x +} + +fn morton_code(pos: Vec2) -> i64 { + interleave_i32_with_zeros(pos.x) | (interleave_i32_with_zeros(pos.y) << 1) +}