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 {
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 {
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 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 conserve_mass(
520 velocity,
521 pressure,
522 divergence,
523 self.config.poisson_pressure_iterations,
524 );
525 }
526 }
527 {
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 {
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
647fn 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
673fn 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 #[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}