Skip to main content

terrain_forge/algorithms/
diamond_square.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2
3#[derive(Debug, Clone)]
4pub struct DiamondSquareConfig {
5    pub roughness: f64,
6    pub threshold: f64,
7}
8
9impl Default for DiamondSquareConfig {
10    fn default() -> Self {
11        Self {
12            roughness: 0.6,
13            threshold: 0.4,
14        }
15    }
16}
17
18pub struct DiamondSquare {
19    config: DiamondSquareConfig,
20}
21
22impl DiamondSquare {
23    pub fn new(config: DiamondSquareConfig) -> Self {
24        Self { config }
25    }
26}
27
28impl Default for DiamondSquare {
29    fn default() -> Self {
30        Self::new(DiamondSquareConfig::default())
31    }
32}
33
34impl Algorithm<Tile> for DiamondSquare {
35    fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
36        let mut rng = Rng::new(seed);
37        let (w, h) = (grid.width(), grid.height());
38
39        // Create heightmap
40        let mut heights = vec![vec![0.0f64; w]; h];
41
42        // Initialize with noise
43        for row in heights.iter_mut() {
44            for cell in row.iter_mut() {
45                *cell = rng.random();
46            }
47        }
48
49        // Diamond-square iterations to smooth
50        let mut step = w.max(h) / 2;
51        let mut scale = self.config.roughness;
52
53        while step > 0 {
54            // Diamond step - set center of each square
55            let mut y = step;
56            while y < h {
57                let mut x = step;
58                while x < w {
59                    let mut sum = 0.0;
60                    let mut count = 0;
61
62                    if y >= step && x >= step {
63                        sum += heights[y - step][x - step];
64                        count += 1;
65                    }
66                    if y >= step && x + step < w {
67                        sum += heights[y - step][x + step];
68                        count += 1;
69                    }
70                    if y + step < h && x >= step {
71                        sum += heights[y + step][x - step];
72                        count += 1;
73                    }
74                    if y + step < h && x + step < w {
75                        sum += heights[y + step][x + step];
76                        count += 1;
77                    }
78
79                    if count > 0 {
80                        heights[y][x] =
81                            (sum / count as f64 + (rng.random() - 0.5) * scale).clamp(0.0, 1.0);
82                    }
83                    x += step * 2;
84                }
85                y += step * 2;
86            }
87
88            // Square step - set edge midpoints
89            for (y, _row) in heights.iter_mut().enumerate() {
90                let x_start = if (y / step) % 2 == 0 { step } else { 0 };
91                let mut x = x_start;
92                while x < w {
93                    x += step * 2;
94                }
95            }
96
97            step /= 2;
98            scale *= 0.5;
99        }
100
101        // Convert to tiles
102        for (y, row) in heights.iter().enumerate() {
103            for (x, &height) in row.iter().enumerate() {
104                if height > self.config.threshold {
105                    grid.set(x as i32, y as i32, Tile::Floor);
106                }
107            }
108        }
109    }
110
111    fn name(&self) -> &'static str {
112        "DiamondSquare"
113    }
114}