Address many of Imbris's comments on MR 2301.

This commit is contained in:
Avi Weinstock
2021-06-01 18:39:15 -04:00
parent 5aa98d18e3
commit 9c4453508e
3 changed files with 196 additions and 187 deletions

View File

@ -34,11 +34,11 @@ struct MinimapColumn {
/// Coordinate of lowest z-slice /// Coordinate of lowest z-slice
zlo: i32, zlo: i32,
/// Z-slices of colors and filled-ness /// Z-slices of colors and filled-ness
layers: Vec<Grid<(Vec4<u8>, bool)>>, layers: Vec<Grid<(Rgba<u8>, bool)>>,
/// Color and filledness above the highest layer /// Color and filledness above the highest layer
above: (Vec4<u8>, bool), above: (Rgba<u8>, bool),
/// Color and filledness below the lowest layer /// Color and filledness below the lowest layer
below: (Vec4<u8>, bool), below: (Rgba<u8>, bool),
} }
pub struct VoxelMinimap { pub struct VoxelMinimap {
@ -53,14 +53,14 @@ pub struct VoxelMinimap {
} }
const VOXEL_MINIMAP_SIDELENGTH: u32 = 256; const VOXEL_MINIMAP_SIDELENGTH: u32 = 256;
impl VoxelMinimap { impl VoxelMinimap {
pub fn new(ui: &mut Ui) -> Self { pub fn new(ui: &mut Ui) -> Self {
let mut composited = RgbaImage::new(VOXEL_MINIMAP_SIDELENGTH, VOXEL_MINIMAP_SIDELENGTH); let composited = RgbaImage::from_pixel(
for x in 0..VOXEL_MINIMAP_SIDELENGTH { VOXEL_MINIMAP_SIDELENGTH,
for y in 0..VOXEL_MINIMAP_SIDELENGTH { VOXEL_MINIMAP_SIDELENGTH,
composited.put_pixel(x, y, image::Rgba([0, 0, 0, 64])); image::Rgba([0, 0, 0, 64]),
} );
}
Self { Self {
chunk_minimaps: HashMap::new(), chunk_minimaps: HashMap::new(),
image_id: ui.add_graphic_with_rotations(Graphic::Image( image_id: ui.add_graphic_with_rotations(Graphic::Image(
@ -75,34 +75,30 @@ impl VoxelMinimap {
} }
} }
fn block_color(block: &Block) -> Option<Vec4<u8>> { fn block_color(block: &Block) -> Option<Rgba<u8>> {
block block
.get_color() .get_color()
.map(|rgb| Vec4::new(rgb.r, rgb.g, rgb.b, 255)) .map(|rgb| Rgba::new(rgb.r, rgb.g, rgb.b, 255))
.or_else(|| { .or_else(|| {
if matches!(block.kind(), BlockKind::Water) { matches!(block.kind(), BlockKind::Water).then(|| Rgba::new(119, 149, 197, 255))
Some(Vec4::new(119, 149, 197, 255))
} else {
None
}
}) })
} }
/// Each layer is a slice of the terrain near that z-level /// Each layer is a slice of the terrain near that z-level
fn composite_layer_slice(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Vec4<u8>, bool)>>) { fn composite_layer_slice(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Rgba<u8>, bool)>>) {
for z in chunk.get_min_z()..chunk.get_max_z() { for z in chunk.get_min_z()..chunk.get_max_z() {
let grid = Grid::populate_from(Vec2::new(32, 32), |v| { let grid = Grid::populate_from(Vec2::new(32, 32), |v| {
let mut rgba = Vec4::<f32>::zero(); let mut rgba = Rgba::<f32>::zero();
let (weights, zoff) = (&[1, 2, 4, 1, 1, 1][..], -2); let (weights, zoff) = (&[1, 2, 4, 1, 1, 1][..], -2);
for dz in 0..weights.len() { for dz in 0..weights.len() {
let color = chunk let color = chunk
.get(Vec3::new(v.x, v.y, dz as i32 + z + zoff)) .get(Vec3::new(v.x, v.y, dz as i32 + z + zoff))
.ok() .ok()
.and_then(Self::block_color) .and_then(Self::block_color)
.unwrap_or_else(Vec4::zero); .unwrap_or_else(Rgba::zero);
rgba += color.as_() * weights[dz as usize] as f32; rgba += color.as_() * weights[dz as usize] as f32;
} }
let rgba: Vec4<u8> = (rgba / weights.iter().map(|x| *x as f32).sum::<f32>()).as_(); let rgba: Rgba<u8> = (rgba / weights.iter().map(|x| *x as f32).sum::<f32>()).as_();
(rgba, true) (rgba, true)
}); });
layers.push(grid); layers.push(grid);
@ -110,7 +106,7 @@ impl VoxelMinimap {
} }
/// Each layer is the overhead as if its z-level were the ceiling /// Each layer is the overhead as if its z-level were the ceiling
fn composite_layer_overhead(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Vec4<u8>, bool)>>) { fn composite_layer_overhead(chunk: &TerrainChunk, layers: &mut Vec<Grid<(Rgba<u8>, bool)>>) {
for z in chunk.get_min_z()..chunk.get_max_z() { for z in chunk.get_min_z()..chunk.get_max_z() {
let grid = Grid::populate_from(Vec2::new(32, 32), |v| { let grid = Grid::populate_from(Vec2::new(32, 32), |v| {
let mut rgba = None; let mut rgba = None;
@ -124,9 +120,6 @@ impl VoxelMinimap {
.and_then(Self::block_color) .and_then(Self::block_color)
{ {
if seen_air > 0 { if seen_air > 0 {
/*rgba = Some(color.map(|j| {
(j as u32).saturating_sub(if seen_air > 2 { 4 } else { 0 }) as u8
}));*/
rgba = Some(color); rgba = Some(color);
break; break;
} }
@ -147,7 +140,7 @@ impl VoxelMinimap {
let is_filled = block.map_or(true, |b| { let is_filled = block.map_or(true, |b| {
b.is_filled() && !matches!(b.kind(), BlockKind::Leaves | BlockKind::Wood) b.is_filled() && !matches!(b.kind(), BlockKind::Leaves | BlockKind::Wood)
}); });
let rgba = rgba.unwrap_or_else(|| Vec3::zero().with_w(255)); let rgba = rgba.unwrap_or_else(|| Rgba::new(0, 0, 0, 255));
(rgba, is_filled) (rgba, is_filled)
}); });
layers.push(grid); layers.push(grid);
@ -164,12 +157,13 @@ impl VoxelMinimap {
for (key, chunk) in terrain.iter() { for (key, chunk) in terrain.iter() {
let delta: Vec2<u32> = (key - cpos).map(i32::abs).as_(); let delta: Vec2<u32> = (key - cpos).map(i32::abs).as_();
if !self.chunk_minimaps.contains_key(&key) if delta.x < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.x
&& delta.x < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.x
&& delta.y < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.y && delta.y < VOXEL_MINIMAP_SIDELENGTH / TerrainChunkSize::RECT_SIZE.y
&& !self.chunk_minimaps.contains_key(&key)
{ {
if let Some((_, column)) = self.keyed_jobs.spawn(Some(&pool), key, || {
let arc_chunk = Arc::clone(chunk); let arc_chunk = Arc::clone(chunk);
if let Some((_, column)) = self.keyed_jobs.spawn(Some(&pool), key, move |_| { move |_| {
let mut layers = Vec::new(); let mut layers = Vec::new();
const MODE_OVERHEAD: bool = true; const MODE_OVERHEAD: bool = true;
if MODE_OVERHEAD { if MODE_OVERHEAD {
@ -180,25 +174,26 @@ impl VoxelMinimap {
let above = arc_chunk let above = arc_chunk
.get(Vec3::new(0, 0, arc_chunk.get_max_z() + 1)) .get(Vec3::new(0, 0, arc_chunk.get_max_z() + 1))
.ok() .ok()
.cloned() .copied()
.unwrap_or_else(Block::empty); .unwrap_or_else(Block::empty);
let below = arc_chunk let below = arc_chunk
.get(Vec3::new(0, 0, arc_chunk.get_min_z() - 1)) .get(Vec3::new(0, 0, arc_chunk.get_min_z() - 1))
.ok() .ok()
.cloned() .copied()
.unwrap_or_else(Block::empty); .unwrap_or_else(Block::empty);
MinimapColumn { MinimapColumn {
zlo: arc_chunk.get_min_z(), zlo: arc_chunk.get_min_z(),
layers, layers,
above: ( above: (
Self::block_color(&above).unwrap_or_else(Vec4::zero), Self::block_color(&above).unwrap_or_else(Rgba::zero),
above.is_filled(), above.is_filled(),
), ),
below: ( below: (
Self::block_color(&below).unwrap_or_else(Vec4::zero), Self::block_color(&below).unwrap_or_else(Rgba::zero),
below.is_filled(), below.is_filled(),
), ),
} }
}
}) { }) {
self.chunk_minimaps.insert(key, column); self.chunk_minimaps.insert(key, column);
new_chunks = true; new_chunks = true;
@ -216,8 +211,11 @@ impl VoxelMinimap {
pub fn maintain(&mut self, client: &Client, ui: &mut Ui) { pub fn maintain(&mut self, client: &Client, ui: &mut Ui) {
let player = client.entity(); let player = client.entity();
if let Some(pos) = client.state().ecs().read_storage::<comp::Pos>().get(player) { let pos = if let Some(pos) = client.state().ecs().read_storage::<comp::Pos>().get(player) {
let pos = pos.0; pos.0
} else {
return;
};
let vpos = pos.xy() - VOXEL_MINIMAP_SIDELENGTH as f32 / 2.0; let vpos = pos.xy() - VOXEL_MINIMAP_SIDELENGTH as f32 / 2.0;
let cpos: Vec2<i32> = vpos let cpos: Vec2<i32> = vpos
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).div_euclid(j)) .map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).div_euclid(j))
@ -228,6 +226,11 @@ impl VoxelMinimap {
let new_chunks = self.add_chunks_near(&pool, &terrain, cpos); let new_chunks = self.add_chunks_near(&pool, &terrain, cpos);
self.remove_unloaded_chunks(&terrain); self.remove_unloaded_chunks(&terrain);
// ceiling_offset is the distance from the player to a block heuristically
// detected as the ceiling height (a non-tree solid block above them, or
// the sky if no such block exists). This is used for determining which
// z-slice of the minimap to show, such that house roofs and caves and
// dungeons are all handled uniformly.
let ceiling_offset = { let ceiling_offset = {
let voff = Vec2::new( let voff = Vec2::new(
VOXEL_MINIMAP_SIDELENGTH as f32, VOXEL_MINIMAP_SIDELENGTH as f32,
@ -246,18 +249,12 @@ impl VoxelMinimap {
zlo, layers, above, .. zlo, layers, above, ..
}| { }| {
(0..layers.len() as i32) (0..layers.len() as i32)
.filter_map(|dz| { .find(|dz| {
layers.get((pos.z as i32 - zlo + dz) as usize).and_then( layers
|grid| { .get((pos.z as i32 - zlo + dz) as usize)
if grid.get(cmod).map_or(false, |(_, b)| *b) { .and_then(|grid| grid.get(cmod))
Some(dz) .map_or(false, |(_, b)| *b)
} else {
None
}
},
)
}) })
.next()
.unwrap_or_else(|| { .unwrap_or_else(|| {
if above.1 { if above.1 {
1 1
@ -286,17 +283,24 @@ impl VoxelMinimap {
.map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).rem_euclid(j)) .map2(TerrainChunkSize::RECT_SIZE, |i, j| (i as u32).rem_euclid(j))
.as_(); .as_();
let column = self.chunk_minimaps.get(&(cpos + coff)); let column = self.chunk_minimaps.get(&(cpos + coff));
let color: Vec4<u8> = column let color: Rgba<u8> = column
.and_then( .and_then(|column| {
|MinimapColumn { let MinimapColumn {
zlo, zlo,
layers, layers,
above, above,
below, below,
}| { } = column;
if pos.z as i32 + ceiling_offset < *zlo { if pos.z as i32 + ceiling_offset < *zlo {
Some(Vec3::zero().with_w(255)) // If the ceiling is below the bottom of a chunk, color it black,
// so that the middles of caves/dungeons don't show the forests
// around them.
Some(Rgba::new(0, 0, 0, 255))
} else { } else {
// Otherwise, take the pixel from the precomputed z-level view at
// the ceiling's height (using the top slice of the chunk if the
// ceiling is above the chunk, (e.g. so that forests with
// differently-tall trees are handled properly)
layers layers
.get( .get(
((pos.z as i32 - zlo + ceiling_offset) as usize) ((pos.z as i32 - zlo + ceiling_offset) as usize)
@ -311,13 +315,12 @@ impl VoxelMinimap {
}) })
}) })
} }
}, })
) .unwrap_or_else(Rgba::zero);
.unwrap_or_else(Vec4::zero);
self.composited.put_pixel( self.composited.put_pixel(
x, x,
VOXEL_MINIMAP_SIDELENGTH - y - 1, VOXEL_MINIMAP_SIDELENGTH - y - 1,
image::Rgba([color.x, color.y, color.z, color.w]), image::Rgba([color.r, color.g, color.b, color.a]),
); );
} }
} }
@ -331,7 +334,6 @@ impl VoxelMinimap {
); );
} }
} }
}
} }
widget_ids! { widget_ids! {

View File

@ -402,11 +402,13 @@ fn draw_graphic(
pool: Option<&SlowJobPool>, pool: Option<&SlowJobPool>,
) -> Option<(RgbaImage, Option<Rgba<f32>>)> { ) -> Option<(RgbaImage, Option<Rgba<f32>>)> {
match graphic_map.get(&graphic_id) { match graphic_map.get(&graphic_id) {
// Short-circuit spawning a threadpool for blank graphics
Some(Graphic::Blank) => None, Some(Graphic::Blank) => None,
Some(inner) => { Some(inner) => {
let inner = inner.clone();
keyed_jobs keyed_jobs
.spawn(pool, (graphic_id, dims), move |_| { .spawn(pool, (graphic_id, dims), || {
let inner = inner.clone();
move |_| {
match inner { match inner {
// Render image at requested resolution // Render image at requested resolution
// TODO: Use source aabr. // TODO: Use source aabr.
@ -422,7 +424,8 @@ fn draw_graphic(
renderer::draw_vox(&segment, dims, trans, sample_strat), renderer::draw_vox(&segment, dims, trans, sample_strat),
None, None,
)), )),
_ => None, Graphic::Blank => None,
}
} }
}) })
.and_then(|(_, v)| v) .and_then(|(_, v)| v)

View File

@ -1077,11 +1077,14 @@ impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Send + Sync + 'static> Key
} }
} }
pub fn spawn( /// Spawn a task on a specified threadpool. The function is given as a thunk
/// so that if work is needed to create captured variables (e.g.
/// `Arc::clone`), that only occurs if the task hasn't yet been scheduled.
pub fn spawn<F: FnOnce(&K) -> V + Send + Sync + 'static>(
&mut self, &mut self,
pool: Option<&SlowJobPool>, pool: Option<&SlowJobPool>,
k: K, k: K,
f: impl FnOnce(&K) -> V + Send + Sync + 'static, f: impl FnOnce() -> F,
) -> Option<(K, V)> { ) -> Option<(K, V)> {
if let Some(pool) = pool { if let Some(pool) = pool {
while let Ok((k2, v)) = self.rx.try_recv() { while let Ok((k2, v)) = self.rx.try_recv() {
@ -1106,6 +1109,7 @@ impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Send + Sync + 'static> Key
}, },
Entry::Vacant(e) => { Entry::Vacant(e) => {
let tx = self.tx.clone(); let tx = self.tx.clone();
let f = f();
pool.spawn("IMAGE_PROCESSING", move || { pool.spawn("IMAGE_PROCESSING", move || {
let v = f(&k); let v = f(&k);
let _ = tx.send((k, v)); let _ = tx.send((k, v));
@ -1115,7 +1119,7 @@ impl<K: Hash + Eq + Send + Sync + 'static + Clone, V: Send + Sync + 'static> Key
}, },
} }
} else { } else {
let v = f(&k); let v = f()(&k);
Some((k, v)) Some((k, v))
} }
} }