hassium_procedural/
world_2d_climate_simulation.rs

1#![allow(dead_code)]
2
3use crate::world_2d::{World2dField, World2dSimulation};
4use hassium_utils::grid_2d::{Grid2d, Grid2dNeighborSample};
5use psyche_utils::switch::Switch;
6#[cfg(feature = "parallel")]
7use rayon::prelude::*;
8use serde::{Deserialize, Serialize};
9use std::{
10    any::Any,
11    f64::consts::{E, PI},
12    ops::{Add, Div, Mul, Neg, Range, Sub},
13};
14
15#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub struct World2dClimateSimulationVector(pub f64, pub f64);
17
18impl World2dClimateSimulationVector {
19    #[inline]
20    pub fn neg_x(&self) -> Self {
21        Self(-self.0, self.1)
22    }
23
24    #[inline]
25    pub fn neg_y(&self) -> Self {
26        Self(self.0, -self.1)
27    }
28
29    #[inline]
30    pub fn dot(&self, other: Self) -> f64 {
31        self.0 * other.0 + self.1 * other.1
32    }
33
34    #[inline]
35    pub fn refract(&self, normal: Self) -> Self {
36        let len = self.magnitude();
37        let dot = self.dot(normal);
38        let offset = if dot >= 0.0 {
39            normal * -dot * 2.0
40        } else {
41            normal * dot * 2.0
42        };
43        (*self + offset).normalized() * len
44    }
45
46    #[inline]
47    pub fn magnitude_sqr(&self) -> f64 {
48        self.0 * self.0 + self.1 * self.1
49    }
50
51    #[inline]
52    pub fn magnitude(&self) -> f64 {
53        self.magnitude_sqr().sqrt()
54    }
55
56    #[inline]
57    pub fn normalized(&self) -> Self {
58        let mag = self.magnitude();
59        if mag > 0.0 {
60            Self(self.0 / mag, self.1 / mag)
61        } else {
62            Self(0.0, 0.0)
63        }
64    }
65}
66
67impl From<(f64, f64)> for World2dClimateSimulationVector {
68    fn from((x, y): (f64, f64)) -> Self {
69        Self(x, y)
70    }
71}
72
73impl Into<(f64, f64)> for World2dClimateSimulationVector {
74    fn into(self) -> (f64, f64) {
75        (self.0, self.1)
76    }
77}
78
79impl Neg for World2dClimateSimulationVector {
80    type Output = Self;
81
82    fn neg(self) -> Self {
83        Self(-self.0, -self.1)
84    }
85}
86
87impl Add for World2dClimateSimulationVector {
88    type Output = Self;
89
90    fn add(self, other: Self) -> Self {
91        Self(self.0 + other.0, self.1 + other.1)
92    }
93}
94
95impl Add<f64> for World2dClimateSimulationVector {
96    type Output = Self;
97
98    fn add(self, other: f64) -> Self {
99        Self(self.0 + other, self.1 + other)
100    }
101}
102
103impl Sub for World2dClimateSimulationVector {
104    type Output = Self;
105
106    fn sub(self, other: Self) -> Self {
107        Self(self.0 - other.0, self.1 - other.1)
108    }
109}
110
111impl Sub<f64> for World2dClimateSimulationVector {
112    type Output = Self;
113
114    fn sub(self, other: f64) -> Self {
115        Self(self.0 - other, self.1 - other)
116    }
117}
118
119impl Mul for World2dClimateSimulationVector {
120    type Output = Self;
121
122    fn mul(self, other: Self) -> Self {
123        Self(self.0 * other.0, self.1 * other.1)
124    }
125}
126
127impl Mul<f64> for World2dClimateSimulationVector {
128    type Output = Self;
129
130    fn mul(self, other: f64) -> Self {
131        Self(self.0 * other, self.1 * other)
132    }
133}
134
135impl Div for World2dClimateSimulationVector {
136    type Output = Self;
137
138    fn div(self, other: Self) -> Self {
139        Self(self.0 / other.0, self.1 / other.1)
140    }
141}
142
143impl Div<f64> for World2dClimateSimulationVector {
144    type Output = Self;
145
146    fn div(self, other: f64) -> Self {
147        Self(self.0 / other, self.1 / other)
148    }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct World2dClimateSimulationConfig {
153    pub world_axis_angle: f64,
154    pub full_year_steps: usize,
155    pub mass_diffuse_factor: f64,
156    pub mass_diffuse_iterations: usize,
157    pub mass_advect_factor: f64,
158    pub viscosity_factor: f64,
159    pub viscosity_iterations: usize,
160    pub poisson_pressure_iterations: usize,
161    pub slopeness_refraction_power: f64,
162    pub water_capacity: f64,
163    pub altitude_range: Range<f64>,
164    pub temperature_range: Range<f64>,
165    pub humidity_limit_range: Range<f64>,
166    pub rainfall_factor: f64,
167    pub evaporation_factor: f64,
168    pub world_core_heating: f64,
169    pub sun_heating: f64,
170    pub sun_heating_adaptive_correction_factor: f64,
171    pub sun_heating_absorption_surface_water_range: Range<f64>,
172    pub thermal_radiation: f64,
173}
174
175impl Default for World2dClimateSimulationConfig {
176    fn default() -> Self {
177        Self {
178            world_axis_angle: 5.0 * PI / 180.0,
179            full_year_steps: 364,
180            mass_diffuse_factor: 0.0001,
181            mass_diffuse_iterations: 5,
182            mass_advect_factor: 1.0,
183            viscosity_factor: 0.0001,
184            viscosity_iterations: 5,
185            poisson_pressure_iterations: 5,
186            slopeness_refraction_power: 3.0,
187            water_capacity: 100.0,
188            altitude_range: 0.0..100.0,
189            temperature_range: 0.0..100.0,
190            humidity_limit_range: 0.05..0.2,
191            rainfall_factor: 0.1,
192            evaporation_factor: 0.05,
193            world_core_heating: 1.0,
194            sun_heating: 0.0,
195            sun_heating_adaptive_correction_factor: 1.0,
196            sun_heating_absorption_surface_water_range: 1.0..0.01,
197            thermal_radiation: 1.0,
198        }
199    }
200}
201
202#[derive(Default)]
203pub struct World2dClimateSimulation {
204    config: World2dClimateSimulationConfig,
205    steps: usize,
206    years: usize,
207    velocity: Option<Switch<Grid2d<World2dClimateSimulationVector>>>,
208    divergence: Option<Grid2d<f64>>,
209    pressure: Option<Switch<Grid2d<f64>>>,
210    slopeness: Option<Grid2d<World2dClimateSimulationVector>>,
211}
212
213impl World2dClimateSimulation {
214    pub fn new(config: World2dClimateSimulationConfig) -> Self {
215        Self {
216            config,
217            steps: 0,
218            years: 0,
219            velocity: None,
220            divergence: None,
221            pressure: None,
222            slopeness: None,
223        }
224    }
225
226    pub fn config(&self) -> &World2dClimateSimulationConfig {
227        &self.config
228    }
229
230    pub fn config_mut(&mut self) -> &mut World2dClimateSimulationConfig {
231        &mut self.config
232    }
233
234    pub fn steps(&self) -> usize {
235        self.steps
236    }
237
238    pub fn years(&self) -> usize {
239        self.years
240    }
241
242    pub fn velocity(&self) -> Option<&Grid2d<World2dClimateSimulationVector>> {
243        if let Some(velocity) = &self.velocity {
244            velocity.get()
245        } else {
246            None
247        }
248    }
249
250    pub fn divergence(&self) -> Option<&Grid2d<f64>> {
251        if let Some(divergence) = &self.divergence {
252            Some(divergence)
253        } else {
254            None
255        }
256    }
257
258    pub fn pressure(&self) -> Option<&Grid2d<f64>> {
259        if let Some(pressure) = &self.pressure {
260            pressure.get()
261        } else {
262            None
263        }
264    }
265
266    pub fn slopeness(&self) -> Option<&Grid2d<World2dClimateSimulationVector>> {
267        if let Some(slopeness) = &self.slopeness {
268            Some(slopeness)
269        } else {
270            None
271        }
272    }
273
274    pub fn rebuild_slopeness(&mut self) {
275        self.slopeness = None;
276    }
277
278    fn heat_exchange(
279        &mut self,
280        temperature: &mut World2dField,
281        surface_water: &Grid2d<f64>,
282        seasons_phase: f64,
283    ) {
284        let temperature = temperature.get_mut().unwrap();
285        let rows = temperature.rows() as f64;
286        let cols = temperature.cols() as f64;
287        let world_core_heating = self.config.world_core_heating.max(0.0);
288        let thermal_radiation = self.config.thermal_radiation.max(0.0);
289        let sun_heating = self.config.sun_heating.max(0.0);
290        let absorption_diff = self.config.sun_heating_absorption_surface_water_range.end
291            - self.config.sun_heating_absorption_surface_water_range.start;
292        let target_average_temp =
293            (self.config.temperature_range.start + self.config.temperature_range.end) * 0.5;
294        temperature.with(|col, row, value| {
295            let water = surface_water[(col, row)];
296            let f = if self.config.water_capacity > 0.0 {
297                (water / self.config.water_capacity).max(0.0).min(1.0)
298            } else {
299                0.0
300            };
301            let absorption =
302                self.config.sun_heating_absorption_surface_water_range.start + absorption_diff * f;
303            let f = (PI * ((row as f64 + 0.5) / rows + seasons_phase)).sin();
304            let sun_value = (sun_heating * f * absorption).max(0.0);
305            value + world_core_heating + sun_value - thermal_radiation
306        });
307        let size = cols * rows;
308        #[cfg(not(feature = "parallel"))]
309        let average_temp = temperature.iter().sum::<f64>() / size;
310        #[cfg(feature = "parallel")]
311        let average_temp = temperature.par_iter().sum::<f64>() / size;
312        let dtemp = logistic_sigmoid_simple_signed(target_average_temp - average_temp);
313        let f = self.config.sun_heating_adaptive_correction_factor;
314        self.config.sun_heating = (self.config.sun_heating + dtemp * f).max(0.0);
315    }
316
317    fn surface_water_transfer(&self, altitude: &Grid2d<f64>, surface_water: &mut World2dField) {
318        let surface_water = surface_water.iterate().unwrap();
319        diffuse_with_barriers(altitude, surface_water.0, surface_water.1);
320    }
321
322    fn rainfall_and_evaporation(
323        &self,
324        surface_water: &mut World2dField,
325        humidity: &mut World2dField,
326        temperature: &Grid2d<f64>,
327    ) {
328        if self.config.water_capacity <= 0.0 {
329            return;
330        }
331        let surface_water = surface_water.iterate().unwrap();
332        let humidity = humidity.iterate().unwrap();
333        #[cfg(not(feature = "parallel"))]
334        {
335            let it = surface_water
336                .0
337                .iter()
338                .zip(surface_water.1.iter_mut())
339                .zip(humidity.0.iter())
340                .zip(humidity.1.iter_mut())
341                .zip(temperature.iter());
342            for ((((swp, swn), hp), hn), t) in it {
343                let limit = remap_in_ranges(
344                    *t,
345                    self.config.temperature_range.clone(),
346                    self.config.humidity_limit_range.clone(),
347                );
348                let h = *hp - limit;
349                let h = if h > 0.0 {
350                    h * self.config.rainfall_factor
351                } else {
352                    h * self.config.evaporation_factor
353                };
354                let w = (h * self.config.water_capacity).max(-swp);
355                let h = w / self.config.water_capacity;
356                *hn = hp - h;
357                *swn = swp + w;
358            }
359        }
360        #[cfg(feature = "parallel")]
361        {
362            surface_water
363                .0
364                .par_iter()
365                .zip(surface_water.1.par_iter_mut())
366                .zip(humidity.0.par_iter())
367                .zip(humidity.1.par_iter_mut())
368                .zip(temperature.par_iter())
369                .for_each(|((((swp, swn), hp), hn), t)| {
370                    let limit = remap_in_ranges(
371                        *t,
372                        self.config.temperature_range.clone(),
373                        self.config.humidity_limit_range.clone(),
374                    );
375                    let h = *hp - limit;
376                    let h = if h > 0.0 {
377                        h * self.config.rainfall_factor
378                    } else {
379                        h * self.config.evaporation_factor
380                    };
381                    let w = (h * self.config.water_capacity).max(-swp);
382                    let h = w / self.config.water_capacity;
383                    *hn = hp - h;
384                    *swn = swp + w;
385                });
386        }
387    }
388}
389
390impl World2dSimulation for World2dClimateSimulation {
391    fn initialize_world(
392        &mut self,
393        altitude: &mut Grid2d<f64>,
394        temperature: &mut Grid2d<f64>,
395        _humidity: &mut Grid2d<f64>,
396        _surface_water: &mut Grid2d<f64>,
397    ) {
398        self.velocity = Some(Switch::new(
399            2,
400            Grid2d::new(altitude.cols(), altitude.rows(), (0.0, 0.0).into()),
401        ));
402        self.divergence = Some(Grid2d::new(altitude.cols(), altitude.rows(), 0.0));
403        self.pressure = Some(Switch::new(
404            2,
405            Grid2d::new(altitude.cols(), altitude.rows(), 0.0),
406        ));
407        let cols = altitude.cols();
408        let rows = altitude.rows();
409        let diff = self.config.altitude_range.end - self.config.altitude_range.start;
410        let mut slopeness = Grid2d::new(cols, rows, (0.0, 0.0).into());
411        slopeness.with(|col, row, _| {
412            if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
413                (0.0, 0.0).into()
414            } else {
415                let left = altitude[(col - 1, row)];
416                let right = altitude[(col + 1, row)];
417                let top = altitude[(col, row - 1)];
418                let bottom = altitude[(col, row + 1)];
419                let dx = (right - left) / diff;
420                let dy = (bottom - top) / diff;
421                (dx, dy).into()
422            }
423        });
424        self.slopeness = Some(slopeness);
425        let diff = self.config.temperature_range.end - self.config.temperature_range.start;
426        temperature.with(|_, row, _| {
427            let f = (PI * ((row as f64 + 0.5) / rows as f64)).sin();
428            self.config.temperature_range.start + diff * f
429        });
430    }
431
432    fn process_world(
433        &mut self,
434        altitude: &mut World2dField,
435        temperature: &mut World2dField,
436        humidity: &mut World2dField,
437        surface_water: &mut World2dField,
438    ) {
439        let steps = self.steps + 1;
440        if steps >= self.config.full_year_steps {
441            self.years += 1;
442            self.steps = 0;
443        } else {
444            self.steps = steps;
445        }
446        let seasons_phase = ((self.steps as f64 / self.config.full_year_steps as f64) * PI * 2.0)
447            .sin()
448            * self.config.world_axis_angle.sin();
449
450        self.heat_exchange(temperature, surface_water.get().unwrap(), seasons_phase);
451        self.surface_water_transfer(altitude.get().unwrap(), surface_water);
452        self.rainfall_and_evaporation(surface_water, humidity, temperature.get().unwrap());
453
454        // process temperature and humidity as density fields.
455        {
456            {
457                diffuse_scalar(
458                    temperature,
459                    self.config.mass_diffuse_iterations,
460                    self.config.mass_diffuse_factor,
461                );
462            }
463            {
464                diffuse_scalar(
465                    humidity,
466                    self.config.mass_diffuse_iterations,
467                    self.config.mass_diffuse_factor,
468                );
469            }
470            if let Some(velocity) = &self.velocity {
471                let velocity = velocity.get().unwrap();
472                {
473                    let temperature = temperature.iterate().unwrap();
474                    let temperature_prev = temperature.0;
475                    let temperature_next = temperature.1;
476                    advect_scalar(
477                        temperature_prev,
478                        temperature_next,
479                        velocity,
480                        self.config.mass_advect_factor,
481                    );
482                }
483                {
484                    let humidity = humidity.iterate().unwrap();
485                    let humidity_prev = humidity.0;
486                    let humidity_next = humidity.1;
487                    advect_scalar(
488                        humidity_prev,
489                        humidity_next,
490                        velocity,
491                        self.config.mass_advect_factor,
492                    );
493                }
494            }
495        }
496        // process velocity flow.
497        {
498            if let Some(velocity) = &mut self.velocity {
499                if let Some(divergence) = &mut self.divergence {
500                    if let Some(pressure) = &mut self.pressure {
501                        // modify velocities by slopeness
502                        if self.config.slopeness_refraction_power > 0.0 {
503                            if let Some(slopeness) = &self.slopeness {
504                                {
505                                    let velocity = velocity.get_mut().unwrap();
506                                    consider_obstacles(
507                                        velocity,
508                                        slopeness,
509                                        self.config.slopeness_refraction_power,
510                                    );
511                                }
512                                // TODO: test if it is needed.
513                                conserve_mass(
514                                    velocity,
515                                    pressure,
516                                    divergence,
517                                    self.config.poisson_pressure_iterations,
518                                );
519                            }
520                        }
521                        // diffuse velocity
522                        {
523                            diffuse_vector(
524                                velocity,
525                                self.config.viscosity_iterations,
526                                self.config.viscosity_factor,
527                            );
528                            conserve_mass(
529                                velocity,
530                                pressure,
531                                divergence,
532                                self.config.poisson_pressure_iterations,
533                            );
534                        }
535                        // velocity flow
536                        {
537                            {
538                                let velocity = velocity.iterate().unwrap();
539                                let velocity_prev = velocity.0;
540                                let velocity_next = velocity.1;
541                                advect_vector(
542                                    velocity_prev,
543                                    velocity_next,
544                                    velocity_prev,
545                                    self.config.mass_advect_factor,
546                                );
547                            }
548                            conserve_mass(
549                                velocity,
550                                pressure,
551                                divergence,
552                                self.config.poisson_pressure_iterations,
553                            );
554                        }
555                    }
556                }
557            }
558        }
559    }
560
561    fn as_any(&self) -> &dyn Any {
562        self
563    }
564}
565
566impl From<&World2dClimateSimulationData> for World2dClimateSimulation {
567    fn from(data: &World2dClimateSimulationData) -> Self {
568        Self {
569            config: data.config.clone(),
570            steps: data.steps,
571            years: data.years,
572            velocity: if let Some(ref velocity) = data.velocity {
573                Some(Switch::new(2, velocity.clone()))
574            } else {
575                None
576            },
577            divergence: data.divergence.clone(),
578            pressure: if let Some(ref pressure) = data.pressure {
579                Some(Switch::new(2, pressure.clone()))
580            } else {
581                None
582            },
583            slopeness: data.slopeness.clone(),
584        }
585    }
586}
587
588fn apply_duplicate_boundaries(field: &mut Grid2d<f64>) {
589    let cols = field.cols();
590    let rows = field.rows();
591    for col in 1..(cols - 1) {
592        field[(col, 0)] = field[(col, 1)];
593        field[(col, rows - 1)] = field[(col, rows - 2)];
594    }
595    for row in 1..(rows - 1) {
596        field[(0, row)] = field[(1, row)];
597        field[(cols - 1, row)] = field[(cols - 2, row)];
598    }
599    field[(0, 0)] = field[(1, 1)];
600    field[(cols - 1, 0)] = field[(cols - 2, 1)];
601    field[(cols - 1, rows - 1)] = field[(cols - 2, rows - 2)];
602    field[(0, rows - 1)] = field[(1, rows - 2)];
603}
604
605fn apply_mirror_boundaries(field: &mut Grid2d<World2dClimateSimulationVector>) {
606    let cols = field.cols();
607    let rows = field.rows();
608    for col in 1..(cols - 1) {
609        field[(col, 0)] = field[(col, 1)].neg_y();
610        field[(col, rows - 1)] = field[(col, rows - 2)].neg_y();
611    }
612    for row in 1..(rows - 1) {
613        field[(0, row)] = field[(1, row)].neg_x();
614        field[(cols - 1, row)] = field[(cols - 2, row)].neg_x();
615    }
616    field[(0, 0)] = (field[(0, 1)] + field[(1, 0)]) * 0.5;
617    field[(cols - 1, 0)] = (field[(cols - 2, 0)] + field[(cols - 1, 1)]) * 0.5;
618    field[(cols - 1, rows - 1)] = (field[(cols - 2, rows - 1)] + field[(cols - 1, rows - 2)]) * 0.5;
619    field[(0, rows - 1)] = (field[(1, rows - 1)] + field[(0, rows - 2)]) * 0.5;
620}
621
622fn consider_obstacles(
623    velocity: &mut Grid2d<World2dClimateSimulationVector>,
624    slopeness: &Grid2d<World2dClimateSimulationVector>,
625    refraction_power: f64,
626) {
627    let cols = velocity.cols();
628    let rows = velocity.rows();
629    velocity.with(|col, row, value| {
630        if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
631            (0.0, 0.0).into()
632        } else {
633            let slope = slopeness[(col, row)];
634            let normal = slope.normalized();
635            let scale = 1.0 - slope.magnitude();
636            let scale = 1.0 - scale.powf(refraction_power);
637            value.refract(normal * scale)
638        }
639    });
640    apply_mirror_boundaries(velocity);
641}
642
643// a.k.a. Jacobi
644fn diffuse_scalar(field: &mut World2dField, iterations: usize, factor: f64) {
645    let cols = field.get().unwrap().cols();
646    let rows = field.get().unwrap().rows();
647    let fa = (cols as f64 * rows as f64) * factor;
648    let fb = 1.0 / (1.0 + 4.0 * fa);
649    for _ in 0..iterations {
650        let field = field.iterate().unwrap();
651        let field_prev = field.0;
652        let field_next = field.1;
653        field_next.with(|col, row, _| {
654            if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
655                0.0
656            } else {
657                let center = field_prev[(col, row)];
658                let left = field_prev[(col - 1, row)];
659                let right = field_prev[(col + 1, row)];
660                let top = field_prev[(col, row - 1)];
661                let bottom = field_prev[(col, row + 1)];
662                ((left + right + top + bottom) * fa + center) * fb
663            }
664        });
665        apply_duplicate_boundaries(field_next);
666    }
667}
668
669// a.k.a. Jacobi
670fn diffuse_vector(
671    field: &mut Switch<Grid2d<World2dClimateSimulationVector>>,
672    iterations: usize,
673    factor: f64,
674) {
675    let cols = field.get().unwrap().cols();
676    let rows = field.get().unwrap().rows();
677    let fa = (cols as f64 * rows as f64) * factor;
678    let fb = 1.0 / (1.0 + 4.0 * fa);
679    for _ in 0..iterations {
680        let field = field.iterate().unwrap();
681        let field_prev = field.0;
682        let field_next = field.1;
683        field_next.with(|col, row, _| {
684            if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
685                (0.0, 0.0).into()
686            } else {
687                let center = field_prev[(col, row)];
688                let left = field_prev[(col - 1, row)];
689                let right = field_prev[(col + 1, row)];
690                let top = field_prev[(col, row - 1)];
691                let bottom = field_prev[(col, row + 1)];
692                ((left + right + top + bottom) * fa + center) * fb
693            }
694        });
695        apply_mirror_boundaries(field_next);
696    }
697}
698
699fn advect_scalar(
700    density_prev: &Grid2d<f64>,
701    density_next: &mut Grid2d<f64>,
702    velocity: &Grid2d<World2dClimateSimulationVector>,
703    factor: f64,
704) {
705    let cols = density_prev.cols();
706    let rows = density_prev.rows();
707    density_next.with(|col, row, _| {
708        if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
709            0.0
710        } else {
711            let vel = velocity[(col, row)];
712            let x = (col as f64 - factor * vel.0)
713                .min(cols as f64 - 1.5)
714                .max(0.5);
715            let y = (row as f64 - factor * vel.1)
716                .min(rows as f64 - 1.5)
717                .max(0.5);
718            let xa = x as usize;
719            let ya = y as usize;
720            let xb = xa + 1;
721            let yb = ya + 1;
722            let dx1 = x - xa as f64;
723            let dx0 = 1.0 - dx1;
724            let dy1 = y - ya as f64;
725            let dy0 = 1.0 - dy1;
726            let val_xaya = density_prev[(xa, ya)];
727            let val_xayb = density_prev[(xa, yb)];
728            let val_xbya = density_prev[(xb, ya)];
729            let val_xbyb = density_prev[(xb, yb)];
730            (val_xaya * dy0 + val_xayb * dy1) * dx0 + (val_xbya * dy0 + val_xbyb * dy1) * dx1
731        }
732    });
733    apply_duplicate_boundaries(density_next);
734}
735
736fn advect_vector(
737    field_prev: &Grid2d<World2dClimateSimulationVector>,
738    field_next: &mut Grid2d<World2dClimateSimulationVector>,
739    velocity: &Grid2d<World2dClimateSimulationVector>,
740    factor: f64,
741) {
742    let cols = field_prev.cols();
743    let rows = field_prev.rows();
744    field_next.with(|col, row, _| {
745        if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
746            (0.0, 0.0).into()
747        } else {
748            let vel = velocity[(col, row)];
749            let x = (col as f64 - factor * vel.0)
750                .min(cols as f64 - 1.5)
751                .max(0.5);
752            let y = (row as f64 - factor * vel.1)
753                .min(rows as f64 - 1.5)
754                .max(0.5);
755            let xa = x as usize;
756            let ya = y as usize;
757            let xb = xa + 1;
758            let yb = ya + 1;
759            let dx1 = x - xa as f64;
760            let dx0 = 1.0 - dx1;
761            let dy1 = y - ya as f64;
762            let dy0 = 1.0 - dy1;
763            let val_xaya = field_prev[(xa, ya)];
764            let val_xayb = field_prev[(xa, yb)];
765            let val_xbya = field_prev[(xb, ya)];
766            let val_xbyb = field_prev[(xb, yb)];
767            (val_xaya * dy0 + val_xayb * dy1) * dx0 + (val_xbya * dy0 + val_xbyb * dy1) * dx1
768        }
769    });
770    apply_mirror_boundaries(field_next);
771}
772
773fn conserve_mass(
774    velocity: &mut Switch<Grid2d<World2dClimateSimulationVector>>,
775    pressure: &mut World2dField,
776    divergence: &mut Grid2d<f64>,
777    poisson_pressure_iterations: usize,
778) {
779    {
780        let velocity = velocity.get().unwrap();
781        calculate_poisson_pressure(velocity, pressure, divergence, poisson_pressure_iterations);
782    }
783    {
784        let velocity = velocity.iterate().unwrap();
785        let velocity_prev = velocity.0;
786        let velocity_next = velocity.1;
787        let pressure = pressure.get().unwrap();
788        calculate_convergence_from_pressure_gradient(velocity_prev, velocity_next, pressure);
789    }
790}
791
792fn calculate_poisson_pressure(
793    velocity: &Grid2d<World2dClimateSimulationVector>,
794    pressure: &mut World2dField,
795    divergence: &mut Grid2d<f64>,
796    iterations: usize,
797) {
798    let cols = divergence.cols();
799    let rows = divergence.rows();
800    divergence.with(|col, row, _| {
801        if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
802            0.0
803        } else {
804            let left = velocity[(col - 1, row)].0;
805            let right = velocity[(col + 1, row)].0;
806            let top = velocity[(col, row - 1)].1;
807            let bottom = velocity[(col, row + 1)].1;
808            -0.5 * (right - left + bottom - top)
809        }
810    });
811    apply_duplicate_boundaries(divergence);
812    pressure.get_mut().unwrap().with(|_, _, _| 0.0);
813    for _ in 0..iterations {
814        let pressure = pressure.iterate().unwrap();
815        let pressure_prev = pressure.0;
816        let pressure_next = pressure.1;
817        pressure_next.with(|col, row, _| {
818            if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
819                0.0
820            } else {
821                let div = divergence[(col, row)];
822                let left = pressure_prev[(col - 1, row)];
823                let right = pressure_prev[(col + 1, row)];
824                let top = pressure_prev[(col, row - 1)];
825                let bottom = pressure_prev[(col, row + 1)];
826                (div + left + right + top + bottom) * 0.25
827            }
828        });
829        apply_duplicate_boundaries(pressure_next);
830    }
831}
832
833fn calculate_convergence_from_pressure_gradient(
834    velocity_prev: &Grid2d<World2dClimateSimulationVector>,
835    velocity_next: &mut Grid2d<World2dClimateSimulationVector>,
836    pressure: &Grid2d<f64>,
837) {
838    let cols = velocity_prev.cols();
839    let rows = velocity_prev.rows();
840    velocity_next.with(|col, row, _| {
841        if col == 0 || col == cols - 1 || row == 0 || row == rows - 1 {
842            (0.0, 0.0).into()
843        } else {
844            let left = pressure[(col - 1, row)];
845            let right = pressure[(col + 1, row)];
846            let top = pressure[(col, row - 1)];
847            let bottom = pressure[(col, row + 1)];
848            let vel = velocity_prev[(col, row)];
849            let x = vel.0 - 0.5 * (right - left);
850            let y = vel.1 - 0.5 * (bottom - top);
851            (x, y).into()
852        }
853    });
854    apply_mirror_boundaries(velocity_next);
855}
856
857fn diffuse_with_barriers(
858    barriers: &Grid2d<f64>,
859    field_prev: &Grid2d<f64>,
860    field_next: &mut Grid2d<f64>,
861) {
862    let levels = (barriers + field_prev).unwrap();
863    field_next.with(|col, row, _| {
864        let sample_coord = (if col == 0 { 0 } else { 1 }, if row == 0 { 0 } else { 1 });
865        let barriers_sample = barriers.neighbor_sample((col, row));
866        let values_sample = field_prev.neighbor_sample((col, row));
867        let levels_sample = levels.neighbor_sample((col, row));
868        let levels_min = *levels_sample
869            .iter()
870            .min_by(|a, b| a.partial_cmp(b).unwrap())
871            .unwrap();
872        let levels_max = *levels_sample
873            .iter()
874            .max_by(|a, b| a.partial_cmp(b).unwrap())
875            .unwrap();
876        let capacity_sample = barriers_sample.map(|_, _, v| levels_max - v.max(levels_min));
877        let capacity = capacity_sample.iter().sum::<f64>();
878        if capacity > 0.0 {
879            let energy_sample = Grid2dNeighborSample::from((
880                levels_sample.cols(),
881                levels_sample
882                    .iter()
883                    .zip(barriers_sample.iter())
884                    .map(|(v, b)| *v - b.max(levels_min)),
885            ));
886            let energy = energy_sample.iter().sum::<f64>();
887            let amount = energy * capacity_sample[sample_coord] / capacity;
888            values_sample[sample_coord] - energy_sample[sample_coord] + amount
889        } else {
890            values_sample[sample_coord]
891        }
892    });
893    // error correction.
894    #[cfg(not(feature = "parallel"))]
895    let before = field_prev.iter().sum::<f64>();
896    #[cfg(feature = "parallel")]
897    let before = field_prev.par_iter().sum::<f64>();
898    #[cfg(not(feature = "parallel"))]
899    let after = field_next.iter().sum::<f64>();
900    #[cfg(feature = "parallel")]
901    let after = field_next.par_iter().sum::<f64>();
902    let diff = (before - after) / field_prev.len() as f64;
903    field_next.with(|_, _, value| *value + diff);
904}
905
906#[inline]
907fn unproject_in_range(value: f64, range: Range<f64>) -> f64 {
908    (value - range.start) / (range.end - range.start)
909}
910
911#[inline]
912fn project_in_range(factor: f64, range: Range<f64>) -> f64 {
913    (range.end - range.start) * factor + range.start
914}
915
916#[inline]
917fn remap_in_ranges(value: f64, from: Range<f64>, to: Range<f64>) -> f64 {
918    project_in_range(unproject_in_range(value, from).max(0.0).min(1.0), to)
919}
920
921#[inline]
922fn logistic_sigmoid_simple(value: f64) -> f64 {
923    logistic_sigmoid_advanced(value, 1.0, 0.0, 1.0)
924}
925
926#[inline]
927fn logistic_sigmoid_simple_signed(value: f64) -> f64 {
928    logistic_sigmoid_advanced_signed(value, 1.0, 0.0, 1.0)
929}
930
931#[inline]
932fn logistic_sigmoid_advanced(
933    value: f64,
934    curve_max: f64,
935    midpoint: f64,
936    logistic_growth: f64,
937) -> f64 {
938    let power = -logistic_growth * (value - midpoint);
939    let denominator = 1.0 + E.powf(power);
940    curve_max / denominator
941}
942
943#[inline]
944fn logistic_sigmoid_advanced_signed(
945    value: f64,
946    curve_max: f64,
947    midpoint: f64,
948    logistic_growth: f64,
949) -> f64 {
950    (logistic_sigmoid_advanced(value.abs(), curve_max, midpoint, logistic_growth) - 0.5)
951        * 2.0
952        * value
953}
954
955#[derive(Clone, Serialize, Deserialize)]
956pub struct World2dClimateSimulationData {
957    config: World2dClimateSimulationConfig,
958    steps: usize,
959    years: usize,
960    velocity: Option<Grid2d<World2dClimateSimulationVector>>,
961    divergence: Option<Grid2d<f64>>,
962    pressure: Option<Grid2d<f64>>,
963    slopeness: Option<Grid2d<World2dClimateSimulationVector>>,
964}
965
966impl From<&World2dClimateSimulation> for World2dClimateSimulationData {
967    fn from(sim: &World2dClimateSimulation) -> Self {
968        Self {
969            config: sim.config.clone(),
970            steps: sim.steps,
971            years: sim.years,
972            velocity: if let Some(ref velocity) = sim.velocity {
973                Some(velocity.get().unwrap().clone())
974            } else {
975                None
976            },
977            divergence: sim.divergence.clone(),
978            pressure: if let Some(ref pressure) = sim.pressure {
979                Some(pressure.get().unwrap().clone())
980            } else {
981                None
982            },
983            slopeness: sim.slopeness.clone(),
984        }
985    }
986}