mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-07-11 09:39:06 +02:00
Add documentation comments for all items
This commit is contained in:
parent
ba86dc8c06
commit
05a8056cbf
26 changed files with 576 additions and 42 deletions
|
@ -1,3 +1,5 @@
|
|||
//! Common data types and functions used by multiple generation steps
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Debug,
|
||||
|
@ -9,12 +11,19 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::core::{io::fs::FileMetaVersion, resource::Biome, types::*, world::layer};
|
||||
|
||||
// Increase to force regeneration of all output files
|
||||
/// MinedMap data version number
|
||||
///
|
||||
/// Increase to force regeneration of all output files
|
||||
pub const FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
|
||||
|
||||
/// Coordinate pair of a generated tile
|
||||
///
|
||||
/// Each tile corresponds to one Minecraft region file
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TileCoords {
|
||||
/// The X coordinate
|
||||
pub x: i32,
|
||||
/// The Z coordinate
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
|
@ -24,11 +33,16 @@ impl Debug for TileCoords {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set of tile coordinates
|
||||
///
|
||||
/// Used to store list of populated tiles for each mipmap level in the
|
||||
/// viewer metadata file.
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TileCoordMap(pub BTreeMap<i32, BTreeSet<i32>>);
|
||||
|
||||
impl TileCoordMap {
|
||||
/// Checks whether the map contains a given coordinate pair
|
||||
pub fn contains(&self, coords: TileCoords) -> bool {
|
||||
let Some(xs) = self.0.get(&coords.z) else {
|
||||
return false;
|
||||
|
@ -38,39 +52,62 @@ impl TileCoordMap {
|
|||
}
|
||||
}
|
||||
|
||||
/// Data structure for storing chunk data between processing and rendering steps
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProcessedChunk {
|
||||
/// Block type data
|
||||
pub blocks: Box<layer::BlockArray>,
|
||||
/// Biome data
|
||||
pub biomes: Box<layer::BiomeArray>,
|
||||
/// Block height/depth data
|
||||
pub depths: Box<layer::DepthArray>,
|
||||
}
|
||||
|
||||
/// Data structure for storing region data between processing and rendering steps
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ProcessedRegion {
|
||||
/// List of biomes used in the region
|
||||
///
|
||||
/// Indexed by [ProcessedChunk] biome data
|
||||
pub biome_list: IndexSet<Biome>,
|
||||
/// Processed chunk data
|
||||
pub chunks: ChunkArray<Option<Box<ProcessedChunk>>>,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub num_threads: usize,
|
||||
pub region_dir: PathBuf,
|
||||
pub processed_dir: PathBuf,
|
||||
pub output_dir: PathBuf,
|
||||
pub level_dat_path: PathBuf,
|
||||
pub metadata_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Derives a filename from region coordinates and a file extension
|
||||
///
|
||||
/// Can be used for input regions, processed data or rendered tiles
|
||||
fn coord_filename(coords: TileCoords, ext: &str) -> String {
|
||||
format!("r.{}.{}.{}", coords.x, coords.z, ext)
|
||||
}
|
||||
|
||||
/// Tile kind corresponding to a map layer
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TileKind {
|
||||
/// Regular map tile contains block colors
|
||||
Map,
|
||||
/// Lightmap tile for illumination layer
|
||||
Lightmap,
|
||||
}
|
||||
|
||||
/// Common configuration based on command line arguments
|
||||
pub struct Config {
|
||||
/// Number of threads for parallel processing
|
||||
pub num_threads: usize,
|
||||
/// Path of input region directory
|
||||
pub region_dir: PathBuf,
|
||||
/// Path of input `level.dat` file
|
||||
pub level_dat_path: PathBuf,
|
||||
/// Base path for storage of rendered tile data
|
||||
pub output_dir: PathBuf,
|
||||
/// Path for storage of intermediate processed data files
|
||||
pub processed_dir: PathBuf,
|
||||
/// Path of viewer metadata file
|
||||
pub metadata_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Crates a new [Config] from [command line arguments](super::Args)
|
||||
pub fn new(args: &super::Args) -> Self {
|
||||
let num_threads = match args.jobs {
|
||||
Some(0) => num_cpus::get(),
|
||||
|
@ -79,30 +116,33 @@ impl Config {
|
|||
};
|
||||
|
||||
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 processed_dir = [&args.output_dir, Path::new("processed")].iter().collect();
|
||||
let metadata_path = [&args.output_dir, Path::new("info.json")].iter().collect();
|
||||
|
||||
Config {
|
||||
num_threads,
|
||||
region_dir,
|
||||
processed_dir,
|
||||
output_dir: args.output_dir.clone(),
|
||||
level_dat_path,
|
||||
output_dir: args.output_dir.clone(),
|
||||
processed_dir,
|
||||
metadata_path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs the path to an input region file
|
||||
pub fn region_path(&self, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "mca");
|
||||
[&self.region_dir, Path::new(&filename)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the path of an intermediate processed region file
|
||||
pub fn processed_path(&self, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "bin");
|
||||
[&self.processed_dir, Path::new(&filename)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the base output path for a [TileKind] and mipmap level
|
||||
pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf {
|
||||
let prefix = match kind {
|
||||
TileKind::Map => "map",
|
||||
|
@ -112,6 +152,7 @@ impl Config {
|
|||
[&self.output_dir, Path::new(&dir)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the path of an output tile image
|
||||
pub fn tile_path(&self, kind: TileKind, level: usize, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "png");
|
||||
let dir = self.tile_dir(kind, level);
|
||||
|
@ -119,6 +160,7 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
/// Copies a chunk image into a region tile
|
||||
pub fn overlay_chunk<I, J>(image: &mut I, chunk: &J, coords: ChunkCoords)
|
||||
where
|
||||
I: image::GenericImage,
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
//! The minedmap generator renders map tile images from Minecraft save data
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
mod common;
|
||||
mod metadata_writer;
|
||||
mod region_group;
|
||||
|
@ -18,6 +23,7 @@ use region_processor::RegionProcessor;
|
|||
use tile_mipmapper::TileMipmapper;
|
||||
use tile_renderer::TileRenderer;
|
||||
|
||||
/// Command line arguments for minedmap
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Number of parallel threads to use for processing
|
||||
|
@ -32,6 +38,7 @@ pub struct Args {
|
|||
pub output_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Configures the Rayon thread pool for parallel processing
|
||||
fn setup_threads(num_threads: usize) -> Result<()> {
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(num_threads)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The [MetadataWriter] and related types
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -6,43 +8,62 @@ use super::{
|
|||
core::{self, io::fs, world::de},
|
||||
};
|
||||
|
||||
/// Minimum and maximum X and Z tile coordinates for a mipmap level
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Bounds {
|
||||
/// Minimum X coordinate
|
||||
min_x: i32,
|
||||
/// Maximum X coordinate
|
||||
max_x: i32,
|
||||
/// Minimum Z coordinate
|
||||
min_z: i32,
|
||||
/// Maximum Z coordinate
|
||||
max_z: i32,
|
||||
}
|
||||
|
||||
/// Mipmap level information in viewer metadata file
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Mipmap<'t> {
|
||||
/// Minimum and maximum tile coordinates of the mipmap level
|
||||
bounds: Bounds,
|
||||
/// Map of populated tiles for the mipmap level
|
||||
regions: &'t TileCoordMap,
|
||||
}
|
||||
|
||||
/// Initial spawn point for new players
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Spawn {
|
||||
/// Spawn X coordinate
|
||||
x: i32,
|
||||
/// Spawn Z coordinate
|
||||
z: i32,
|
||||
}
|
||||
|
||||
/// Viewer metadata JSON data structure
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Metadata<'t> {
|
||||
/// Tile information for each mipmap level
|
||||
mipmaps: Vec<Mipmap<'t>>,
|
||||
/// Initial spawn point for new players
|
||||
spawn: Spawn,
|
||||
}
|
||||
|
||||
/// The MetadataWriter is used to generate the viewer metadata file
|
||||
pub struct MetadataWriter<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// Map of generated tiles for each mipmap level
|
||||
tiles: &'a [TileCoordMap],
|
||||
}
|
||||
|
||||
impl<'a> MetadataWriter<'a> {
|
||||
/// Creates a new MetadataWriter
|
||||
pub fn new(config: &'a Config, tiles: &'a [TileCoordMap]) -> Self {
|
||||
MetadataWriter { config, tiles }
|
||||
}
|
||||
|
||||
/// Helper to construct a [Mipmap] data structure from a [TileCoordMap]
|
||||
fn mipmap_entry(regions: &TileCoordMap) -> Mipmap {
|
||||
let mut min_x = i32::MAX;
|
||||
let mut max_x = i32::MIN;
|
||||
|
@ -78,10 +99,12 @@ impl<'a> MetadataWriter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reads and deserializes the `level.dat` of the Minecraft save data
|
||||
fn read_level_dat(&self) -> Result<de::LevelDat> {
|
||||
core::io::data::from_file(&self.config.level_dat_path).context("Failed to read level.dat")
|
||||
}
|
||||
|
||||
/// Generates [Spawn] data from a [de::LevelDat]
|
||||
fn spawn(level_dat: &de::LevelDat) -> Spawn {
|
||||
Spawn {
|
||||
x: level_dat.data.spawn_x,
|
||||
|
@ -89,6 +112,7 @@ impl<'a> MetadataWriter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs the viewer metadata file generation
|
||||
pub fn run(self) -> Result<()> {
|
||||
let level_dat = self.read_level_dat()?;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The generic [RegionGroup] data structure
|
||||
|
||||
use std::{future::Future, iter};
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -5,17 +7,27 @@ use futures_util::future::OptionFuture;
|
|||
|
||||
/// A generic array of 3x3 elements
|
||||
///
|
||||
/// A RegionGroup is used to store information about a 3x3 neighbourhood of
|
||||
/// A RegionGroup is used to store information about a 3x3 neighborhood of
|
||||
/// regions.
|
||||
///
|
||||
/// The center element is always populated, while the 8 adjacent elements may be None.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegionGroup<T> {
|
||||
/// The element corresponding to the center of the 9x9 neighborhood
|
||||
center: T,
|
||||
/// The remaining elements, stored in row-first order
|
||||
///
|
||||
/// The center element is always None.
|
||||
neighs: [Option<T>; 9],
|
||||
}
|
||||
|
||||
impl<T> RegionGroup<T> {
|
||||
/// Constructs a new RegionGroup from a closure called for each element
|
||||
///
|
||||
/// The X and Z coordinates relative to the center (in the range -1..1)
|
||||
/// are passed to the closure.
|
||||
///
|
||||
/// Panics of the closure returns None for the center element.
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(i8, i8) -> Option<T>,
|
||||
|
@ -36,10 +48,14 @@ impl<T> RegionGroup<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the center element
|
||||
pub fn center(&self) -> &T {
|
||||
&self.center
|
||||
}
|
||||
|
||||
/// Returns a reference to an element of the RegionGroup, if populated
|
||||
///
|
||||
/// Always returns None for X and Z coordinates outside of the -1..1 range.
|
||||
pub fn get(&self, x: i8, z: i8) -> Option<&T> {
|
||||
if (x, z) == (0, 0) {
|
||||
return Some(&self.center);
|
||||
|
@ -50,6 +66,7 @@ impl<T> RegionGroup<T> {
|
|||
self.neighs.get((3 * x + z + 4) as usize)?.as_ref()
|
||||
}
|
||||
|
||||
/// Runs a closure on each element to construct a new RegionGroup
|
||||
pub fn map<U, F>(self, mut f: F) -> RegionGroup<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
|
@ -60,6 +77,10 @@ impl<T> RegionGroup<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs a fallible closure on each element to construct a new RegionGroup
|
||||
///
|
||||
/// [Err] return values for the center element are passed up. Outer elements
|
||||
/// become unpopulated when the closure fails.
|
||||
pub fn try_map<U, F>(self, mut f: F) -> Result<RegionGroup<U>>
|
||||
where
|
||||
F: FnMut(T) -> Result<U>,
|
||||
|
@ -70,6 +91,7 @@ impl<T> RegionGroup<T> {
|
|||
Ok(RegionGroup { center, neighs })
|
||||
}
|
||||
|
||||
/// Runs an asynchronous closure on each element to construct a new RegionGroup
|
||||
#[allow(dead_code)]
|
||||
pub async fn async_map<U, F, Fut>(self, mut f: F) -> RegionGroup<U>
|
||||
where
|
||||
|
@ -88,6 +110,10 @@ impl<T> RegionGroup<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs a fallible asynchronous closure on each element to construct a new RegionGroup
|
||||
///
|
||||
/// [Err] return values for the center element are passed up. Outer elements
|
||||
/// become unpopulated when the closure fails.
|
||||
pub async fn async_try_map<U, F, Fut>(self, mut f: F) -> Result<RegionGroup<U>>
|
||||
where
|
||||
Fut: Future<Output = Result<U>>,
|
||||
|
@ -110,6 +136,7 @@ impl<T> RegionGroup<T> {
|
|||
Ok(RegionGroup { center, neighs })
|
||||
}
|
||||
|
||||
/// Returns an [Iterator] over all populated elements
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
iter::once(&self.center).chain(self.neighs.iter().filter_map(Option::as_ref))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The [RegionProcessor] and related functions
|
||||
|
||||
use std::{ffi::OsStr, path::Path, time::SystemTime};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
@ -32,13 +34,20 @@ fn parse_region_filename(file_name: &OsStr) -> Option<TileCoords> {
|
|||
}
|
||||
|
||||
/// Type with methods for processing the regions of a Minecraft save directory
|
||||
///
|
||||
/// The RegionProcessor builds lightmap tiles as well as processed region data
|
||||
/// consumed by subsequent generation steps.
|
||||
pub struct RegionProcessor<'a> {
|
||||
/// Registry of known block types
|
||||
block_types: resource::BlockTypes,
|
||||
/// Registry of known biome types
|
||||
biome_types: resource::BiomeTypes,
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> RegionProcessor<'a> {
|
||||
/// Constructs a new RegionProcessor
|
||||
pub fn new(config: &'a Config) -> Self {
|
||||
RegionProcessor {
|
||||
block_types: resource::BlockTypes::default(),
|
||||
|
@ -47,6 +56,7 @@ impl<'a> RegionProcessor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all regions of the input Minecraft save data
|
||||
fn collect_regions(&self) -> Result<Vec<TileCoords>> {
|
||||
Ok(self
|
||||
.config
|
||||
|
@ -80,9 +90,11 @@ impl<'a> RegionProcessor<'a> {
|
|||
world::layer::top_layer(biome_list, &chunk)
|
||||
}
|
||||
|
||||
/// Renders a lightmap subtile from chunk block light data
|
||||
fn render_chunk_lightmap(
|
||||
block_light: Box<world::layer::BlockLightArray>,
|
||||
) -> image::GrayAlphaImage {
|
||||
/// Width/height of generated chunk lightmap
|
||||
const N: u32 = BLOCKS_PER_CHUNK as u32;
|
||||
|
||||
image::GrayAlphaImage::from_fn(N, N, |x, z| {
|
||||
|
@ -95,6 +107,9 @@ impl<'a> RegionProcessor<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Saves processed region data
|
||||
///
|
||||
/// The timestamp is the time of the last modification of the input region data.
|
||||
fn save_region(
|
||||
path: &Path,
|
||||
processed_region: &ProcessedRegion,
|
||||
|
@ -103,6 +118,9 @@ impl<'a> RegionProcessor<'a> {
|
|||
storage::write(path, processed_region, FILE_META_VERSION, timestamp)
|
||||
}
|
||||
|
||||
/// Saves a lightmap tile
|
||||
///
|
||||
/// The timestamp is the time of the last modification of the input region data.
|
||||
fn save_lightmap(
|
||||
path: &Path,
|
||||
lightmap: &image::GrayAlphaImage,
|
||||
|
@ -117,6 +135,7 @@ impl<'a> RegionProcessor<'a> {
|
|||
|
||||
/// Processes a single region file
|
||||
fn process_region(&self, coords: TileCoords) -> Result<()> {
|
||||
/// Width/height of the region data
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let mut processed_region = ProcessedRegion::default();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The [TileMipmapper]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use rayon::prelude::*;
|
||||
|
||||
|
@ -6,16 +8,24 @@ use super::{
|
|||
core::{io::fs, types::*},
|
||||
};
|
||||
|
||||
/// Generates mipmap tiles from full-resolution tile images
|
||||
pub struct TileMipmapper<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// List of populated tiles for base mipmap level (level 0)
|
||||
regions: &'a [TileCoords],
|
||||
}
|
||||
|
||||
impl<'a> TileMipmapper<'a> {
|
||||
/// Constructs a new TileMipmapper
|
||||
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
||||
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
|
||||
|
@ -23,6 +33,7 @@ impl<'a> TileMipmapper<'a> {
|
|||
.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();
|
||||
|
||||
|
@ -38,6 +49,10 @@ impl<'a> TileMipmapper<'a> {
|
|||
ret
|
||||
}
|
||||
|
||||
/// Renders and saves a single mipmap tile image
|
||||
///
|
||||
/// Each mipmap tile is rendered by taking 2x2 tiles from the
|
||||
/// previous level and scaling them down by 50%.
|
||||
fn render_mipmap<P: image::PixelWithColorType>(
|
||||
&self,
|
||||
kind: TileKind,
|
||||
|
@ -49,6 +64,7 @@ impl<'a> TileMipmapper<'a> {
|
|||
[P::Subpixel]: image::EncodableLayout,
|
||||
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
||||
{
|
||||
/// Tile width/height
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let output_path = self.config.tile_path(kind, level, coords);
|
||||
|
@ -131,6 +147,7 @@ impl<'a> TileMipmapper<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Runs the mipmap generation
|
||||
pub fn run(self) -> Result<Vec<TileCoordMap>> {
|
||||
let mut tile_stack = {
|
||||
let mut tile_map = TileCoordMap::default();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! The [TileRenderer] and related types and functions
|
||||
|
||||
use std::{
|
||||
num::NonZeroUsize,
|
||||
path::PathBuf,
|
||||
|
@ -22,8 +24,16 @@ use super::{
|
|||
region_group::RegionGroup,
|
||||
};
|
||||
|
||||
/// Type for referencing loaded [ProcessedRegion] data
|
||||
type RegionRef = Arc<ProcessedRegion>;
|
||||
|
||||
/// Returns the index of the biome at a block coordinate
|
||||
///
|
||||
/// The passed chunk and block coordinates relative to the center of the
|
||||
/// region group is offset by *dx* and *dz*.
|
||||
///
|
||||
/// The returned tuple contains the relative region coordinates the offset coordinate
|
||||
/// ends up in (in the range -1..1) and the index in that region's biome list.
|
||||
fn biome_at(
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: ChunkCoords,
|
||||
|
@ -49,15 +59,22 @@ fn biome_at(
|
|||
))
|
||||
}
|
||||
|
||||
/// The TileRenderer generates map tiles from processed region data
|
||||
pub struct TileRenderer<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// Runtime for asynchronous region loading
|
||||
rt: &'a tokio::runtime::Runtime,
|
||||
/// List of populated regions to render tiles for
|
||||
regions: &'a [TileCoords],
|
||||
/// Set of populated regions for fast existence checking
|
||||
region_set: rustc_hash::FxHashSet<TileCoords>,
|
||||
/// Cache of previously loaded regions
|
||||
region_cache: Mutex<LruCache<PathBuf, Arc<OnceCell<RegionRef>>>>,
|
||||
}
|
||||
|
||||
impl<'a> TileRenderer<'a> {
|
||||
/// Constructs a new TileRenderer
|
||||
pub fn new(
|
||||
config: &'a Config,
|
||||
rt: &'a tokio::runtime::Runtime,
|
||||
|
@ -76,6 +93,7 @@ impl<'a> TileRenderer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Loads [ProcessedRegion] for a region or returns previously loaded data from the region cache
|
||||
async fn load_region(&self, processed_path: PathBuf) -> Result<RegionRef> {
|
||||
let region_loader = {
|
||||
let mut region_cache = self.region_cache.lock().unwrap();
|
||||
|
@ -96,6 +114,7 @@ impl<'a> TileRenderer<'a> {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
/// Loads a 3x3 neighborhood of processed region data
|
||||
async fn load_region_group(
|
||||
&self,
|
||||
processed_paths: RegionGroup<PathBuf>,
|
||||
|
@ -105,18 +124,29 @@ impl<'a> TileRenderer<'a> {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Computes the color of a tile pixel
|
||||
fn block_color_at(
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: &ProcessedChunk,
|
||||
chunk_coords: ChunkCoords,
|
||||
block_coords: LayerBlockCoords,
|
||||
) -> Option<Vec3> {
|
||||
/// Helper for keys in the weight table
|
||||
///
|
||||
/// Hashing the value as a single u32 is more efficient than hashing
|
||||
/// the tuple elements separately.
|
||||
fn biome_key((dx, dz, index): (i8, i8, u16)) -> u32 {
|
||||
(dx as u8 as u32) | (dz as u8 as u32) << 8 | (index as u32) << 16
|
||||
}
|
||||
|
||||
/// One quadrant of the kernel used to smooth biome edges
|
||||
///
|
||||
/// The kernel is mirrored in X und Z direction to build the full 5x5
|
||||
/// smoothing kernel.
|
||||
const SMOOTH: [[f32; 3]; 3] = [[41.0, 26.0, 7.0], [26.0, 16.0, 4.0], [7.0, 4.0, 1.0]];
|
||||
/// Maximum X coordinate offset to take into account for biome smoothing
|
||||
const X: isize = SMOOTH[0].len() as isize - 1;
|
||||
/// Maximum Z coordinate offset to take into account for biome smoothing
|
||||
const Z: isize = SMOOTH.len() as isize - 1;
|
||||
|
||||
let block = chunk.blocks[block_coords]?;
|
||||
|
@ -168,12 +198,14 @@ impl<'a> TileRenderer<'a> {
|
|||
Some(color / total)
|
||||
}
|
||||
|
||||
/// Renders a chunk subtile into a region tile image
|
||||
fn render_chunk(
|
||||
image: &mut image::RgbaImage,
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: &ProcessedChunk,
|
||||
chunk_coords: ChunkCoords,
|
||||
) {
|
||||
/// Width/height of a chunk subtile
|
||||
const N: u32 = BLOCKS_PER_CHUNK as u32;
|
||||
|
||||
let chunk_image = image::RgbaImage::from_fn(N, N, |x, z| {
|
||||
|
@ -191,6 +223,7 @@ impl<'a> TileRenderer<'a> {
|
|||
overlay_chunk(image, &chunk_image, chunk_coords);
|
||||
}
|
||||
|
||||
/// Renders a region tile image
|
||||
fn render_region(image: &mut image::RgbaImage, region_group: &RegionGroup<RegionRef>) {
|
||||
for (coords, chunk) in region_group.center().chunks.iter() {
|
||||
let Some(chunk) = chunk else {
|
||||
|
@ -201,12 +234,15 @@ impl<'a> TileRenderer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the filename of the processed data for a region and the time of its last modification
|
||||
fn processed_source(&self, coords: TileCoords) -> Result<(PathBuf, SystemTime)> {
|
||||
let path = self.config.processed_path(coords);
|
||||
let timestamp = fs::modified_timestamp(&path)?;
|
||||
Ok((path, timestamp))
|
||||
}
|
||||
|
||||
/// Returns the filenames of the processed data for a 3x3 neighborhood of a region
|
||||
/// and the time of last modification for any of them
|
||||
fn processed_sources(&self, coords: TileCoords) -> Result<(RegionGroup<PathBuf>, SystemTime)> {
|
||||
let sources = RegionGroup::new(|x, z| {
|
||||
Some(TileCoords {
|
||||
|
@ -228,7 +264,9 @@ impl<'a> TileRenderer<'a> {
|
|||
Ok((paths, max_timestamp))
|
||||
}
|
||||
|
||||
/// Renders and saves a region tile image
|
||||
fn render_tile(&self, coords: TileCoords) -> Result<()> {
|
||||
/// Width/height of a tile image
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let (processed_paths, processed_timestamp) = self.processed_sources(coords)?;
|
||||
|
@ -274,6 +312,7 @@ impl<'a> TileRenderer<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Runs the tile generation
|
||||
pub fn run(self) -> Result<()> {
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, 0))?;
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
//! Dumps data from a NBT data file in a human-readable format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
/// Command line arguments for nbtdump
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
/// Filename to dump
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
//! Dumps data from a region data file in a human-readable format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
/// Command line arguments for regiondump
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
/// Filename to dump
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue