use crate::noise::{NoiseExt, Perlin, Simplex, Value, Worley};
use crate::{Algorithm, Grid, Tile};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum NoiseType {
#[default]
Perlin,
Simplex,
Value,
Worley,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoiseFillConfig {
pub noise: NoiseType,
pub frequency: f64,
pub scale: f64,
pub output_range: (f64, f64),
pub threshold: f64,
pub fill_range: Option<(f64, f64)>,
pub octaves: u32,
pub lacunarity: f64,
pub persistence: f64,
}
impl Default for NoiseFillConfig {
fn default() -> Self {
Self {
noise: NoiseType::Perlin,
frequency: 0.08,
scale: 1.0,
output_range: (0.0, 1.0),
threshold: 0.0,
fill_range: None,
octaves: 1,
lacunarity: 2.0,
persistence: 0.5,
}
}
}
#[derive(Debug, Clone)]
pub struct NoiseFill {
config: NoiseFillConfig,
}
impl NoiseFill {
pub fn new(config: NoiseFillConfig) -> Self {
Self { config }
}
}
impl Default for NoiseFill {
fn default() -> Self {
Self::new(NoiseFillConfig::default())
}
}
impl Algorithm<Tile> for NoiseFill {
fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
let (w, h) = (grid.width(), grid.height());
let scale = if self.config.scale > 0.0 {
self.config.scale
} else {
1.0
};
let frequency = self.config.frequency / scale;
match self.config.noise {
NoiseType::Perlin => {
let noise = Perlin::new(seed).with_frequency(frequency);
fill_with_config(grid, noise, &self.config);
}
NoiseType::Simplex => {
let noise = Simplex::new(seed).with_frequency(frequency);
fill_with_config(grid, noise, &self.config);
}
NoiseType::Value => {
let noise = Value::new(seed).with_frequency(frequency);
fill_with_config(grid, noise, &self.config);
}
NoiseType::Worley => {
let noise = Worley::new(seed).with_frequency(frequency);
fill_with_config(grid, noise, &self.config);
}
}
if w > 0 && h > 0 {
for x in 0..w {
grid.set(x as i32, 0, Tile::Wall);
grid.set(x as i32, (h - 1) as i32, Tile::Wall);
}
for y in 0..h {
grid.set(0, y as i32, Tile::Wall);
grid.set((w - 1) as i32, y as i32, Tile::Wall);
}
}
}
fn name(&self) -> &'static str {
"NoiseFill"
}
}
fn fill_with_config<N: crate::noise::NoiseSource>(
grid: &mut Grid<Tile>,
noise: N,
config: &NoiseFillConfig,
) {
let (mut out_min, mut out_max) = config.output_range;
if out_min > out_max {
std::mem::swap(&mut out_min, &mut out_max);
}
let range_span = out_max - out_min;
let fill_range = config
.fill_range
.map(|(a, b)| if a <= b { (a, b) } else { (b, a) });
if config.octaves > 1 {
let fbm = noise.fbm(config.octaves, config.lacunarity, config.persistence);
fill_from_noise(
grid,
&fbm,
out_min,
range_span,
fill_range,
config.threshold,
);
} else {
fill_from_noise(
grid,
&noise,
out_min,
range_span,
fill_range,
config.threshold,
);
}
}
fn fill_from_noise<N: crate::noise::NoiseSource>(
grid: &mut Grid<Tile>,
noise: &N,
out_min: f64,
range_span: f64,
fill_range: Option<(f64, f64)>,
threshold: f64,
) {
let (w, h) = (grid.width(), grid.height());
for y in 0..h {
for x in 0..w {
let raw = noise.sample(x as f64, y as f64);
let mut value = (raw + 1.0) * 0.5;
value = out_min + value * range_span;
let fill = match fill_range {
Some((min, max)) => value >= min && value <= max,
None => value >= threshold,
};
let tile = if fill { Tile::Floor } else { Tile::Wall };
grid.set(x as i32, y as i32, tile);
}
}
}