mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
world: implement top_layer function
Implement one of the core functions of MinedMap: finding the topmost visible block at each coordinate.
This commit is contained in:
parent
2530a557a9
commit
f48aa877d2
3 changed files with 148 additions and 2 deletions
19
src/main.rs
19
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),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
130
src/world/layer.rs
Normal file
130
src/world/layer.rs
Normal file
|
@ -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<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)]
|
||||
pub struct BlockInfo {
|
||||
block_type: BlockType,
|
||||
y: BlockHeight,
|
||||
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, block_types: &BlockTypeMap) -> Result<BlockInfoArray> {
|
||||
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)
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod chunk;
|
||||
pub mod de;
|
||||
pub mod layer;
|
||||
pub mod section;
|
||||
|
|
Loading…
Add table
Reference in a new issue