procedural_generation/
lib.rs

1//! Utility for creating procedurally generated maps
2//!
3//! # Quick Start
4//!
5//! ```rust
6//! use procedural_generation::Generator;
7//!
8//! fn main() {
9//!     Generator::new()
10//!         .with_size(40, 10)
11//!         .spawn_perlin(|value| {
12//!             if value > 0.66 {
13//!                 2
14//!             } else if value > 0.33 {
15//!                 1
16//!             } else {
17//!                 0
18//!             }
19//!         })
20//!         .show();
21//! }
22//! ```
23//!
24//! Produces the following (prints with colors in terminal!):
25//!
26//! ```bash
27//! 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
28//! 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
29//! 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
30//! 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
31//! 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
32//! 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
33//! 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
34//! 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 1 1 1 1
35//! 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 1 1 1 1
36//! 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1
37//! ```
38
39use owo_colors::OwoColorize;
40use rand::prelude::*;
41use rand::rngs::ThreadRng;
42use noise::{Perlin, NoiseFn, Seedable};
43use smart_default::*;
44use rayon::prelude::*;
45use std::fmt;
46
47/// Different options for defining how noise should behave. 
48#[derive(Debug, SmartDefault)]
49pub struct NoiseOptions {
50    /// Higher frequency adds a zooming effect to the noise. Default is 1.0.
51    #[default = 1.0]
52    pub frequency: f64,
53    #[default = 1.0]
54    /// Higher redistribution exaggerates higher peaks and lower lows. Default is 1.0.
55    pub redistribution: f64,
56    /// More octaves increases variety. Default is 1.
57    #[default = 1]
58    pub octaves: usize,
59}
60
61impl NoiseOptions {
62    /// Creates `NoiseOptions`. See [Making maps with noise functions](https://www.redblobgames.com/maps/terrain-from-noise/)
63    /// for more detail on each option. Usage of `NoiseOptions` looks like this:
64    ///
65    /// ```rust
66    /// fn main() {
67    ///     let noise_options = NoiseOptions { frequency: 4., ..NoiseOptions::default() };
68    ///     Generator::new()
69    ///         .with_size(40, 10)
70    ///         .with_options(noise_options)
71    ///         .spawn_perlin(|value| {
72    ///             if value > 0.5 {
73    ///                 1
74    ///             } else {
75    ///                 0
76    ///             }
77    ///         })
78    ///         .show();
79    /// }
80    /// ```
81    pub fn new() -> Self {
82        Self::default()
83    }
84}
85
86/// The foundation of this crate
87#[derive(Debug, Default)]
88pub struct Generator {
89    pub map: Vec<usize>,
90    pub width: usize,
91    pub height: usize,
92    pub noise_options: NoiseOptions,
93    rooms: Vec<Room>,
94    seed: u32,
95}
96
97impl Generator {
98    /// Create generator.
99    pub fn new() -> Self {
100        let seed: u32 = rand::thread_rng().gen();
101        Self {
102            seed,
103            ..Self::default()
104        }
105    }
106    fn spawn_room(&mut self, number: usize, size: &Size, rng: &mut ThreadRng) -> &mut Self {
107        let mut x = rng.gen_range(0, self.width);
108        let mut y = rng.gen_range(0, self.height);
109
110        let width = rng.gen_range(size.min_size.0, size.max_size.0);
111        let height = rng.gen_range(size.min_size.1, size.max_size.1);
112
113        // shift room back on if it's off
114        if x + width > self.width {
115            x = self.width - width;
116        }
117
118        // shift room back on if it's off
119        if y + height > self.height {
120            y = self.height - height;
121        }
122
123        let mut collides = false;
124        let room = Room::new(x, y, width, height);
125
126        for other_room in &self.rooms {
127            if room.intersects(&other_room) {
128                collides = true;
129                break;
130            }
131        }
132
133        if !collides {
134            for row in 0..height {
135                for col in 0..width {
136                    let pos = (room.x + col, room.y + row);
137                    self.set(pos.0, pos.1, number);
138                }
139            }
140            self.rooms.push(room);
141        }
142        self
143    }
144    /// Set seed for noise generation. Useful for reproducing results. Random otherwise.
145    pub fn with_seed(mut self, seed: u32) -> Self {
146        self.seed = seed;
147        self
148    }
149    /// Changes how noise is generated. Different values make for much more interesting noise
150    pub fn with_options(mut self, options: NoiseOptions) -> Self {
151        self.noise_options = options;
152        self
153    }
154    /// Prints the map to stdout with colors.
155    pub fn show(&self) {
156        println!("{}", self);
157    }
158    /// Sets size of map. This clears the map as well.
159    pub fn with_size(mut self, width: usize, height: usize) -> Self {
160        self.map = vec![0; width * height];
161        self.width = width;
162        self.height = height;
163        self
164    }
165    /// Generates perlin noise over the entire map.
166    /// For every coordinate, the closure `f(f64)` receives a value
167    /// between 0 and 1. This closure must then return a usize
168    /// accordingly to what value it receives, such as the following.
169    /// You can also modify some options for how the noise should behave,
170    /// see [NoiseOptions](struct.NoiseOptions.html).
171    ///
172    /// ```rust
173    /// fn main() {
174    ///     Generator::new()
175    ///         .with_size(40, 20)
176    ///         .spawn_perlin(|value| {
177    ///             if value > 0.66 {
178    ///                 2
179    ///             } else if value > 0.33 {
180    ///                 1
181    ///             } else {
182    ///                 0
183    ///             }
184    ///         })
185    ///         .show();
186    /// }
187    /// ```
188    pub fn spawn_perlin<F: Fn(f64) -> usize + Sync>(mut self, f: F) -> Self {
189        let perlin = Perlin::new().set_seed(self.seed);
190        let redistribution = self.noise_options.redistribution;
191        let freq = self.noise_options.frequency;
192        let octaves = self.noise_options.octaves;
193        let width = self.width;
194
195        self.map.par_iter_mut().enumerate().for_each(|(pos, index)| {
196            let x = pos % width;
197            let y = pos / width;
198
199            let nx = x as f64 / width as f64;
200            let ny = y as f64 / width as f64;
201
202            let value = (0..octaves).fold(0., |acc, n| {
203                let power = 2.0f64.powf(n as f64);
204                let modifier = 1. / power;
205                acc + modifier * perlin.get([nx * freq * power, ny * freq * power])
206            });
207
208            // add redistribution, map range from -1, 1 to 0, 1 then parse
209            // biome and set it
210            *index = f((value.powf(redistribution) + 1.) / 2.);
211        });
212        self
213    }
214    /// Spawns rooms of varying sizes based on input `size`. `number` sets
215    /// what number the rooms are represented with in the map, `rooms` is amount of rooms
216    /// to generate and `size` specifies the minimum and maximum boundaries for each room.
217    ///
218    /// ```rust
219    /// fn main() {
220    ///     let size = Size::new((4, 4), (10, 10));
221    ///     Generator::new()
222    ///         .with_size(30, 20)
223    ///         .spawn_rooms(2, 3, &size)
224    ///         .show();
225    /// }
226    /// ```
227    pub fn spawn_rooms(mut self, number: usize, rooms: usize, size: &Size) -> Self {
228        let mut rng = rand::thread_rng();
229        for _ in 0..rooms {
230            self.spawn_room(number, size, &mut rng);
231        }
232        self
233    }
234    /// Returns value at (x, y) coordinate, useful since map is in 1d form
235    /// but treated as 2d.
236    pub fn get(&self, x: usize, y: usize) -> usize {
237        self.map[x + y * self.width]
238    }
239    /// Same as `get(...)`, except sets value.
240    pub fn set(&mut self, x: usize, y: usize, value: usize) {
241        self.map[x + y * self.width] = value;
242    }
243    /// This is not recommended unless it's convenient or necessary,
244    /// as 2d vectors are slow.
245    pub fn get_2d_map(&self) -> Vec<Vec<usize>> {
246        self.map.chunks(self.width).fold(vec![], |mut map, chunk| {
247            map.push(chunk.into());
248            map
249        })
250    }
251}
252
253impl fmt::Display for Generator {
254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
255        for y in 0..self.height {
256            for x in 0..self.width {
257                let value = self.get(x, y);
258                let remainder = value % 7;
259                match remainder {
260                    1 => write!(f, "{:?} ", value.red())?,
261                    2 => write!(f, "{:?} ", value.green())?,
262                    3 => write!(f, "{:?} ", value.cyan())?,
263                    4 => write!(f, "{:?} ", value.magenta())?,
264                    5 => write!(f, "{:?} ", value.white())?,
265                    6 => write!(f, "{:?} ", value.yellow())?,
266                    _ => write!(f, "{:?} ", value.blue())?,
267                }
268            }
269            if y < self.height - 1 {
270                write!(f, "\n")?
271            }
272        }
273        Ok(())
274    }
275}
276
277/// Size constraints for spawning rooms
278pub struct Size {
279    /// First option is width, second option is height
280    pub min_size: (usize, usize),
281    /// First option is width, second option is height
282    pub max_size: (usize, usize),
283}
284
285impl Size {
286    pub fn new(min_size: (usize, usize), max_size: (usize, usize)) -> Self {
287        Self { min_size, max_size }
288    }
289}
290
291#[derive(Debug, Default)]
292struct Room {
293    x: usize,
294    y: usize,
295    x2: usize,
296    y2: usize,
297    width: usize,
298    height: usize,
299}
300
301impl Room {
302    fn new(x: usize, y: usize, width: usize, height: usize) -> Self {
303        Room {
304            x,
305            y,
306            x2: x + width,
307            y2: y + height,
308            width,
309            height,
310        }
311    }
312    fn intersects(&self, other: &Self) -> bool {
313        self.x <= other.x2 && self.x2 >= other.x && self.y <= other.y2 && self.y2 >= other.y
314    }
315}