Skip to main content

terrain_forge/algorithms/
voronoi.rs

1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5/// Configuration for Voronoi region generation.
6pub struct VoronoiConfig {
7    /// Number of Voronoi seed points. Default: 15.
8    pub num_points: usize,
9    /// Probability of a region being floor. Default: 0.5.
10    pub floor_chance: f64,
11}
12
13impl Default for VoronoiConfig {
14    fn default() -> Self {
15        Self {
16            num_points: 15,
17            floor_chance: 0.5,
18        }
19    }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23/// Voronoi diagram region generator.
24pub struct Voronoi {
25    config: VoronoiConfig,
26}
27
28impl Voronoi {
29    /// Creates a new Voronoi generator with the given config.
30    pub fn new(config: VoronoiConfig) -> Self {
31        Self { config }
32    }
33}
34
35impl Default for Voronoi {
36    fn default() -> Self {
37        Self::new(VoronoiConfig::default())
38    }
39}
40
41impl Algorithm<Tile> for Voronoi {
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        let points: Vec<(usize, usize)> = (0..self.config.num_points)
47            .map(|_| (rng.range_usize(1, w - 1), rng.range_usize(1, h - 1)))
48            .collect();
49
50        let is_floor: Vec<bool> = (0..self.config.num_points)
51            .map(|_| rng.chance(self.config.floor_chance))
52            .collect();
53
54        for y in 1..h - 1 {
55            for x in 1..w - 1 {
56                let mut min_dist = usize::MAX;
57                let mut closest = 0;
58                for (i, &(px, py)) in points.iter().enumerate() {
59                    let dist = (x as i32 - px as i32).unsigned_abs() as usize
60                        + (y as i32 - py as i32).unsigned_abs() as usize;
61                    if dist < min_dist {
62                        min_dist = dist;
63                        closest = i;
64                    }
65                }
66                if is_floor[closest] {
67                    grid.set(x as i32, y as i32, Tile::Floor);
68                }
69            }
70        }
71    }
72
73    fn name(&self) -> &'static str {
74        "Voronoi"
75    }
76}