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 {
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 {
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 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 conserve_mass(
514 velocity,
515 pressure,
516 divergence,
517 self.config.poisson_pressure_iterations,
518 );
519 }
520 }
521 {
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 {
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
643fn 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
669fn 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 #[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}