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}