diff --git a/Cargo.lock b/Cargo.lock index d8fac2f..a0b4d5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,6 +325,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + [[package]] name = "jobserver" version = "0.1.26" @@ -372,6 +378,7 @@ dependencies = [ "itertools", "num-integer", "serde", + "serde_json", "zstd", ] @@ -471,6 +478,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + [[package]] name = "serde" version = "1.0.164" @@ -500,6 +513,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "simd-adler32" version = "0.3.5" diff --git a/Cargo.toml b/Cargo.toml index 60a20cc..5e04100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ image = { version = "0.24.5", default-features = false, features = ["png"] } itertools = "0.11.0" num-integer = "0.1.45" serde = "1.0.152" +serde_json = "1.0.99" zstd = "0.12.3" [features] diff --git a/src/bin/minedmap/common.rs b/src/bin/minedmap/common.rs index 7062d45..4aed1c0 100644 --- a/src/bin/minedmap/common.rs +++ b/src/bin/minedmap/common.rs @@ -20,7 +20,8 @@ impl Debug for TileCoords { } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Serialize)] +#[serde(transparent)] pub struct TileCoordMap(pub BTreeMap>); impl TileCoordMap { @@ -45,6 +46,8 @@ pub struct Config { pub region_dir: PathBuf, pub processed_dir: PathBuf, pub output_dir: PathBuf, + pub level_dat_path: PathBuf, + pub metadata_path: PathBuf, } fn coord_filename(coords: TileCoords, ext: &str) -> String { @@ -61,11 +64,15 @@ impl Config { pub fn new(args: super::Args) -> Self { let region_dir = [&args.input_dir, Path::new("region")].iter().collect(); let processed_dir = [&args.output_dir, Path::new("processed")].iter().collect(); + let level_dat_path = [&args.input_dir, Path::new("level.dat")].iter().collect(); + let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect(); Config { region_dir, processed_dir, output_dir: args.output_dir, + level_dat_path, + metadata_path, } } diff --git a/src/bin/minedmap/main.rs b/src/bin/minedmap/main.rs index 5e89025..34aa985 100644 --- a/src/bin/minedmap/main.rs +++ b/src/bin/minedmap/main.rs @@ -1,4 +1,5 @@ mod common; +mod metadata_writer; mod region_processor; mod tile_mipmapper; mod tile_renderer; @@ -9,6 +10,7 @@ use anyhow::Result; use clap::Parser; use common::Config; +use metadata_writer::MetadataWriter; use region_processor::RegionProcessor; use tile_mipmapper::TileMipmapper; use tile_renderer::TileRenderer; @@ -27,7 +29,8 @@ fn main() -> Result<()> { let regions = RegionProcessor::new(&config).run()?; TileRenderer::new(&config).run(®ions)?; - TileMipmapper::new(&config).run(®ions)?; + let tiles = TileMipmapper::new(&config).run(®ions)?; + MetadataWriter::new(&config).run(tiles)?; Ok(()) } diff --git a/src/bin/minedmap/metadata_writer.rs b/src/bin/minedmap/metadata_writer.rs new file mode 100644 index 0000000..437152a --- /dev/null +++ b/src/bin/minedmap/metadata_writer.rs @@ -0,0 +1,106 @@ +use anyhow::{Context, Result}; +use minedmap::{io::fs, world::de}; +use serde::Serialize; + +use super::common::*; + +pub struct MetadataWriter<'a> { + config: &'a Config, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct Bounds { + min_x: i32, + max_x: i32, + min_z: i32, + max_z: i32, +} + +#[derive(Debug, Serialize)] +struct Mipmap<'t> { + bounds: Bounds, + regions: &'t TileCoordMap, +} + +#[derive(Debug, Serialize)] +struct Spawn { + x: i32, + z: i32, +} + +#[derive(Debug, Serialize)] +struct Metadata<'t> { + mipmaps: Vec>, + spawn: Spawn, +} + +impl<'a> MetadataWriter<'a> { + pub fn new(config: &'a Config) -> Self { + MetadataWriter { config } + } + + fn mipmap_entry(regions: &TileCoordMap) -> Mipmap { + let mut min_x = i32::MAX; + let mut max_x = i32::MIN; + let mut min_z = i32::MAX; + let mut max_z = i32::MIN; + + for (&z, xs) in ®ions.0 { + if z < min_z { + min_z = z; + } + if z > max_z { + max_z = z; + } + + for &x in xs { + if x < min_x { + min_x = x; + } + if x > max_x { + max_x = x; + } + } + } + + Mipmap { + bounds: Bounds { + min_x, + max_x, + min_z, + max_z, + }, + regions, + } + } + + fn read_level_dat(&self) -> Result { + minedmap::io::data::from_file(&self.config.level_dat_path) + .context("Failed to read level.dat") + } + + fn spawn(level_dat: &de::LevelDat) -> Spawn { + Spawn { + x: level_dat.data.spawn_x, + z: level_dat.data.spawn_z, + } + } + + pub fn run(&self, tiles: Vec) -> Result<()> { + let level_dat = self.read_level_dat()?; + + let mut metadata = Metadata { + mipmaps: Vec::new(), + spawn: Self::spawn(&level_dat), + }; + + for tile_map in tiles.iter() { + metadata.mipmaps.push(Self::mipmap_entry(tile_map)); + } + + fs::create_with_tmpfile(&self.config.metadata_path, |file| { + serde_json::to_writer(file, &metadata).context("Failed to write metadata") + }) + } +}