mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
core: split TileCollector trait out of TileMipmapper
Make mipmap-style recursive processing of tiles reusable.
This commit is contained in:
parent
7297c03567
commit
825cf70e51
3 changed files with 206 additions and 107 deletions
|
@ -4,6 +4,7 @@ mod common;
|
||||||
mod metadata_writer;
|
mod metadata_writer;
|
||||||
mod region_group;
|
mod region_group;
|
||||||
mod region_processor;
|
mod region_processor;
|
||||||
|
mod tile_collector;
|
||||||
mod tile_mipmapper;
|
mod tile_mipmapper;
|
||||||
mod tile_renderer;
|
mod tile_renderer;
|
||||||
|
|
||||||
|
|
107
src/core/tile_collector.rs
Normal file
107
src/core/tile_collector.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
//! A trait for recursively processing tiles
|
||||||
|
//!
|
||||||
|
//! Used for mipmap generation and collecting entity data
|
||||||
|
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
|
||||||
|
use super::common::*;
|
||||||
|
|
||||||
|
/// Helper to determine if no further mipmap levels are needed
|
||||||
|
///
|
||||||
|
/// If all tile coordinates are -1 or 0, further mipmap levels will not
|
||||||
|
/// decrease the number of tiles and mipmap generated is considered finished.
|
||||||
|
fn done(tiles: &TileCoordMap) -> bool {
|
||||||
|
tiles
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives the map of populated tile coordinates for the next mipmap level
|
||||||
|
fn map_coords(tiles: &TileCoordMap) -> TileCoordMap {
|
||||||
|
let mut ret = TileCoordMap::default();
|
||||||
|
|
||||||
|
for (&z, xs) in &tiles.0 {
|
||||||
|
for &x in xs {
|
||||||
|
let xt = x >> 1;
|
||||||
|
let zt = z >> 1;
|
||||||
|
|
||||||
|
ret.0.entry(zt).or_default().insert(xt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait to implement for collecting tiles recursively
|
||||||
|
pub trait TileCollector: Sync {
|
||||||
|
/// Return value of [TileCollector::collect_one]
|
||||||
|
type CollectOutput: Send;
|
||||||
|
|
||||||
|
/// List of level 0 tiles
|
||||||
|
fn tiles(&self) -> &[TileCoords];
|
||||||
|
|
||||||
|
/// Called at the beginning of each level of processing
|
||||||
|
fn prepare(&self, level: usize) -> Result<()>;
|
||||||
|
|
||||||
|
/// Called at the end of each level of processing
|
||||||
|
fn finish(
|
||||||
|
&self,
|
||||||
|
level: usize,
|
||||||
|
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Called for each tile coordinate of the level that is currently being generated
|
||||||
|
fn collect_one(
|
||||||
|
&self,
|
||||||
|
level: usize,
|
||||||
|
coords: TileCoords,
|
||||||
|
prev: &TileCoordMap,
|
||||||
|
) -> Result<Self::CollectOutput>;
|
||||||
|
|
||||||
|
/// Collects tiles recursively
|
||||||
|
fn collect_tiles(&self) -> Result<Vec<TileCoordMap>> {
|
||||||
|
let mut tile_stack = {
|
||||||
|
let mut tile_map = TileCoordMap::default();
|
||||||
|
|
||||||
|
for &TileCoords { x, z } in self.tiles() {
|
||||||
|
tile_map.0.entry(z).or_default().insert(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![tile_map]
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let level = tile_stack.len();
|
||||||
|
let prev = &tile_stack[level - 1];
|
||||||
|
if done(prev) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.prepare(level)?;
|
||||||
|
|
||||||
|
let next = map_coords(prev);
|
||||||
|
|
||||||
|
let (send, recv) = mpsc::channel();
|
||||||
|
|
||||||
|
next.0
|
||||||
|
.par_iter()
|
||||||
|
.flat_map(|(&z, xs)| xs.par_iter().map(move |&x| TileCoords { x, z }))
|
||||||
|
.try_for_each(|coords| {
|
||||||
|
let output = self.collect_one(level, coords, prev)?;
|
||||||
|
send.send(output).unwrap();
|
||||||
|
anyhow::Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
drop(send);
|
||||||
|
self.finish(level, recv.into_iter())?;
|
||||||
|
|
||||||
|
tile_stack.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tile_stack)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,53 @@
|
||||||
//! The [TileMipmapper]
|
//! The [TileMipmapper]
|
||||||
|
|
||||||
use std::sync::mpsc;
|
use std::ops::Add;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rayon::prelude::*;
|
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use super::common::*;
|
use super::{common::*, tile_collector::TileCollector};
|
||||||
use crate::{io::fs, types::*};
|
use crate::{io::fs, types::*};
|
||||||
|
|
||||||
|
/// Counters for the number of processed and total tiles
|
||||||
|
///
|
||||||
|
/// Used as return of [TileMipmapper::collect_one]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct MipmapStat {
|
||||||
|
/// Total number of tiles
|
||||||
|
total: usize,
|
||||||
|
/// Processed number of tiles
|
||||||
|
processed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MipmapStat {
|
||||||
|
/// Mipmap step return when none of the input files exist
|
||||||
|
const NOT_FOUND: MipmapStat = MipmapStat {
|
||||||
|
total: 0,
|
||||||
|
processed: 0,
|
||||||
|
};
|
||||||
|
/// Mipmap step return when output file is up-to-date
|
||||||
|
const SKIPPED: MipmapStat = MipmapStat {
|
||||||
|
total: 1,
|
||||||
|
processed: 0,
|
||||||
|
};
|
||||||
|
/// Mipmap step return when a new output file has been generated
|
||||||
|
const PROCESSED: MipmapStat = MipmapStat {
|
||||||
|
total: 1,
|
||||||
|
processed: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for MipmapStat {
|
||||||
|
type Output = MipmapStat;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
MipmapStat {
|
||||||
|
total: self.total + rhs.total,
|
||||||
|
processed: self.processed + rhs.processed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates mipmap tiles from full-resolution tile images
|
/// Generates mipmap tiles from full-resolution tile images
|
||||||
pub struct TileMipmapper<'a> {
|
pub struct TileMipmapper<'a> {
|
||||||
/// Common MinedMap configuration from command line
|
/// Common MinedMap configuration from command line
|
||||||
|
@ -17,39 +56,63 @@ pub struct TileMipmapper<'a> {
|
||||||
regions: &'a [TileCoords],
|
regions: &'a [TileCoords],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> TileCollector for TileMipmapper<'a> {
|
||||||
|
type CollectOutput = MipmapStat;
|
||||||
|
|
||||||
|
fn tiles(&self) -> &[TileCoords] {
|
||||||
|
self.regions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, level: usize) -> Result<()> {
|
||||||
|
info!("Generating level {} mipmaps...", level);
|
||||||
|
|
||||||
|
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?;
|
||||||
|
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(
|
||||||
|
&self,
|
||||||
|
level: usize,
|
||||||
|
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let stat = outputs.fold(
|
||||||
|
MipmapStat {
|
||||||
|
total: 0,
|
||||||
|
processed: 0,
|
||||||
|
},
|
||||||
|
MipmapStat::add,
|
||||||
|
);
|
||||||
|
info!(
|
||||||
|
"Generated level {} mipmaps ({} processed, {} unchanged)",
|
||||||
|
level,
|
||||||
|
stat.processed,
|
||||||
|
stat.total - stat.processed,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_one(
|
||||||
|
&self,
|
||||||
|
level: usize,
|
||||||
|
coords: TileCoords,
|
||||||
|
prev: &TileCoordMap,
|
||||||
|
) -> Result<Self::CollectOutput> {
|
||||||
|
let map_stat = self.render_mipmap::<image::Rgba<u8>>(TileKind::Map, level, coords, prev)?;
|
||||||
|
let lightmap_stat =
|
||||||
|
self.render_mipmap::<image::LumaA<u8>>(TileKind::Lightmap, level, coords, prev)?;
|
||||||
|
Ok(map_stat + lightmap_stat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> TileMipmapper<'a> {
|
impl<'a> TileMipmapper<'a> {
|
||||||
/// Constructs a new TileMipmapper
|
/// Constructs a new TileMipmapper
|
||||||
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
||||||
TileMipmapper { config, regions }
|
TileMipmapper { config, regions }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to determine if no further mipmap levels are needed
|
|
||||||
///
|
|
||||||
/// If all tile coordinates are -1 or 0, further mipmap levels will not
|
|
||||||
/// decrease the number of tiles and mipmap generated is considered finished.
|
|
||||||
fn done(tiles: &TileCoordMap) -> bool {
|
|
||||||
tiles
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Derives the map of populated tile coordinates for the next mipmap level
|
|
||||||
fn map_coords(tiles: &TileCoordMap) -> TileCoordMap {
|
|
||||||
let mut ret = TileCoordMap::default();
|
|
||||||
|
|
||||||
for (&z, xs) in &tiles.0 {
|
|
||||||
for &x in xs {
|
|
||||||
let xt = x >> 1;
|
|
||||||
let zt = z >> 1;
|
|
||||||
|
|
||||||
ret.0.entry(zt).or_default().insert(xt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renders and saves a single mipmap tile image
|
/// Renders and saves a single mipmap tile image
|
||||||
///
|
///
|
||||||
/// Each mipmap tile is rendered by taking 2x2 tiles from the
|
/// Each mipmap tile is rendered by taking 2x2 tiles from the
|
||||||
|
@ -60,9 +123,7 @@ impl<'a> TileMipmapper<'a> {
|
||||||
level: usize,
|
level: usize,
|
||||||
coords: TileCoords,
|
coords: TileCoords,
|
||||||
prev: &TileCoordMap,
|
prev: &TileCoordMap,
|
||||||
count_total: &mpsc::Sender<()>,
|
) -> Result<MipmapStat>
|
||||||
count_processed: &mpsc::Sender<()>,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
where
|
||||||
[P::Subpixel]: image::EncodableLayout,
|
[P::Subpixel]: image::EncodableLayout,
|
||||||
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
||||||
|
@ -97,11 +158,9 @@ impl<'a> TileMipmapper<'a> {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else {
|
let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else {
|
||||||
return Ok(());
|
return Ok(MipmapStat::NOT_FOUND);
|
||||||
};
|
};
|
||||||
|
|
||||||
count_total.send(()).unwrap();
|
|
||||||
|
|
||||||
if Some(input_timestamp) <= output_timestamp {
|
if Some(input_timestamp) <= output_timestamp {
|
||||||
debug!(
|
debug!(
|
||||||
"Skipping unchanged mipmap tile {}",
|
"Skipping unchanged mipmap tile {}",
|
||||||
|
@ -110,7 +169,7 @@ impl<'a> TileMipmapper<'a> {
|
||||||
.expect("tile path must be in output directory")
|
.expect("tile path must be in output directory")
|
||||||
.display(),
|
.display(),
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(MipmapStat::SKIPPED);
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -156,79 +215,11 @@ impl<'a> TileMipmapper<'a> {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
count_processed.send(()).unwrap();
|
Ok(MipmapStat::PROCESSED)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the mipmap generation
|
/// Runs the mipmap generation
|
||||||
pub fn run(self) -> Result<Vec<TileCoordMap>> {
|
pub fn run(self) -> Result<Vec<TileCoordMap>> {
|
||||||
let mut tile_stack = {
|
self.collect_tiles()
|
||||||
let mut tile_map = TileCoordMap::default();
|
|
||||||
|
|
||||||
for &TileCoords { x, z } in self.regions {
|
|
||||||
tile_map.0.entry(z).or_default().insert(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![tile_map]
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let level = tile_stack.len();
|
|
||||||
let prev = &tile_stack[level - 1];
|
|
||||||
if Self::done(prev) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Generating level {} mipmaps...", level);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let (total_send, total_recv) = mpsc::channel();
|
|
||||||
let (processed_send, processed_recv) = mpsc::channel();
|
|
||||||
|
|
||||||
next.0
|
|
||||||
.par_iter()
|
|
||||||
.flat_map(|(&z, xs)| xs.par_iter().map(move |&x| TileCoords { x, z }))
|
|
||||||
.try_for_each(|coords| {
|
|
||||||
self.render_mipmap::<image::Rgba<u8>>(
|
|
||||||
TileKind::Map,
|
|
||||||
level,
|
|
||||||
coords,
|
|
||||||
prev,
|
|
||||||
&total_send,
|
|
||||||
&processed_send,
|
|
||||||
)?;
|
|
||||||
self.render_mipmap::<image::LumaA<u8>>(
|
|
||||||
TileKind::Lightmap,
|
|
||||||
level,
|
|
||||||
coords,
|
|
||||||
prev,
|
|
||||||
&total_send,
|
|
||||||
&processed_send,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
drop(total_send);
|
|
||||||
let total = total_recv.into_iter().count();
|
|
||||||
|
|
||||||
drop(processed_send);
|
|
||||||
let processed = processed_recv.into_iter().count();
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Generated level {} mipmaps ({} processed, {} unchanged)",
|
|
||||||
level,
|
|
||||||
processed,
|
|
||||||
total - processed,
|
|
||||||
);
|
|
||||||
|
|
||||||
tile_stack.push(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(tile_stack)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue