Skip to main content

terrain_forge/algorithms/
diamond_square.rs

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