use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Instant;
use clap::Parser;
use terrain_generator::{
ArchipelagoParams, BreachParams, ErosionParams, IslandParams, Layer, NoiseParams, RenderStyle,
RiverParams, Terrain,
};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short = 'W', long, default_value_t = 512)]
width: usize,
#[arg(short = 'H', long, default_value_t = 512)]
height: usize,
#[arg(short, long, default_value_t = 0)]
seed: u32,
#[arg(short = 'n', long, default_value_t = 1)]
count: u32,
#[arg(short, long, default_value = ".")]
output: PathBuf,
#[arg(long, default_value = "map")]
prefix: String,
#[arg(long, default_value_t = 0.08)]
sea_level: f32,
#[arg(long, default_value_t = 8)]
octaves: usize,
#[arg(long, default_value_t = 2.5)]
frequency: f64,
#[arg(long, default_value_t = 2.0)]
lacunarity: f64,
#[arg(long, default_value_t = 0.5)]
persistence: f64,
#[arg(long, default_value_t = 1.0)]
island: f32,
#[arg(long, default_value_t = 1.0)]
coast_noise: f32,
#[arg(long)]
no_island: bool,
#[arg(long, value_name = "N", conflicts_with = "no_island")]
archipelago: Option<usize>,
#[arg(long, default_value_t = 0.35)]
islands_size: f32,
#[arg(long, default_value_t = 0.35)]
islands_variation: f32,
#[arg(long, default_value_t = 0.85)]
islands_spread: f32,
#[arg(long, default_value_t = 0.22)]
islands_spacing: f32,
#[arg(long)]
droplets: Option<usize>,
#[arg(long)]
no_erosion: bool,
#[arg(long, default_value_t = 0.01)]
rivers: f32,
#[arg(long, conflicts_with = "rivers")]
river_min_upstream: Option<f32>,
#[arg(long, conflicts_with_all = ["rivers", "river_min_upstream"])]
river_per_edge: Option<f32>,
#[arg(long)]
no_rivers: bool,
#[arg(long)]
breach_basins: Option<f32>,
#[arg(long, conflicts_with = "layers")]
grey: bool,
#[arg(long, value_delimiter = ',', value_name = "LIST")]
layers: Vec<Layer>,
}
fn main() -> ExitCode {
let args = Args::parse();
let mut builder = Terrain::builder()
.size(args.width, args.height)
.sea_level(args.sea_level)
.noise(NoiseParams {
octaves: args.octaves,
frequency: args.frequency,
lacunarity: args.lacunarity,
persistence: args.persistence,
});
builder = if args.no_island {
builder.no_island()
} else if let Some(islands) = args.archipelago {
builder.archipelago(ArchipelagoParams {
islands,
size: args.islands_size,
size_variation: args.islands_variation,
spread: args.islands_spread,
min_spacing: args.islands_spacing,
coast_noise: args.coast_noise,
})
} else {
builder.island(IslandParams {
size: args.island,
coast_noise: args.coast_noise,
})
};
builder = if args.no_erosion {
builder.no_erosion()
} else {
builder.erosion(ErosionParams {
droplets: args.droplets,
..Default::default()
})
};
builder = if args.no_rivers {
builder.no_rivers()
} else if let Some(cells) = args.river_min_upstream {
builder.rivers(RiverParams::min_upstream_cells(cells))
} else if let Some(factor) = args.river_per_edge {
builder.rivers(RiverParams::per_edge(factor))
} else {
builder.rivers(RiverParams::density(args.rivers))
};
if let Some(max_lake_fraction) = args.breach_basins {
builder = builder.breach_basins(BreachParams { max_lake_fraction });
}
let style = if args.grey {
RenderStyle::Greyscale
} else {
RenderStyle::Shaded
};
if let Err(e) = std::fs::create_dir_all(&args.output) {
eprintln!(
"cannot create output directory {}: {e}",
args.output.display()
);
return ExitCode::FAILURE;
}
for seed in args.seed..args.seed.saturating_add(args.count) {
let start = Instant::now();
let terrain = match builder.clone().seed(seed).try_generate() {
Ok(t) => t,
Err(e) => {
eprintln!("invalid parameters: {e}");
return ExitCode::FAILURE;
}
};
let mut written = Vec::new();
if args.layers.is_empty() {
let path = args.output.join(format!("{}{}.png", args.prefix, seed));
if let Err(e) = terrain.save_png(&path, style) {
eprintln!("failed to write {}: {e}", path.display());
return ExitCode::FAILURE;
}
written.push(path);
} else {
for layer in &args.layers {
let path = args
.output
.join(format!("{}{}_{}.png", args.prefix, seed, layer));
if let Err(e) = terrain.save_layer_png(*layer, &path) {
eprintln!("failed to write {}: {e}", path.display());
return ExitCode::FAILURE;
}
written.push(path);
}
}
let names: Vec<String> = written.iter().map(|p| p.display().to_string()).collect();
println!(
"seed {seed}: {} ({:.1}s)",
names.join(", "),
start.elapsed().as_secs_f32()
);
}
ExitCode::SUCCESS
}