mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-04-19 11:05:08 +02:00
128 lines
3 KiB
Rust
128 lines
3 KiB
Rust
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::chunk::Chunk;
|
|
use crate::{
|
|
resource::{BlockFlag, BlockType},
|
|
types::*,
|
|
};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
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<Self> {
|
|
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, Serialize, Deserialize)]
|
|
pub struct BlockInfo {
|
|
pub block_type: BlockType,
|
|
pub y: BlockHeight,
|
|
pub depth: Option<BlockHeight>,
|
|
}
|
|
|
|
/// 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<BlockInfo> {
|
|
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<Option<BlockInfo>>;
|
|
|
|
/// 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) -> Result<Box<BlockInfoArray>> {
|
|
use BLOCKS_PER_CHUNK as N;
|
|
|
|
let mut done = 0;
|
|
let mut ret = Box::<BlockInfoArray>::default();
|
|
|
|
for (section_y, section) in chunk.sections().rev() {
|
|
for y in BlockY::iter().rev() {
|
|
for xz in BlockInfoArray::keys() {
|
|
let entry = &mut ret[xz];
|
|
if entry.done() {
|
|
continue;
|
|
}
|
|
|
|
let coords = SectionBlockCoords { xz, y };
|
|
let Some(block_type) = section.block_at(coords)? else {
|
|
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)
|
|
}
|