veloren_common/
weather.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4use vek::{Lerp, Vec2, Vec3};
5
6use crate::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize};
7
8/// Weather::default is Clear, 0 degrees C and no wind
9#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
10pub struct Weather {
11    /// Clouds currently in the area between 0 and 1
12    pub cloud: f32,
13    /// Rain per time, between 0 and 1
14    pub rain: f32,
15    /// Wind velocity in block / second
16    pub wind: Vec2<f32>,
17}
18
19impl Weather {
20    pub fn new(cloud: f32, rain: f32, wind: Vec2<f32>) -> Self { Self { cloud, rain, wind } }
21
22    pub fn get_kind(&self) -> WeatherKind {
23        // Over 24.5 m/s wind is a storm
24        if self.wind.magnitude_squared() >= 24.5f32.powi(2) {
25            WeatherKind::Storm
26        } else if (0.1..=1.0).contains(&self.rain) {
27            WeatherKind::Rain
28        } else if (0.2..=1.0).contains(&self.cloud) {
29            WeatherKind::Cloudy
30        } else {
31            WeatherKind::Clear
32        }
33    }
34
35    pub fn lerp_unclamped(&self, to: &Self, t: f32) -> Self {
36        Self {
37            cloud: f32::lerp_unclamped(self.cloud, to.cloud, t),
38            rain: f32::lerp_unclamped(self.rain, to.rain, t),
39            wind: Vec2::<f32>::lerp_unclamped(self.wind, to.wind, t),
40        }
41    }
42
43    // Get the rain velocity for this weather
44    pub fn rain_vel(&self) -> Vec3<f32> {
45        const FALL_RATE: f32 = 30.0;
46        self.wind.with_z(-FALL_RATE)
47    }
48
49    // Get the wind velocity for this weather
50    pub fn wind_vel(&self) -> Vec2<f32> { self.wind }
51}
52
53#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
54pub enum WeatherKind {
55    Clear,
56    Cloudy,
57    Rain,
58    Storm,
59}
60
61impl fmt::Display for WeatherKind {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            WeatherKind::Clear => write!(f, "Clear"),
65            WeatherKind::Cloudy => write!(f, "Cloudy"),
66            WeatherKind::Rain => write!(f, "Rain"),
67            WeatherKind::Storm => write!(f, "Storm"),
68        }
69    }
70}
71
72// How many chunks wide a weather cell is.
73// So one weather cell has (CHUNKS_PER_CELL * CHUNKS_PER_CELL) chunks.
74pub const CHUNKS_PER_CELL: u32 = 16;
75
76pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
77
78#[derive(Debug, Clone)]
79pub struct WeatherGrid {
80    weather: Grid<Weather>,
81}
82
83/// Weather that's compressed in order to send it to the client.
84#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
85pub struct CompressedWeather {
86    cloud: u8,
87    rain: u8,
88}
89
90impl CompressedWeather {
91    pub fn lerp_unclamped(&self, to: &CompressedWeather, t: f32) -> Weather {
92        Weather {
93            cloud: f32::lerp_unclamped(self.cloud as f32, to.cloud as f32, t) / 255.0,
94            rain: f32::lerp_unclamped(self.rain as f32, to.rain as f32, t) / 255.0,
95            wind: Vec2::zero(),
96        }
97    }
98}
99
100impl From<Weather> for CompressedWeather {
101    fn from(weather: Weather) -> Self {
102        Self {
103            cloud: (weather.cloud * 255.0).round() as u8,
104            rain: (weather.rain * 255.0).round() as u8,
105        }
106    }
107}
108
109impl From<CompressedWeather> for Weather {
110    fn from(weather: CompressedWeather) -> Self {
111        Self {
112            cloud: weather.cloud as f32 / 255.0,
113            rain: weather.rain as f32 / 255.0,
114            wind: Vec2::zero(),
115        }
116    }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct SharedWeatherGrid {
121    weather: Grid<CompressedWeather>,
122}
123
124impl From<&WeatherGrid> for SharedWeatherGrid {
125    fn from(value: &WeatherGrid) -> Self {
126        Self {
127            weather: Grid::from_raw(
128                value.weather.size(),
129                value
130                    .weather
131                    .raw()
132                    .iter()
133                    .copied()
134                    .map(CompressedWeather::from)
135                    .collect::<Vec<_>>(),
136            ),
137        }
138    }
139}
140
141impl From<&SharedWeatherGrid> for WeatherGrid {
142    fn from(value: &SharedWeatherGrid) -> Self {
143        Self {
144            weather: Grid::from_raw(
145                value.weather.size(),
146                value
147                    .weather
148                    .raw()
149                    .iter()
150                    .copied()
151                    .map(Weather::from)
152                    .collect::<Vec<_>>(),
153            ),
154        }
155    }
156}
157
158impl SharedWeatherGrid {
159    pub fn new(size: Vec2<u32>) -> Self {
160        size.map(|e| debug_assert!(i32::try_from(e).is_ok()));
161        Self {
162            weather: Grid::new(size.as_(), CompressedWeather::default()),
163        }
164    }
165
166    pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &CompressedWeather)> {
167        self.weather.iter()
168    }
169
170    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut CompressedWeather)> {
171        self.weather.iter_mut()
172    }
173
174    pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() }
175}
176
177/// Transforms a world position to cell coordinates. Where (0.0, 0.0) in cell
178/// coordinates is the center of the weather cell located at (0, 0) in the grid.
179fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 }
180
181// TODO: Move consts from world to common to avoid duplication
182const LOCALITY: [Vec2<i32>; 9] = [
183    Vec2::new(0, 0),
184    Vec2::new(0, 1),
185    Vec2::new(1, 0),
186    Vec2::new(0, -1),
187    Vec2::new(-1, 0),
188    Vec2::new(1, 1),
189    Vec2::new(1, -1),
190    Vec2::new(-1, 1),
191    Vec2::new(-1, -1),
192];
193
194impl WeatherGrid {
195    pub fn new(size: Vec2<u32>) -> Self {
196        size.map(|e| debug_assert!(i32::try_from(e).is_ok()));
197        Self {
198            weather: Grid::new(size.as_(), Weather::default()),
199        }
200    }
201
202    pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &Weather)> { self.weather.iter() }
203
204    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut Weather)> {
205        self.weather.iter_mut()
206    }
207
208    pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() }
209
210    pub fn get(&self, cell_pos: Vec2<u32>) -> Weather {
211        self.weather
212            .get(cell_pos.as_())
213            .copied()
214            .unwrap_or_default()
215    }
216
217    /// Get the weather at a given world position by doing bilinear
218    /// interpolation between four cells.
219    pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather {
220        let cell_pos = to_cell_pos(wpos);
221        let rpos = cell_pos.map(|e| e.fract() + (1.0 - e.signum()) / 2.0);
222        let cell_pos = cell_pos.map(|e| e.floor());
223
224        let cpos = cell_pos.as_::<i32>();
225        Weather::lerp_unclamped(
226            &Weather::lerp_unclamped(
227                self.weather.get(cpos).unwrap_or(&Weather::default()),
228                self.weather
229                    .get(cpos + Vec2::unit_x())
230                    .unwrap_or(&Weather::default()),
231                rpos.x,
232            ),
233            &Weather::lerp_unclamped(
234                self.weather
235                    .get(cpos + Vec2::unit_y())
236                    .unwrap_or(&Weather::default()),
237                self.weather
238                    .get(cpos + Vec2::one())
239                    .unwrap_or(&Weather::default()),
240                rpos.x,
241            ),
242            rpos.y,
243        )
244    }
245
246    /// Get the max weather near a position
247    pub fn get_max_near(&self, wpos: Vec2<f32>) -> Weather {
248        let cell_pos: Vec2<i32> = to_cell_pos(wpos).as_();
249        LOCALITY
250            .iter()
251            .map(|l| {
252                self.weather
253                    .get(cell_pos + l)
254                    .cloned()
255                    .unwrap_or_default()
256            })
257            .reduce(|a, b| Weather {
258                cloud: a.cloud.max(b.cloud),
259                rain: a.rain.max(b.rain),
260                wind: a.wind.map2(b.wind, |a, b| a.max(b)),
261            })
262            // There will always be 9 elements in locality
263            .unwrap()
264    }
265}