veloren_server/weather/
sim.rs1use common::{
2 grid::Grid,
3 resources::TimeOfDay,
4 weather::{CELL_SIZE, CHUNKS_PER_CELL, Weather, WeatherGrid},
5};
6use noise::{NoiseFn, Perlin, SuperSimplex, Turbulence};
7use vek::*;
8use world::World;
9
10use crate::weather::WEATHER_DT;
11
12fn cell_to_wpos_center(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 + CELL_SIZE as i32 / 2 }
13
14#[derive(Clone)]
15struct WeatherZone {
16 weather: Weather,
17 time_to_live: f32,
19}
20
21struct CellConsts {
22 humidity: f32,
23}
24
25pub struct WeatherSim {
26 size: Vec2<u32>,
27 consts: Grid<CellConsts>,
28 zones: Grid<Option<WeatherZone>>,
29}
30
31#[derive(Default)]
33pub struct LightningCells {
34 pub cells: Vec<Vec2<i32>>,
35}
36
37impl WeatherSim {
38 pub fn new(size: Vec2<u32>, world: &World) -> Self {
39 Self {
40 size,
41 consts: Grid::from_raw(
42 size.as_(),
43 (0..size.x * size.y)
44 .map(|i| Vec2::new(i % size.x, i / size.x))
45 .map(|p| {
46 let mut humid_sum = 0.0;
47
48 for y in 0..CHUNKS_PER_CELL {
49 for x in 0..CHUNKS_PER_CELL {
50 let chunk_pos = p * CHUNKS_PER_CELL + Vec2::new(x, y);
51 if let Some(chunk) = world.sim().get(chunk_pos.as_()) {
52 let env = chunk.get_environment();
53 humid_sum += env.humid;
54 }
55 }
56 }
57 let average_humid = humid_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32;
58 CellConsts {
59 humidity: average_humid.powf(0.2).min(1.0),
60 }
61 })
62 .collect::<Vec<_>>(),
63 ),
64 zones: Grid::new(size.as_(), None),
65 }
66 }
67
68 pub fn add_zone(&mut self, weather: Weather, pos: Vec2<f32>, radius: f32, time: f32) {
71 let min: Vec2<i32> = (pos - radius).as_::<i32>().map(|e| e.max(0));
72 let max: Vec2<i32> = (pos + radius)
73 .ceil()
74 .as_::<i32>()
75 .map2(self.size.as_::<i32>(), |a, b| a.min(b));
76 for y in min.y..max.y {
77 for x in min.x..max.x {
78 let ipos = Vec2::new(x, y);
79 let p = ipos.as_::<f32>();
80
81 if p.distance_squared(pos) < radius.powi(2) {
82 self.zones[ipos] = Some(WeatherZone {
83 weather,
84 time_to_live: time,
85 });
86 }
87 }
88 }
89 }
90
91 pub fn tick(&mut self, time_of_day: TimeOfDay, out: &mut WeatherGrid) -> LightningCells {
93 let time = time_of_day.0;
94
95 let base_nz: Turbulence<Turbulence<SuperSimplex, Perlin>, Perlin> = Turbulence::new(
96 Turbulence::new(SuperSimplex::new(0))
97 .set_frequency(0.2)
98 .set_power(1.5),
99 )
100 .set_frequency(2.0)
101 .set_power(0.2);
102
103 let rain_nz = SuperSimplex::new(0);
104
105 let mut lightning_cells = Vec::new();
106 for (point, cell) in out.iter_mut() {
107 if let Some(zone) = &mut self.zones[point] {
108 *cell = zone.weather;
109 zone.time_to_live -= WEATHER_DT;
110 if zone.time_to_live <= 0.0 {
111 self.zones[point] = None;
112 }
113 } else {
114 let wpos = cell_to_wpos_center(point);
115
116 let pos = wpos.as_::<f64>() + time * 0.1;
117
118 let space_scale = 7_500.0;
119 let time_scale = 100_000.0;
120 let spos = (pos / space_scale).with_z(time / time_scale);
121
122 let avg_scale = 30_000.0;
123 let avg_delay = 250_000.0;
124 let pressure = ((base_nz
125 .get((pos / avg_scale).with_z(time / avg_delay).into_array())
126 + base_nz.get(
127 (pos / (avg_scale * 0.25))
128 .with_z(time / (avg_delay * 0.25))
129 .into_array(),
130 ) * 0.5)
131 * 0.5
132 + 1.0)
133 .clamped(0.0, 1.0) as f32
134 + 0.55
135 - self.consts[point].humidity * 0.6;
136
137 const RAIN_CLOUD_THRESHOLD: f32 = 0.25;
138 cell.cloud = (1.0 - pressure).max(0.0).powi(2) * 4.0;
139 cell.rain = ((1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0)
140 * self.consts[point].humidity
141 * 2.5)
142 .powf(0.75);
143 cell.wind = Vec2::new(
144 rain_nz.get(spos.into_array()).powi(3) as f32,
145 rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
146 ) * 200.0
147 * (1.0 - pressure);
148 }
149
150 if cell.rain > 0.2 && cell.cloud > 0.15 {
151 lightning_cells.push(point);
152 }
153 }
154 LightningCells {
155 cells: lightning_cells,
156 }
157 }
158
159 pub fn size(&self) -> Vec2<u32> { self.size }
160}