diff --git a/src/bin/minedmap/main.rs b/src/bin/minedmap/main.rs index 3aef0f8..22fed56 100644 --- a/src/bin/minedmap/main.rs +++ b/src/bin/minedmap/main.rs @@ -1,5 +1,6 @@ mod common; mod region_processor; +mod tile_mipmapper; mod tile_renderer; use std::path::PathBuf; @@ -9,6 +10,7 @@ use clap::Parser; use common::Config; use region_processor::RegionProcessor; +use tile_mipmapper::TileMipmapper; use tile_renderer::TileRenderer; #[derive(Debug, Parser)] @@ -25,6 +27,7 @@ fn main() -> Result<()> { let regions = RegionProcessor::new(&config).run()?; TileRenderer::new(&config).run(regions.iter().copied())?; + TileMipmapper::new(&config).run(regions)?; Ok(()) } diff --git a/src/bin/minedmap/tile_mipmapper.rs b/src/bin/minedmap/tile_mipmapper.rs new file mode 100644 index 0000000..567c656 --- /dev/null +++ b/src/bin/minedmap/tile_mipmapper.rs @@ -0,0 +1,125 @@ +use std::collections::BTreeSet; + +use anyhow::{Context, Result}; + +use minedmap::{io::fs, types::*}; + +use super::common::*; + +pub struct TileMipmapper<'a> { + config: &'a Config, +} + +impl<'a> TileMipmapper<'a> { + pub fn new(config: &'a Config) -> Self { + TileMipmapper { config } + } + + fn done(tiles: &BTreeSet) -> bool { + tiles + .into_iter() + .all(|TileCoords { x, z }| (-1..=0).contains(x) && (-1..=0).contains(z)) + } + + fn map_coords(tiles: &BTreeSet) -> BTreeSet { + let mut ret = BTreeSet::new(); + + for coords in tiles { + ret.insert(TileCoords { + x: coords.x >> 1, + z: coords.z >> 1, + }); + } + + ret + } + + fn render_mipmap( + &self, + kind: TileKind, + level: usize, + coords: TileCoords, + prev: &BTreeSet, + ) -> Result<()> + where + [P::Subpixel]: image::EncodableLayout, + image::ImageBuffer>: Into, + { + const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32; + + let output_path = self.config.tile_path(kind, level, coords); + + println!( + "Rendering mipmap tile {}", + output_path + .strip_prefix(&self.config.output_dir) + .expect("tile path must be in output directory") + .display(), + ); + + let mut image: image::DynamicImage = + image::ImageBuffer::>::new(N, N).into(); + + for (dx, dz) in [(0, 0), (0, 1), (1, 0), (1, 1)] { + let source_coords = TileCoords { + x: 2 * coords.x + dx, + z: 2 * coords.z + dz, + }; + if !prev.contains(&source_coords) { + continue; + } + + let source_path = self.config.tile_path(kind, level - 1, source_coords); + let source = match image::open(&source_path) { + Ok(source) => source, + Err(err) => { + eprintln!( + "Failed to read source image {}: {}", + source_path.display(), + err, + ); + continue; + } + }; + let resized = source.resize(N / 2, N / 2, image::imageops::FilterType::Triangle); + image::imageops::overlay( + &mut image, + &resized, + dx as i64 * (N / 2) as i64, + dz as i64 * (N / 2) as i64, + ); + } + + fs::create_with_tmpfile(&output_path, |file| { + image + .write_to(file, image::ImageFormat::Png) + .context("Failed to save image") + }) + } + + pub fn run(self, tiles: BTreeSet) -> Result>> { + let mut tile_stack = vec![tiles]; + + loop { + let level = tile_stack.len(); + let prev = &tile_stack[level - 1]; + if Self::done(prev) { + break; + } + + fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?; + fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?; + + let next = Self::map_coords(prev); + + for &coords in &next { + self.render_mipmap::>(TileKind::Map, level, coords, prev)?; + self.render_mipmap::>(TileKind::Lightmap, level, coords, prev)?; + } + + tile_stack.push(next); + } + + Ok(tile_stack) + } +}