Skip to main content

terrain_forge/algorithms/
fractal.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
5/// Type of fractal to generate.
6pub enum FractalType {
7    #[default]
8    /// Mandelbrot set (default).
9    Mandelbrot,
10    /// Julia set.
11    Julia,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15/// Configuration for fractal terrain generation.
16pub struct FractalConfig {
17    /// Which fractal set to use. Default: Mandelbrot.
18    pub fractal_type: FractalType,
19    /// Maximum escape iterations. Default: 100.
20    pub max_iterations: usize,
21}
22
23impl Default for FractalConfig {
24    fn default() -> Self {
25        Self {
26            fractal_type: FractalType::default(),
27            max_iterations: 100,
28        }
29    }
30}
31
32#[derive(Debug, Clone)]
33/// Fractal terrain generator.
34pub struct Fractal {
35    config: FractalConfig,
36}
37
38impl Fractal {
39    /// Creates a new fractal generator with the given config.
40    pub fn new(config: FractalConfig) -> Self {
41        Self { config }
42    }
43}
44
45impl Default for Fractal {
46    fn default() -> Self {
47        Self::new(FractalConfig::default())
48    }
49}
50
51impl Algorithm<Tile> for Fractal {
52    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
53        let mut rng = Rng::new(seed);
54        match self.config.fractal_type {
55            FractalType::Mandelbrot => generate_mandelbrot(grid, self.config.max_iterations),
56            FractalType::Julia => generate_julia(grid, &mut rng, self.config.max_iterations),
57        }
58    }
59
60    fn name(&self) -> &'static str {
61        "Fractal"
62    }
63}
64
65fn generate_mandelbrot(grid: &mut Grid<Tile>, max_iter: usize) {
66    let (w, h) = (grid.width(), grid.height());
67
68    for y in 0..h {
69        for x in 0..w {
70            let cx = (x as f64 / w as f64 - 0.5) * 4.0 - 0.5;
71            let cy = (y as f64 / h as f64 - 0.5) * 4.0;
72
73            let mut zx = 0.0;
74            let mut zy = 0.0;
75            let mut iter = 0;
76
77            while zx * zx + zy * zy < 4.0 && iter < max_iter {
78                let temp = zx * zx - zy * zy + cx;
79                zy = 2.0 * zx * zy + cy;
80                zx = temp;
81                iter += 1;
82            }
83
84            if iter < max_iter / 3 {
85                grid.set(x as i32, y as i32, Tile::Floor);
86            }
87        }
88    }
89}
90
91fn generate_julia(grid: &mut Grid<Tile>, rng: &mut Rng, max_iter: usize) {
92    let (w, h) = (grid.width(), grid.height());
93    // Constrain Julia constants to a range that reliably yields structure.
94    let cx = rng.random() * 1.6 - 0.8;
95    let cy = rng.random() * 1.6 - 0.8;
96
97    for y in 0..h {
98        for x in 0..w {
99            let mut zx = (x as f64 / w as f64 - 0.5) * 3.0;
100            let mut zy = (y as f64 / h as f64 - 0.5) * 3.0;
101            let mut iter = 0;
102
103            while zx * zx + zy * zy < 4.0 && iter < max_iter {
104                let temp = zx * zx - zy * zy + cx;
105                zy = 2.0 * zx * zy + cy;
106                zx = temp;
107                iter += 1;
108            }
109
110            if iter < max_iter / 2 {
111                grid.set(x as i32, y as i32, Tile::Floor);
112            }
113        }
114    }
115}