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}