diff --git a/Cargo.lock b/Cargo.lock index b286664..d771fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.2" @@ -279,6 +285,12 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42218cb640844e3872cc3c153dc975229e080a6c4733b34709ef445610550226" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -305,6 +317,17 @@ dependencies = [ "png", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -375,6 +398,7 @@ dependencies = [ "flate2", "glam", "image", + "indexmap", "itertools", "num-integer", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5e04100..aad8ff8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ fastnbt = "2.3.2" flate2 = "1.0.25" glam = "0.24.0" image = { version = "0.24.5", default-features = false, features = ["png"] } +indexmap = { version = "2.0.0", features = ["serde"] } itertools = "0.11.0" num-integer = "0.1.45" serde = "1.0.152" diff --git a/src/bin/minedmap/common.rs b/src/bin/minedmap/common.rs index a108e8d..eb1613e 100644 --- a/src/bin/minedmap/common.rs +++ b/src/bin/minedmap/common.rs @@ -4,9 +4,10 @@ use std::{ path::{Path, PathBuf}, }; +use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use minedmap::{io::fs::FileMetaVersion, types::*, world::layer}; +use minedmap::{io::fs::FileMetaVersion, resource::Biome, types::*, world::layer}; // Increase to force regeneration of all output files pub const FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0); @@ -43,7 +44,12 @@ pub struct ProcessedChunk { pub biomes: Box, pub depths: Box, } -pub type ProcessedRegion = ChunkArray>; + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ProcessedRegion { + pub biome_list: IndexSet, + pub chunks: ChunkArray>, +} pub struct Config { pub region_dir: PathBuf, diff --git a/src/bin/minedmap/region_processor.rs b/src/bin/minedmap/region_processor.rs index f2720c7..8a77a73 100644 --- a/src/bin/minedmap/region_processor.rs +++ b/src/bin/minedmap/region_processor.rs @@ -2,9 +2,10 @@ use std::{path::Path, time::SystemTime}; use anyhow::{Context, Result}; +use indexmap::IndexSet; use minedmap::{ io::{fs, storage}, - resource, + resource::{self, Biome}, types::*, world::{ self, @@ -45,9 +46,13 @@ impl<'a> RegionProcessor<'a> { } /// Processes a single chunk - fn process_chunk(&self, data: world::de::Chunk) -> Result> { + fn process_chunk( + &self, + biome_list: &mut IndexSet, + data: world::de::Chunk, + ) -> Result> { let chunk = world::chunk::Chunk::new(&data, &self.block_types, &self.biome_types)?; - world::layer::top_layer(&chunk) + world::layer::top_layer(biome_list, &chunk) } fn render_chunk_lightmap( @@ -110,12 +115,12 @@ impl<'a> RegionProcessor<'a> { minedmap::io::region::from_file(path)?.foreach_chunk( |chunk_coords, data: world::de::Chunk| { let Some(layer::LayerData{ blocks, biomes, block_light, depths }) = self - .process_chunk(data) + .process_chunk(&mut processed_region.biome_list, data) .with_context(|| format!("Failed to process chunk {:?}", chunk_coords))? else { return Ok(()); }; - processed_region[chunk_coords] = Some(ProcessedChunk { + processed_region.chunks[chunk_coords] = Some(ProcessedChunk { blocks, biomes, depths, diff --git a/src/bin/minedmap/tile_renderer.rs b/src/bin/minedmap/tile_renderer.rs index c66431f..556483b 100644 --- a/src/bin/minedmap/tile_renderer.rs +++ b/src/bin/minedmap/tile_renderer.rs @@ -56,7 +56,8 @@ fn biome_at( z: block_z, }; let region = region_group.get(region_x, region_z)?; - region[chunk].as_ref()?.biomes[block].as_ref() + let index = region.chunks[chunk].as_ref()?.biomes[block]?.get() - 1; + region.biome_list.get_index(index.into()) } pub struct TileRenderer<'a> { @@ -144,7 +145,7 @@ impl<'a> TileRenderer<'a> { } fn render_region(image: &mut image::RgbaImage, region_group: &RegionGroup) { - for (coords, chunk) in region_group.center().iter() { + for (coords, chunk) in region_group.center().chunks.iter() { let Some(chunk) = chunk else { continue; }; diff --git a/src/resource/biomes.rs b/src/resource/biomes.rs index cf0db4b..0515e93 100644 --- a/src/resource/biomes.rs +++ b/src/resource/biomes.rs @@ -2,13 +2,13 @@ use serde::{Deserialize, Serialize}; use super::Color; -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum BiomeGrassColorModifier { DarkForest, Swamp, } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Biome { pub temp: i8, pub downfall: i8, diff --git a/src/resource/mod.rs b/src/resource/mod.rs index 921fdc2..5eb2838 100644 --- a/src/resource/mod.rs +++ b/src/resource/mod.rs @@ -35,7 +35,7 @@ where BitFlags::::from_bits(bits).map_err(de::Error::custom) } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Color(pub [u8; 3]); #[derive(Debug, Clone, Copy, Serialize, Deserialize)] diff --git a/src/world/layer.rs b/src/world/layer.rs index 4b23f26..fa9a4f7 100644 --- a/src/world/layer.rs +++ b/src/world/layer.rs @@ -1,4 +1,7 @@ +use std::num::NonZeroU16; + use anyhow::{Context, Result}; +use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use super::chunk::{Chunk, SectionIterItem}; @@ -26,13 +29,13 @@ impl BlockHeight { } pub type BlockArray = LayerBlockArray>; -pub type BiomeArray = LayerBlockArray>; +pub type BiomeArray = LayerBlockArray>; pub type BlockLightArray = LayerBlockArray; pub type DepthArray = LayerBlockArray>; struct LayerEntry<'a> { block: &'a mut Option, - biome: &'a mut Option, + biome: &'a mut Option, block_light: &'a mut u8, depth: &'a mut Option, } @@ -46,7 +49,12 @@ impl<'a> LayerEntry<'a> { self.depth.is_some() } - fn fill(&mut self, section: SectionIterItem, coords: SectionBlockCoords) -> Result { + 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 { @@ -59,7 +67,14 @@ impl<'a> LayerEntry<'a> { if self.is_empty() { *self.block = Some(block_type); - *self.biome = section.biomes.biome_at(section.y, coords)?.copied(); + 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) { @@ -99,7 +114,7 @@ impl LayerData { /// 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> { +pub fn top_layer(biome_list: &mut IndexSet, chunk: &Chunk) -> Result> { use BLOCKS_PER_CHUNK as N; if chunk.is_empty() { @@ -121,7 +136,7 @@ pub fn top_layer(chunk: &Chunk) -> Result> { } let coords = SectionBlockCoords { xz, y }; - if !entry.fill(section, coords)? { + if !entry.fill(biome_list, section, coords)? { continue; }