From f48aa877d23f46c3f4b57e1ff145d580f717ff3a Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sat, 18 Feb 2023 11:51:24 +0100 Subject: [PATCH] world: implement top_layer function Implement one of the core functions of MinedMap: finding the topmost visible block at each coordinate. --- src/main.rs | 19 ++++++- src/world/layer.rs | 130 +++++++++++++++++++++++++++++++++++++++++++++ src/world/mod.rs | 1 + 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/world/layer.rs diff --git a/src/main.rs b/src/main.rs index 2f104ed..63711c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use anyhow::Result; use clap::Parser; +use minedmap::{resource, world}; + #[derive(Debug, Parser)] struct Args { /// Filename to dump @@ -12,9 +14,22 @@ struct Args { fn main() -> Result<()> { let args = Args::parse(); + let block_types = resource::block_types(); + minedmap::io::region::from_file(args.file.as_path())?.foreach_chunk( - |coords, value: minedmap::world::de::Chunk| { - println!("Chunk {:?}: {:#?}", coords, value); + |coords, data: world::de::Chunk| { + let chunk = match world::chunk::Chunk::new(&data) { + Ok(chunk) => chunk, + Err(err) => { + eprintln!("Chunk {:?}: {}", coords, err); + return; + } + }; + + match world::layer::top_layer(&chunk, &block_types) { + Ok(_) => {} + Err(err) => println!("{:?}", err), + } }, ) } diff --git a/src/world/layer.rs b/src/world/layer.rs new file mode 100644 index 0000000..d57c51f --- /dev/null +++ b/src/world/layer.rs @@ -0,0 +1,130 @@ +use anyhow::{Context, Result}; +use itertools::iproduct; + +use super::chunk::Chunk; +use crate::{ + resource::{BlockFlag, BlockType, BlockTypeMap}, + types::*, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BlockHeight(i32); + +impl BlockHeight { + /// Constructs a new [BlockHeight] from section and block Y indices + /// + /// Returns an error if the resulting coordindate does not fit into + /// an [i32]. + pub fn new(section: SectionY, block: BlockY) -> Result { + let height = section + .0 + .checked_mul(BLOCKS_PER_CHUNK as i32) + .and_then(|y| y.checked_add_unsigned(block.0.into())) + .context("Block height out of bounds")?; + Ok(BlockHeight(height)) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct BlockInfo { + block_type: BlockType, + y: BlockHeight, + depth: Option, +} + +/// Helper methods for [BlockInfo] +trait OptionBlockInfoExt { + /// Checks if a [BlockInfo] has been filled in completely + /// + /// Helper used by [top_layer] + fn done(&self) -> bool; + + /// Fills in a [BlockInfo] based on a [BlockType] + /// + /// Only fills in data if the block is part of the visible top layer + /// of the rendered map. + /// + /// Must be called on an incomplete [BlockInfo] entry. Returns `true` + /// if the entry has been filled in completely. + fn fill(&mut self, y: BlockHeight, block_type: BlockType) -> bool; +} + +impl OptionBlockInfoExt for Option { + fn done(&self) -> bool { + let Some(info) = self else { + return false; + }; + + info.depth.is_some() + } + + fn fill(&mut self, y: BlockHeight, block_type: BlockType) -> bool { + if !block_type.is(BlockFlag::Opaque) { + return false; + } + + if self.is_none() { + *self = Some(BlockInfo { + block_type, + y, + depth: None, + }); + } + + if block_type.is(BlockFlag::Water) { + return false; + } + + let info = self.as_mut().unwrap(); + info.depth = Some(y); + + true + } +} + +pub type BlockInfoArray = LayerBlockArray>; + +/// Fills in a [BlockInfoArray] with the information of the chunk's top +/// block layer +/// +/// For each (X, Z) coordinate pair, the topmost opaque block is +/// determined as the block that should be visible on the rendered +/// map. For water blocks, the height of the first non-water block +/// is additionally filled in as the water depth. +pub fn top_layer(chunk: &Chunk, block_types: &BlockTypeMap) -> Result { + use BLOCKS_PER_CHUNK as N; + + let mut done = 0; + let mut ret = BlockInfoArray::default(); + + for ((section_y, section), y, xz) in iproduct!( + chunk.sections().rev(), + BlockY::iter().rev(), + BlockInfoArray::keys() + ) { + let entry = &mut ret[xz]; + if entry.done() { + continue; + } + + let coords = SectionBlockCoords { xz, y }; + let block_id = section.block_id_at(coords)?; + let Some(&block_type) = block_types.get(block_id) else { + eprintln!("Unknown block type: {}", block_id); + continue; + }; + let height = BlockHeight::new(section_y, y)?; + if !entry.fill(height, block_type) { + continue; + } + + assert!(entry.done()); + + done += 1; + if done == N * N { + break; + } + } + + Ok(ret) +} diff --git a/src/world/mod.rs b/src/world/mod.rs index ae071e0..9879066 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1,3 +1,4 @@ pub mod chunk; pub mod de; +pub mod layer; pub mod section;