//! Functions to search the "top" layer of a chunk use std::num::NonZeroU16; use anyhow::{Context, Result}; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use super::chunk::{Chunk, SectionIterItem}; use crate::{ resource::{Biome, BlockFlag, BlockType}, types::*, }; /// Height (Y coordinate) of a block #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct BlockHeight(pub 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)) } } /// Array optionally storing a [BlockType] for each coordinate of a chunk pub type BlockArray = LayerBlockArray>; /// Array optionally storing a biome index for each coordinate of a chunk /// /// The entries refer to a biome list generated with the top layer data. /// Indices are stored incremented by 1 to allow using a [NonZeroU16]. pub type BiomeArray = LayerBlockArray>; /// Array storing a block light value for each coordinate for a chunk pub type BlockLightArray = LayerBlockArray; /// Array optionally storing a depth value for each coordinate for a chunk pub type DepthArray = LayerBlockArray>; /// References to LayerData entries for a single coordinate pair struct LayerEntry<'a> { /// The block type of the referenced entry block: &'a mut Option, /// The biome type of the referenced entry biome: &'a mut Option, /// The block light of the referenced entry block_light: &'a mut u8, /// The depth value of the referenced entry depth: &'a mut Option, } impl<'a> LayerEntry<'a> { /// Returns true if the entry has not been filled yet (no opaque block has been encountered) /// /// The depth value is filled separately when a non-water block is encountered after the block type /// has already been filled. fn is_empty(&self) -> bool { self.block.is_none() } /// Returns true if the entry has been filled including its depth (an opaque non-water block has been /// encountered) fn done(&self) -> bool { self.depth.is_some() } /// Fills in the LayerEntry /// /// Checks whether the passed coordinates point at an opaque or non-water block and /// fills in the entry accordingly. Returns true when the block has been filled including its depth. fn fill( &mut self, biome_list: &mut IndexSet, section: SectionIterItem, coords: SectionBlockCoords, ) -> Result { let Some(block_type) = section .section .block_at(coords)? .filter(|block_type| block_type.is(BlockFlag::Opaque)) else { if self.is_empty() { *self.block_light = section.block_light.block_light_at(coords); } return Ok(false); }; if self.is_empty() { *self.block = Some(block_type); if let Some(biome) = section.biomes.biome_at(section.y, coords)? { let (biome_index, _) = biome_list.insert_full(*biome); *self.biome = NonZeroU16::new( (biome_index + 1) .try_into() .expect("biome index not in range"), ); } } if block_type.is(BlockFlag::Water) { return Ok(false); } let height = BlockHeight::new(section.y, coords.y)?; *self.depth = Some(height); Ok(true) } } /// Top layer data /// /// A LayerData stores block type, biome, block light and depth data for /// each coordinate of a chunk. #[derive(Debug, Default)] pub struct LayerData { /// Block type data pub blocks: Box, /// Biome data pub biomes: Box, /// Block light data pub block_light: Box, /// Depth data pub depths: Box, } impl LayerData { /// Builds a [LayerEntry] referencing the LayerData at a given coordinate pair fn entry(&mut self, coords: LayerBlockCoords) -> LayerEntry { LayerEntry { block: &mut self.blocks[coords], biome: &mut self.biomes[coords], block_light: &mut self.block_light[coords], depth: &mut self.depths[coords], } } } /// Fills in a [LayerData] 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 (the block height is /// used as depth otherwise). pub fn top_layer(biome_list: &mut IndexSet, chunk: &Chunk) -> Result> { use BLOCKS_PER_CHUNK as N; if chunk.is_empty() { return Ok(None); } let mut done = 0; let mut ret = LayerData::default(); for section in chunk.sections().rev() { for y in BlockY::iter().rev() { for z in BlockZ::iter() { for x in BlockX::iter() { let xz = LayerBlockCoords { x, z }; let mut entry = ret.entry(xz); if entry.done() { continue; } let coords = SectionBlockCoords { xz, y }; if !entry.fill(biome_list, section, coords)? { continue; } assert!(entry.done()); done += 1; if done == N * N { break; } } } } } Ok(Some(ret)) }