terrain-forge 0.7.0

A modular procedural generation engine for terrain, dungeons, and maps
Documentation
use crate::{Algorithm, Grid, Rng, Tile};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
/// Type of fractal to generate.
pub enum FractalType {
    #[default]
    /// Mandelbrot set (default).
    Mandelbrot,
    /// Julia set.
    Julia,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
/// Configuration for fractal terrain generation.
pub struct FractalConfig {
    /// Which fractal set to use. Default: Mandelbrot.
    pub fractal_type: FractalType,
    /// Maximum escape iterations. Default: 100.
    pub max_iterations: usize,
}

impl Default for FractalConfig {
    fn default() -> Self {
        Self {
            fractal_type: FractalType::default(),
            max_iterations: 100,
        }
    }
}

#[derive(Debug, Clone)]
/// Fractal terrain generator.
pub struct Fractal {
    config: FractalConfig,
}

impl Fractal {
    /// Creates a new fractal generator with the given config.
    pub fn new(config: FractalConfig) -> Self {
        Self { config }
    }
}

impl Default for Fractal {
    fn default() -> Self {
        Self::new(FractalConfig::default())
    }
}

impl Algorithm<Tile> for Fractal {
    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
        let mut rng = Rng::new(seed);
        match self.config.fractal_type {
            FractalType::Mandelbrot => generate_mandelbrot(grid, self.config.max_iterations),
            FractalType::Julia => generate_julia(grid, &mut rng, self.config.max_iterations),
        }
    }

    fn name(&self) -> &'static str {
        "Fractal"
    }
}

fn generate_mandelbrot(grid: &mut Grid<Tile>, max_iter: usize) {
    let (w, h) = (grid.width(), grid.height());

    for y in 0..h {
        for x in 0..w {
            let cx = (x as f64 / w as f64 - 0.5) * 4.0 - 0.5;
            let cy = (y as f64 / h as f64 - 0.5) * 4.0;

            let mut zx = 0.0;
            let mut zy = 0.0;
            let mut iter = 0;

            while zx * zx + zy * zy < 4.0 && iter < max_iter {
                let temp = zx * zx - zy * zy + cx;
                zy = 2.0 * zx * zy + cy;
                zx = temp;
                iter += 1;
            }

            if iter < max_iter / 3 {
                grid.set(x as i32, y as i32, Tile::Floor);
            }
        }
    }
}

fn generate_julia(grid: &mut Grid<Tile>, rng: &mut Rng, max_iter: usize) {
    let (w, h) = (grid.width(), grid.height());
    // Constrain Julia constants to a range that reliably yields structure.
    let cx = rng.random() * 1.6 - 0.8;
    let cy = rng.random() * 1.6 - 0.8;

    for y in 0..h {
        for x in 0..w {
            let mut zx = (x as f64 / w as f64 - 0.5) * 3.0;
            let mut zy = (y as f64 / h as f64 - 0.5) * 3.0;
            let mut iter = 0;

            while zx * zx + zy * zy < 4.0 && iter < max_iter {
                let temp = zx * zx - zy * zy + cx;
                zy = 2.0 * zx * zy + cy;
                zx = temp;
                iter += 1;
            }

            if iter < max_iter / 2 {
                grid.set(x as i32, y as i32, Tile::Floor);
            }
        }
    }
}