oxygengine_procedural/
world_2d.rs

1use oxygengine_utils::{grid_2d::Grid2d, noise_map_generator::NoiseMapGenerator, Scalar};
2use psyche_utils::switch::Switch;
3use serde::{Deserialize, Serialize};
4#[cfg(not(feature = "scalar64"))]
5use std::f32::{INFINITY as SCALAR_INFINITY, NEG_INFINITY as SCALAR_NEG_INFINITY};
6#[cfg(feature = "scalar64")]
7use std::f64::{INFINITY as SCALAR_INFINITY, NEG_INFINITY as SCALAR_NEG_INFINITY};
8use std::{any::Any, borrow::Borrow, ops::Range};
9
10pub type World2dField = Switch<Grid2d<Scalar>>;
11
12#[derive(Debug, Clone)]
13pub struct World2dConfig {
14    pub size: usize,
15    pub zoom: Scalar,
16    pub altitude_seed: u32,
17    pub altitude_range: Range<Scalar>,
18    pub temperature_seed: u32,
19    pub temperature_range: Range<Scalar>,
20    pub humidity_seed: u32,
21    pub humidity_range: Range<Scalar>,
22}
23
24impl Default for World2dConfig {
25    fn default() -> Self {
26        Self {
27            size: 100,
28            zoom: 5.0,
29            altitude_seed: 1,
30            altitude_range: 0.0..100.0,
31            temperature_seed: 2,
32            temperature_range: 0.0..100.0,
33            humidity_seed: 3,
34            humidity_range: 0.1..1.0,
35        }
36    }
37}
38
39pub trait World2dSimulation: Any + Send + Sync {
40    fn initialize_world(
41        &mut self,
42        _altitude: &mut Grid2d<Scalar>,
43        _temperature: &mut Grid2d<Scalar>,
44        _humidity: &mut Grid2d<Scalar>,
45        _surface_water: &mut Grid2d<Scalar>,
46    ) {
47    }
48
49    fn process_world(
50        &mut self,
51        altitude: &mut World2dField,
52        temperature: &mut World2dField,
53        humidity: &mut World2dField,
54        surface_water: &mut World2dField,
55    );
56
57    fn as_any(&self) -> &dyn Any;
58}
59
60impl World2dSimulation for () {
61    fn process_world(
62        &mut self,
63        _altitude: &mut World2dField,
64        _temperature: &mut World2dField,
65        _humidity: &mut World2dField,
66        _surface_water: &mut World2dField,
67    ) {
68    }
69
70    fn as_any(&self) -> &dyn Any {
71        self
72    }
73}
74
75#[derive(Debug, Default, Clone)]
76pub struct World2dStats {
77    /// (min, max, mean)
78    pub altitude: (Scalar, Scalar, Scalar),
79    /// (min, max, mean)
80    pub temperature: (Scalar, Scalar, Scalar),
81    /// (min, max, mean)
82    pub humidity: (Scalar, Scalar, Scalar),
83    /// (min, max, mean)
84    pub surface_water: (Scalar, Scalar, Scalar),
85}
86
87pub struct World2d {
88    size: usize,
89    altitude: Switch<Grid2d<Scalar>>,
90    temperature: Switch<Grid2d<Scalar>>,
91    humidity: Switch<Grid2d<Scalar>>,
92    surface_water: Switch<Grid2d<Scalar>>,
93    simulation: Box<dyn World2dSimulation>,
94    stats: World2dStats,
95}
96
97impl World2d {
98    pub fn new(config: &World2dConfig, mut simulation: Box<dyn World2dSimulation>) -> Self {
99        let mut altitude = {
100            let gen = NoiseMapGenerator::new(config.altitude_seed, config.size, config.zoom);
101            let diff = config.altitude_range.end - config.altitude_range.start;
102            Switch::new(
103                2,
104                gen.build_chunk((0, 0), 0)
105                    .map(|_, _, v| config.altitude_range.start + diff * v),
106            )
107        };
108        let mut temperature = {
109            let gen = NoiseMapGenerator::new(config.temperature_seed, config.size, config.zoom);
110            let diff = config.temperature_range.end - config.temperature_range.start;
111            Switch::new(
112                2,
113                gen.build_chunk((0, 0), 0)
114                    .map(|_, _, v| config.temperature_range.start + diff * v),
115            )
116        };
117        let mut humidity = {
118            let gen = NoiseMapGenerator::new(config.humidity_seed, config.size, config.zoom);
119            let diff = config.humidity_range.end - config.humidity_range.start;
120            Switch::new(
121                2,
122                gen.build_chunk((0, 0), 0)
123                    .map(|_, _, v| config.humidity_range.start + diff * v),
124            )
125        };
126        let mut surface_water = Switch::new(2, Grid2d::new(config.size, config.size, 0.0));
127        simulation.initialize_world(
128            altitude.get_mut().unwrap(),
129            temperature.get_mut().unwrap(),
130            humidity.get_mut().unwrap(),
131            surface_water.get_mut().unwrap(),
132        );
133        let mut result = Self {
134            size: config.size,
135            altitude,
136            temperature,
137            humidity,
138            surface_water,
139            simulation,
140            stats: Default::default(),
141        };
142        result.calculate_stats();
143        result
144    }
145
146    pub fn generate<FA, FT, FH, FSW>(
147        size: usize,
148        mut simulation: Box<dyn World2dSimulation>,
149        mut altitude_generator: FA,
150        mut temperature_generator: FT,
151        mut humidity_generator: FH,
152        mut surface_water_generator: FSW,
153    ) -> Self
154    where
155        FA: FnMut(usize, usize) -> Scalar,
156        FT: FnMut(usize, usize) -> Scalar,
157        FH: FnMut(usize, usize) -> Scalar,
158        FSW: FnMut(usize, usize) -> Scalar,
159    {
160        let altitude = (0..(size * size))
161            .map(|i| altitude_generator(i % size, i / size))
162            .collect::<Vec<_>>();
163        let temperature = (0..size * size)
164            .map(|i| temperature_generator(i % size, i / size))
165            .collect::<Vec<_>>();
166        let humidity = (0..size * size)
167            .map(|i| humidity_generator(i % size, i / size))
168            .collect::<Vec<_>>();
169        let surface_water = (0..size * size)
170            .map(|i| surface_water_generator(i % size, i / size))
171            .collect::<Vec<_>>();
172        let mut altitude = Switch::new(2, Grid2d::with_cells(size, altitude));
173        let mut temperature = Switch::new(2, Grid2d::with_cells(size, temperature));
174        let mut humidity = Switch::new(2, Grid2d::with_cells(size, humidity));
175        let mut surface_water = Switch::new(2, Grid2d::with_cells(size, surface_water));
176        simulation.initialize_world(
177            altitude.get_mut().unwrap(),
178            temperature.get_mut().unwrap(),
179            humidity.get_mut().unwrap(),
180            surface_water.get_mut().unwrap(),
181        );
182        let mut result = Self {
183            size,
184            altitude,
185            temperature,
186            humidity,
187            surface_water,
188            simulation,
189            stats: Default::default(),
190        };
191        result.calculate_stats();
192        result
193    }
194
195    pub fn stats(&self) -> &World2dStats {
196        &self.stats
197    }
198
199    pub fn size(&self) -> usize {
200        self.size
201    }
202
203    pub fn altitude(&self) -> &Grid2d<Scalar> {
204        self.altitude.get().unwrap()
205    }
206
207    pub fn temperature(&self) -> &Grid2d<Scalar> {
208        self.temperature.get().unwrap()
209    }
210
211    pub fn humidity(&self) -> &Grid2d<Scalar> {
212        self.humidity.get().unwrap()
213    }
214
215    pub fn surface_water(&self) -> &Grid2d<Scalar> {
216        self.surface_water.get().unwrap()
217    }
218
219    pub fn simulation(&self) -> &dyn World2dSimulation {
220        self.simulation.borrow()
221    }
222
223    pub fn as_simulation<T>(&self) -> Option<&T>
224    where
225        T: World2dSimulation,
226    {
227        self.simulation.as_any().downcast_ref::<T>()
228    }
229
230    pub fn process(&mut self) {
231        self.simulation.process_world(
232            &mut self.altitude,
233            &mut self.temperature,
234            &mut self.humidity,
235            &mut self.surface_water,
236        );
237        self.calculate_stats();
238    }
239
240    pub fn remap_region<F, T>(&self, mut range: Range<(usize, usize)>, mut f: F) -> Grid2d<T>
241    where
242        // (col, row, altitude, temperature, humidity, surface water)
243        F: FnMut(usize, usize, Scalar, Scalar, Scalar, Scalar) -> T,
244        T: Clone + Send + Sync,
245    {
246        range.end.0 = range.end.0.min(self.size);
247        range.end.1 = range.end.1.min(self.size);
248        range.start.0 = range.start.0.min(range.end.0);
249        range.start.1 = range.start.1.min(range.end.1);
250        let cells = self
251            .altitude
252            .get()
253            .unwrap()
254            .iter_view(range.clone())
255            .zip(self.temperature.get().unwrap().iter_view(range.clone()))
256            .zip(self.humidity.get().unwrap().iter_view(range.clone()))
257            .zip(self.surface_water.get().unwrap().iter_view(range.clone()))
258            .map(|((((col, row, a), (_, _, t)), (_, _, h)), (_, _, sw))| {
259                f(col, row, *a, *t, *h, *sw)
260            })
261            .collect::<Vec<T>>();
262        Grid2d::with_cells(range.end.0 - range.start.0, cells)
263    }
264
265    pub fn resample_region<F, T>(
266        &self,
267        mut range: Range<(usize, usize)>,
268        margin: usize,
269        mut f: F,
270    ) -> Grid2d<T>
271    where
272        // (col, row, altitude, temperature, humidity, surface water)
273        F: FnMut(
274            usize,
275            usize,
276            Grid2d<&Scalar>,
277            Grid2d<&Scalar>,
278            Grid2d<&Scalar>,
279            Grid2d<&Scalar>,
280        ) -> T,
281        T: Clone + Send + Sync,
282    {
283        range.end.0 = range.end.0.min(self.size);
284        range.end.1 = range.end.1.min(self.size);
285        range.start.0 = range.start.0.min(range.end.0);
286        range.start.1 = range.start.1.min(range.end.1);
287        let cells = self
288            .altitude
289            .get()
290            .unwrap()
291            .iter_sample(range.clone(), margin)
292            .zip(
293                self.temperature
294                    .get()
295                    .unwrap()
296                    .iter_sample(range.clone(), margin),
297            )
298            .zip(
299                self.humidity
300                    .get()
301                    .unwrap()
302                    .iter_sample(range.clone(), margin),
303            )
304            .zip(
305                self.surface_water
306                    .get()
307                    .unwrap()
308                    .iter_sample(range.clone(), margin),
309            )
310            .map(|((((col, row, a), (_, _, t)), (_, _, h)), (_, _, sw))| f(col, row, a, t, h, sw))
311            .collect::<Vec<T>>();
312        Grid2d::with_cells(range.end.0 - range.start.0, cells)
313    }
314
315    fn calculate_stats(&mut self) {
316        self.stats.altitude = {
317            let (min, max, accum) = self
318                .altitude
319                .get()
320                .unwrap()
321                .iter()
322                .fold((SCALAR_INFINITY, SCALAR_NEG_INFINITY, 0.0), |a, v| {
323                    (a.0.min(*v), a.1.max(*v), a.2 + *v)
324                });
325            (
326                min,
327                max,
328                accum / self.altitude.get().unwrap().len() as Scalar,
329            )
330        };
331        self.stats.temperature = {
332            let (min, max, accum) = self
333                .temperature
334                .get()
335                .unwrap()
336                .iter()
337                .fold((SCALAR_INFINITY, SCALAR_NEG_INFINITY, 0.0), |a, v| {
338                    (a.0.min(*v), a.1.max(*v), a.2 + *v)
339                });
340            (
341                min,
342                max,
343                accum / self.altitude.get().unwrap().len() as Scalar,
344            )
345        };
346        self.stats.humidity = {
347            let (min, max, accum) = self
348                .humidity
349                .get()
350                .unwrap()
351                .iter()
352                .fold((SCALAR_INFINITY, SCALAR_NEG_INFINITY, 0.0), |a, v| {
353                    (a.0.min(*v), a.1.max(*v), a.2 + *v)
354                });
355            (
356                min,
357                max,
358                accum / self.altitude.get().unwrap().len() as Scalar,
359            )
360        };
361        self.stats.surface_water = {
362            let (min, max, accum) = self
363                .surface_water
364                .get()
365                .unwrap()
366                .iter()
367                .fold((SCALAR_INFINITY, SCALAR_NEG_INFINITY, 0.0), |a, v| {
368                    (a.0.min(*v), a.1.max(*v), a.2 + *v)
369                });
370            (
371                min,
372                max,
373                accum / self.altitude.get().unwrap().len() as Scalar,
374            )
375        };
376    }
377}
378
379#[derive(Clone, Serialize, Deserialize)]
380pub struct World2dData<S>
381where
382    S: World2dSimulation,
383{
384    size: usize,
385    altitude: Grid2d<Scalar>,
386    temperature: Grid2d<Scalar>,
387    humidity: Grid2d<Scalar>,
388    surface_water: Grid2d<Scalar>,
389    simulation: S,
390}
391
392impl<S> From<&World2d> for World2dData<S>
393where
394    S: World2dSimulation + Clone,
395{
396    fn from(world: &World2d) -> Self {
397        Self {
398            size: world.size,
399            altitude: world.altitude.get().unwrap().clone(),
400            temperature: world.temperature.get().unwrap().clone(),
401            humidity: world.humidity.get().unwrap().clone(),
402            surface_water: world.surface_water.get().unwrap().clone(),
403            simulation: world.as_simulation::<S>().unwrap().clone(),
404        }
405    }
406}
407
408impl<S> From<&World2dData<S>> for World2d
409where
410    S: World2dSimulation + Clone,
411{
412    fn from(data: &World2dData<S>) -> Self {
413        Self {
414            size: data.size,
415            altitude: Switch::new(2, data.altitude.clone()),
416            temperature: Switch::new(2, data.temperature.clone()),
417            humidity: Switch::new(2, data.humidity.clone()),
418            surface_water: Switch::new(2, data.surface_water.clone()),
419            simulation: Box::new(data.simulation.clone()),
420            stats: Default::default(),
421        }
422    }
423}