thedes_gen/
map.rs

1use std::convert::Infallible;
2
3use layer::matter::{
4    BiomeLayer,
5    BiomeLayerError,
6    GroundDistrError,
7    GroundLayer,
8    GroundLayerDistr,
9    GroundLayerError,
10};
11use rand::Rng;
12use rand_distr::{Triangular, TriangularError};
13use thedes_async_util::progress;
14use thedes_domain::{
15    geometry::{Coord, CoordPair, Rect},
16    map::{self, Map},
17};
18use thedes_geometry::orientation::Axis;
19use thiserror::Error;
20
21use crate::{matter::BiomeDistr, random::PickedReproducibleRng};
22
23pub mod layer;
24
25#[derive(Debug, Error)]
26pub enum InvalidConfig {
27    #[error("Map rectangle {given_rect} has overflowing bottom right point")]
28    BottomRightOverflow { given_rect: Rect },
29    #[error("Minimum map top left {min} cannot be greater than maximum {max}")]
30    TopLeftBoundOrder { min: CoordPair, max: CoordPair },
31    #[error("Minimum map size {min} cannot be greater than maximum {max}")]
32    SizeBoundOrder { min: CoordPair, max: CoordPair },
33}
34
35#[derive(Debug, Error)]
36pub enum InitError {
37    #[error("Failed to initialize biome layer generator")]
38    Biome(#[source] layer::region::InitError),
39    #[error("Error creating random distribution for map top left's axis {1}")]
40    TopLeftDistr(#[source] TriangularError, Axis),
41    #[error("Error creating random distribution for map size's axis {1}")]
42    SizeDistr(#[source] TriangularError, Axis),
43    #[error("Failed to create a map")]
44    Creation(
45        #[source]
46        #[from]
47        map::InitError,
48    ),
49}
50
51#[derive(Debug, Error)]
52pub enum Error {
53    #[error("Error generating map bioome layer")]
54    BiomeLayer(
55        #[source]
56        #[from]
57        layer::region::Error<BiomeLayerError, Infallible, Infallible>,
58    ),
59    #[error("Error generating map ground layer")]
60    GroundLayer(
61        #[source]
62        #[from]
63        layer::pointwise::Error<GroundLayerError, GroundDistrError>,
64    ),
65}
66
67#[derive(Debug, Clone)]
68pub struct Config {
69    min_top_left: CoordPair,
70    max_top_left: CoordPair,
71    min_size: CoordPair,
72    max_size: CoordPair,
73    biome_distr: BiomeDistr,
74    biome_layer_config: layer::region::Config,
75    ground_layer_distr: GroundLayerDistr,
76}
77
78impl Default for Config {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84impl Config {
85    pub fn new() -> Self {
86        Self {
87            min_top_left: CoordPair { y: 0, x: 0 },
88            max_top_left: CoordPair { y: 10_000, x: 10_000 },
89            min_size: CoordPair { y: 950, x: 950 },
90            max_size: CoordPair { y: 1050, x: 1050 },
91            biome_distr: BiomeDistr::default(),
92            biome_layer_config: layer::region::Config::new(),
93            ground_layer_distr: GroundLayerDistr::default(),
94        }
95    }
96
97    pub fn with_min_top_left(
98        self,
99        min_top_left: CoordPair,
100    ) -> Result<Self, InvalidConfig> {
101        if min_top_left.zip2(self.max_top_left).any(|(min, max)| min > max) {
102            Err(InvalidConfig::TopLeftBoundOrder {
103                min: min_top_left,
104                max: self.max_top_left,
105            })?;
106        }
107        let rect = Rect { top_left: min_top_left, size: self.max_size };
108        if rect.checked_bottom_right().is_none() {
109            Err(InvalidConfig::BottomRightOverflow { given_rect: rect })?
110        }
111        Ok(Self { min_top_left, ..self })
112    }
113
114    pub fn with_max_top_left(
115        self,
116        max_top_left: CoordPair,
117    ) -> Result<Self, InvalidConfig> {
118        if self.min_top_left.zip2(max_top_left).any(|(min, max)| min > max) {
119            Err(InvalidConfig::TopLeftBoundOrder {
120                min: self.min_top_left,
121                max: max_top_left,
122            })?;
123        }
124        let rect = Rect { top_left: max_top_left, size: self.max_size };
125        if rect.checked_bottom_right().is_none() {
126            Err(InvalidConfig::BottomRightOverflow { given_rect: rect })?
127        }
128        Ok(Self { max_top_left, ..self })
129    }
130
131    pub fn with_min_size(
132        self,
133        min_size: CoordPair,
134    ) -> Result<Self, InvalidConfig> {
135        if min_size.zip2(self.max_size).any(|(min, max)| min > max) {
136            Err(InvalidConfig::SizeBoundOrder {
137                min: min_size,
138                max: self.max_size,
139            })?;
140        }
141        let rect = Rect { top_left: self.max_top_left, size: min_size };
142        if rect.checked_bottom_right().is_none() {
143            Err(InvalidConfig::BottomRightOverflow { given_rect: rect })?
144        }
145        Ok(Self { min_size, ..self })
146    }
147
148    pub fn with_max_size(
149        self,
150        max_size: CoordPair,
151    ) -> Result<Self, InvalidConfig> {
152        if self.min_size.zip2(max_size).any(|(min, max)| min > max) {
153            Err(InvalidConfig::SizeBoundOrder {
154                min: self.min_size,
155                max: max_size,
156            })?;
157        }
158        let rect = Rect { top_left: self.max_top_left, size: max_size };
159        if rect.checked_bottom_right().is_none() {
160            Err(InvalidConfig::BottomRightOverflow { given_rect: rect })?
161        }
162        Ok(Self { max_size, ..self })
163    }
164
165    pub fn with_biome_layer(self, config: layer::region::Config) -> Self {
166        Self { biome_layer_config: config, ..self }
167    }
168
169    pub fn with_biome_distr(self, distr: BiomeDistr) -> Self {
170        Self { biome_distr: distr, ..self }
171    }
172
173    pub fn with_ground_layer_distr(self, distr: GroundLayerDistr) -> Self {
174        Self { ground_layer_distr: distr, ..self }
175    }
176
177    pub fn finish(
178        self,
179        rng: &mut PickedReproducibleRng,
180    ) -> Result<Generator, InitError> {
181        let top_left_distr = self
182            .min_top_left
183            .zip2_with_axes(self.max_top_left, |min, max, axis| {
184                let min = f64::from(min);
185                let max = f64::from(max) + 1.0 - f64::EPSILON;
186                let mode = min + (max - min) / 2.0;
187                Triangular::new(min, max, mode)
188                    .map_err(|error| InitError::TopLeftDistr(error, axis))
189            })
190            .transpose()?;
191        let size_distr = self
192            .min_size
193            .zip2_with_axes(self.max_size, |min, max, axis| {
194                let min = f64::from(min);
195                let max = f64::from(max) + 1.0 - f64::EPSILON;
196                let mode = min + (max - min) / 2.0;
197                Triangular::new(min, max, mode)
198                    .map_err(|error| InitError::SizeDistr(error, axis))
199            })
200            .transpose()?;
201
202        let top_left = top_left_distr.as_ref().map(|distr| rng.sample(distr));
203        let size = size_distr.as_ref().map(|distr| rng.sample(distr));
204        let rect = thedes_geometry::Rect { top_left, size };
205        let rect = rect.map(|coord| coord as Coord);
206
207        let map = Map::new(rect)?;
208
209        let biome_layer_gen = self
210            .biome_layer_config
211            .clone()
212            .finish(&map, rng)
213            .map_err(InitError::Biome)?;
214        let ground_layer_gen = layer::pointwise::Generator::new();
215
216        Ok(Generator { config: self, map, biome_layer_gen, ground_layer_gen })
217    }
218}
219
220#[derive(Debug)]
221pub struct Generator {
222    config: Config,
223    map: Map,
224    biome_layer_gen: layer::region::Generator,
225    ground_layer_gen: layer::pointwise::Generator,
226}
227
228impl Generator {
229    pub fn progress_goal(&self) -> usize {
230        self.biome_layer_gen.progress_goal(&self.map)
231            + self.ground_layer_gen.progress_goal(&self.map)
232    }
233
234    pub async fn execute(
235        mut self,
236        rng: &mut PickedReproducibleRng,
237        progress_logger: progress::Logger,
238    ) -> Result<Map, Error> {
239        progress_logger.set_status("generating biome layer");
240        self.biome_layer_gen
241            .execute(
242                &BiomeLayer,
243                &mut self.config.biome_distr,
244                &mut self.map,
245                rng,
246                &mut layer::region::NopCollector,
247                progress_logger.nest(),
248            )
249            .await?;
250
251        progress_logger.set_status("generating ground layer");
252        self.ground_layer_gen
253            .execute(
254                &GroundLayer,
255                &mut self.config.ground_layer_distr,
256                &mut self.map,
257                rng,
258                progress_logger.nest(),
259            )
260            .await?;
261
262        progress_logger.set_status("done");
263
264        Ok(self.map)
265    }
266}