From 394c7bf8fafcb745bb3f186e996d7efa4f5dc750 Mon Sep 17 00:00:00 2001 From: Andrew Brower Date: Thu, 14 Aug 2025 22:43:09 -0400 Subject: [PATCH] feat(lib): provide palette helpers --- slimeball-gui/src/main.rs | 8 ++++-- slimeball-gui/src/proto.rs | 33 +++++++++++++++++++++++++ slimeball-lib/src/lib.rs | 50 +++++++++++++++++++++++++++++++------- 3 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 slimeball-gui/src/proto.rs diff --git a/slimeball-gui/src/main.rs b/slimeball-gui/src/main.rs index 4f4b9b7..74a2b9f 100644 --- a/slimeball-gui/src/main.rs +++ b/slimeball-gui/src/main.rs @@ -13,6 +13,7 @@ extern crate tracing; static GLOBAL: MiMalloc = MiMalloc; mod anvil; +mod proto; mod viewer; fn main() { @@ -82,7 +83,8 @@ impl SlimeballGui { let file = File::open(path).unwrap(); let mut buf = BufReader::new(file); match slimeball_lib::SlimeWorld::deserialize(&mut buf) { - Ok(v) => debug!("{:#?}", v), + // TODO: async? + Ok(v) => proto::load_chunk_tops(v), Err(why) => error!("{:?}", why), } } @@ -112,6 +114,8 @@ impl SlimeballGui { }); }); - egui::CentralPanel::default().show(ui, |ui| ui.heading(world_path.to_string_lossy())); + egui::CentralPanel::default().show(ui, |ui| { + ui.heading(world_path.to_string_lossy()); + }); } } diff --git a/slimeball-gui/src/proto.rs b/slimeball-gui/src/proto.rs new file mode 100644 index 0000000..be1cd3d --- /dev/null +++ b/slimeball-gui/src/proto.rs @@ -0,0 +1,33 @@ +use slimeball_lib::SlimeWorld; + +pub fn load_chunk_tops(world: SlimeWorld) { + let mut top_blocks = vec!["".to_string(); 16 * 16]; + + let chunk = &world.chunks[4]; + + for (i, sec) in chunk.sections.iter().enumerate() { + for block in 0..4096 { + let v = sec.block_states.get(block); + let coord = chunk_coord(i, block); + + if let Some(block_state) = v + && block_state.name != "minecraft:air" + { + debug!("block index {block} ({:?}) in sec {i} val {v:?}", coord); + top_blocks[block % 256] = block_state.name.clone(); + } + } + } + + debug!("{top_blocks:#?}"); +} + +fn chunk_coord(sec: usize, block: usize) -> (usize, usize, usize) { + let (x, y, z) = index_to_coord(block); + (x, y + (16 * sec), z) +} + +fn index_to_coord(i: usize) -> (usize, usize, usize) { + let layer_index = i % 256; + (layer_index / 16, i / (16 * 16), layer_index % 16) +} diff --git a/slimeball-lib/src/lib.rs b/slimeball-lib/src/lib.rs index 757fe1c..0fb7d37 100644 --- a/slimeball-lib/src/lib.rs +++ b/slimeball-lib/src/lib.rs @@ -34,7 +34,7 @@ pub struct Chunk { pub struct Section { pub sky_light: Option>, pub block_light: Option>, - pub block_states: BlockStates, + pub block_states: PalettedContainer<4096, BlockState>, // FIXME: use strong type pub biomes: fastnbt::Value, } @@ -170,7 +170,7 @@ fn read_chunks(buf: &mut impl Read, world_flags: WorldFlags) -> Result = read_sized(buf)?; debug!("{:?}", block_states); debug!("reading biomes"); let biomes: fastnbt::Value = read_sized(buf)?; @@ -264,14 +264,46 @@ fn read_sized(buf: &mut impl Read) -> Result } #[derive(Deserialize, Debug, Clone)] -pub struct BlockStates { - palette: Vec, - data: Option, +#[serde(rename_all = "PascalCase")] +pub struct BlockState { + pub name: String, + pub properties: Option>, } #[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "PascalCase")] -pub struct BlockState { - name: String, - properties: Option>, +pub struct PalettedContainer { + pub palette: Vec, + pub data: Option, +} + +// TODO: move this stuff to a different package as it's shared between Anvil and Slime +impl PalettedContainer { + pub fn get(&self, index: usize) -> Option<&T> { + if self.palette.len() == 1 { + return self.palette.get(0); + } + + if index >= SIZE { + panic!("index {index} outside range for PalettedContainer of size {SIZE}"); + } + + // All indices are the same length. This length is set to the minimum amount + // of bits required to represent the largest index in the palette, and then + // set to a minimum size of 4 bits. + let index_len: usize = self.palette.len().ilog2().try_into().unwrap(); + + debug!( + "Using index length {index_len} for palette with {} items", + self.palette.len() + ); + + // this can almost certainly be optimized but i just want something working for now + let indices_per_long = 64 / index_len; + let index_long = index / indices_per_long; + let index_bit_pos = 64 - (index % indices_per_long); + let mask = ((1 << index_len) - 1) << (index_bit_pos - index_len); + let true_index = ((self.data.as_ref().unwrap()[index_long] & mask) + >> (index_bit_pos - index_len)) as usize; + self.palette.get(true_index) + } } -- 2.51.2