oxygengine_procedural/
world_2d_climate_simulation.rs

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