From b7c01961293b2ed5c967a126a6fb601323189593 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 17 Nov 2021 18:09:51 +0100
Subject: [PATCH 01/71] Server weather sim

---
 assets/voxygen/shaders/include/lod.glsl     |   8 +-
 assets/voxygen/shaders/include/sky.glsl     |  23 +-
 assets/voxygen/shaders/sprite-vert.glsl     |   2 +-
 client/src/lib.rs                           |   5 +
 common/net/src/msg/server.rs                |   5 +-
 common/src/lib.rs                           |   2 +
 common/src/weather.rs                       |  34 +++
 server/src/client.rs                        |   6 +-
 server/src/lib.rs                           |   4 +
 server/src/weather/mod.rs                   |  34 +++
 server/src/weather/sim.rs                   | 296 ++++++++++++++++++++
 server/src/weather/sync.rs                  |  36 +++
 server/src/weather/tick.rs                  |  34 +++
 voxygen/src/render/pipelines/lod_terrain.rs |  48 ++++
 voxygen/src/render/pipelines/mod.rs         |  28 ++
 voxygen/src/render/pipelines/sprite.rs      |   6 +-
 voxygen/src/scene/lod.rs                    |   1 +
 voxygen/src/session/mod.rs                  |  23 ++
 18 files changed, 579 insertions(+), 16 deletions(-)
 create mode 100644 common/src/weather.rs
 create mode 100644 server/src/weather/mod.rs
 create mode 100644 server/src/weather/sim.rs
 create mode 100644 server/src/weather/sync.rs
 create mode 100644 server/src/weather/tick.rs

diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index fa417dd021..ef9143b05a 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -10,14 +10,8 @@ layout(set = 0, binding = 6) uniform sampler s_alt;
 layout(set = 0, binding = 7) uniform texture2D t_horizon;
 layout(set = 0, binding = 8) uniform sampler s_horizon;
 
-const float MIN_SHADOW = 0.33;
 
-vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) {
-    // Want: (pixel + 0.5) / W
-    vec2 texSize = textureSize(sampler2D(tex, s), 0);
-    vec2 uv_pos = (focus_off.xy + pos + 16) / (32.0 * texSize);
-    return vec2(uv_pos.x, /*1.0 - */uv_pos.y);
-}
+const float MIN_SHADOW = 0.33;
 
 vec2 pos_to_tex(vec2 pos) {
     // Want: (pixel + 0.5)
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 9c2b9bd0ce..260deb081c 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -97,12 +97,31 @@ vec2 wind_offset = vec2(time_of_day.x * wind_speed);
 
 float cloud_scale = view_distance.z / 150.0;
 
+vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) {
+    // Want: (pixel + 0.5) / W
+    vec2 texSize = textureSize(sampler2D(tex, s), 0);
+    vec2 uv_pos = (focus_off.xy + pos + 16) / (32.0 * texSize);
+    return vec2(uv_pos.x, /*1.0 - */uv_pos.y);
+}
+
+// Weather texture
+layout(set = 0, binding = 12) uniform texture2D t_clouds;
+layout(set = 0, binding = 13) uniform sampler s_clouds;
+
 float cloud_tendency_at(vec2 pos) {
-    float nz = textureLod(sampler2D(t_noise, s_noise), (pos + wind_offset) / 60000.0 / cloud_scale, 0).x - 0.3;
-    nz = pow(clamp(nz, 0, 1), 3);
+    float nz = textureLod/*textureBicubic16*/(sampler2D(t_clouds, s_clouds), pos / 33500 , 0).r;
+    //float nz = textureLod(sampler2D(t_noise, s_noise), (pos + wind_offset) / 60000.0 / cloud_scale, 0).x - 0.3;
+    nz = pow(nz, 3);
     return nz;
 }
 
+// vec2 get_wind(vec2 pos) {}
+
+const float RAIN_CLOUD = 0.05;
+float rain_density_at(vec2 pos) {
+    return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
+}
+
 float cloud_shadow(vec3 pos, vec3 light_dir) {
     #if (CLOUD_MODE <= CLOUD_MODE_MINIMAL)
         return 1.0;
diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl
index 44ee20bfe8..bdd402a41e 100644
--- a/assets/voxygen/shaders/sprite-vert.glsl
+++ b/assets/voxygen/shaders/sprite-vert.glsl
@@ -30,7 +30,7 @@ layout(location = 7) in float inst_glow;
 layout(location = 8) in float model_wind_sway; // NOTE: this only varies per model
 layout(location = 9) in float model_z_scale; // NOTE: this only varies per model
 
-layout(set = 0, binding = 12) restrict readonly buffer sprite_verts {
+layout(set = 0, binding = 14) restrict readonly buffer sprite_verts {
     uvec2 verts[];
 };
 
diff --git a/client/src/lib.rs b/client/src/lib.rs
index eb6da6b25a..ec4ebd59c1 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -48,6 +48,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
     uid::{Uid, UidAllocator},
     vol::RectVolSize,
+    weather::Weather,
 };
 #[cfg(feature = "tracy")] use common_base::plot;
 use common_base::{prof_span, span};
@@ -106,6 +107,7 @@ pub enum Event {
     CharacterEdited(CharacterId),
     CharacterError(String),
     MapMarker(comp::MapMarkerUpdate),
+    WeatherUpdate(Grid<Weather>),
 }
 
 pub struct WorldData {
@@ -2193,6 +2195,9 @@ impl Client {
             ServerGeneral::MapMarker(event) => {
                 frontend_events.push(Event::MapMarker(event));
             },
+            ServerGeneral::WeatherUpdate(weather) => {
+                frontend_events.push(Event::WeatherUpdate(weather));
+            },
             _ => unreachable!("Not a in_game message"),
         }
         Ok(())
diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs
index 4b233de6bc..1515648414 100644
--- a/common/net/src/msg/server.rs
+++ b/common/net/src/msg/server.rs
@@ -15,6 +15,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeId, TradeResult},
     uid::Uid,
     uuid::Uuid,
+    weather::Weather,
 };
 use hashbrown::HashMap;
 use serde::{Deserialize, Serialize};
@@ -197,6 +198,7 @@ pub enum ServerGeneral {
     /// Economic information about sites
     SiteEconomy(EconomyInfo),
     MapMarker(comp::MapMarkerUpdate),
+    WeatherUpdate(common::grid::Grid<Weather>),
 }
 
 impl ServerGeneral {
@@ -309,7 +311,8 @@ impl ServerMsg {
                         | ServerGeneral::UpdatePendingTrade(_, _, _)
                         | ServerGeneral::FinishedTrade(_)
                         | ServerGeneral::SiteEconomy(_)
-                        | ServerGeneral::MapMarker(_) => {
+                        | ServerGeneral::MapMarker(_)
+                        | ServerGeneral::WeatherUpdate(_) => {
                             c_type == ClientType::Game && presence.is_some()
                         },
                         // Always possible
diff --git a/common/src/lib.rs b/common/src/lib.rs
index e1c0a036e8..88ae0bdefb 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -84,6 +84,8 @@ pub mod uid;
 #[cfg(not(target_arch = "wasm32"))] pub mod vol;
 #[cfg(not(target_arch = "wasm32"))]
 pub mod volumes;
+#[cfg(not(target_arch = "wasm32"))]
+pub mod weather;
 
 #[cfg(not(target_arch = "wasm32"))]
 pub use cached_spatial_grid::CachedSpatialGrid;
diff --git a/common/src/weather.rs b/common/src/weather.rs
new file mode 100644
index 0000000000..64634146c4
--- /dev/null
+++ b/common/src/weather.rs
@@ -0,0 +1,34 @@
+use serde::{Deserialize, Serialize};
+use vek::Vec2;
+
+// Weather::default is Clear, 0 degrees C and no wind
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
+pub struct Weather {
+    pub cloud: f32,
+    pub rain: f32,
+    pub wind: Vec2<f32>,
+}
+
+impl Weather {
+    pub fn new(cloud: f32, rain: f32, wind: Vec2<f32>) -> Self { Self { cloud, rain, wind } }
+
+    pub fn get_kind(&self) -> WeatherKind {
+        match (
+            (self.cloud * 10.0) as i32,
+            (self.rain * 10.0) as i32,
+            (self.wind.magnitude() * 10.0) as i32,
+        ) {
+            (_, _, 2455..) => WeatherKind::Storm,
+            (_, 1..=10, _) => WeatherKind::Rain,
+            (4..=10, _, _) => WeatherKind::Cloudy,
+            _ => WeatherKind::Clear,
+        }
+    }
+}
+
+pub enum WeatherKind {
+    Clear,
+    Cloudy,
+    Rain,
+    Storm,
+}
diff --git a/server/src/client.rs b/server/src/client.rs
index 202790ad08..012c77efb0 100644
--- a/server/src/client.rs
+++ b/server/src/client.rs
@@ -113,7 +113,8 @@ impl Client {
                     | ServerGeneral::Outcomes(_)
                     | ServerGeneral::Knockback(_)
                     | ServerGeneral::UpdatePendingTrade(_, _, _)
-                    | ServerGeneral::FinishedTrade(_) => {
+                    | ServerGeneral::FinishedTrade(_)
+                    | ServerGeneral::WeatherUpdate(_) => {
                         self.in_game_stream.lock().unwrap().send(g)
                     },
                     //Ingame related, terrain
@@ -187,7 +188,8 @@ impl Client {
                     | ServerGeneral::SiteEconomy(_)
                     | ServerGeneral::UpdatePendingTrade(_, _, _)
                     | ServerGeneral::FinishedTrade(_)
-                    | ServerGeneral::MapMarker(_) => {
+                    | ServerGeneral::MapMarker(_)
+                    | ServerGeneral::WeatherUpdate(_) => {
                         PreparedMsg::new(2, &g, &self.in_game_stream_params)
                     },
                     //Ingame related, terrain
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 39adc8fa4b..921e92dd73 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -39,6 +39,7 @@ pub mod sys;
 #[cfg(feature = "persistent_world")]
 pub mod terrain_persistence;
 #[cfg(not(feature = "worldgen"))] mod test_world;
+mod weather;
 pub mod wiring;
 
 // Reexports
@@ -569,6 +570,8 @@ impl Server {
         #[cfg(not(feature = "worldgen"))]
         rtsim::init(&mut state);
 
+        weather::init(&mut state, &world);
+
         let this = Self {
             state,
             world,
@@ -707,6 +710,7 @@ impl Server {
                 sys::add_server_systems(dispatcher_builder);
                 #[cfg(feature = "worldgen")]
                 rtsim::add_server_systems(dispatcher_builder);
+                weather::add_server_systems(dispatcher_builder);
             },
             false,
         );
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
new file mode 100644
index 0000000000..393b8f5a4f
--- /dev/null
+++ b/server/src/weather/mod.rs
@@ -0,0 +1,34 @@
+use common_ecs::{dispatch, System};
+use common_state::State;
+use specs::DispatcherBuilder;
+use std::time::Duration;
+
+use crate::sys::SysScheduler;
+
+mod sim;
+mod sync;
+mod tick;
+
+pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
+    dispatch::<tick::Sys>(dispatch_builder, &[]);
+    dispatch::<sync::Sys>(dispatch_builder, &[&tick::Sys::sys_name()]);
+}
+const CHUNKS_PER_CELL: u32 = 16;
+
+pub fn init(state: &mut State, world: &world::World) {
+    // How many chunks wide a weather cell is.
+    // 16 here means that a weather cell is 16x16 chunks.
+    let sim = sim::WeatherSim::new(world.sim().get_size() / CHUNKS_PER_CELL, world);
+    state.ecs_mut().insert(sim);
+    // Tick weather every 2 seconds
+    state
+        .ecs_mut()
+        .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(
+            0.1,
+        )));
+    state
+        .ecs_mut()
+        .insert(SysScheduler::<sync::Sys>::every(Duration::from_secs_f32(
+            0.1,
+        )));
+}
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
new file mode 100644
index 0000000000..149b3cea90
--- /dev/null
+++ b/server/src/weather/sim.rs
@@ -0,0 +1,296 @@
+use std::{
+    ops::{Add, Deref, DerefMut, Div, Mul},
+    sync::Arc,
+};
+
+use common::{
+    grid::Grid,
+    resources::TimeOfDay,
+    terrain::{BiomeKind, TerrainChunkSize},
+    vol::RectVolSize,
+    weather::Weather,
+};
+use itertools::Itertools;
+use vek::*;
+use world::{
+    util::{FastNoise, Sampler},
+    World,
+};
+
+#[derive(Default)]
+pub struct Constants {
+    drag: f32, // How much drag therfe is on wind. Caused by slopes.
+    humidity: Option<f32>,
+    temperature_day: Option<f32>,
+    temperature_night: Option<f32>,
+}
+
+#[derive(Clone, Copy, Default)]
+struct Cell {
+    wind: Vec2<f32>,
+    temperature: f32,
+    moisture: f32,
+    rain: f32,
+}
+/// Used to sample weather that isn't simulated
+fn sample_cell(p: Vec2<i32>, time: f64) -> Cell {
+    let noise = FastNoise::new(0b10110_101_1100_1111_10010_101_1110);
+
+    Cell {
+        wind: Vec2::new(noise.get(p.with_z(0).as_()), noise.get(p.with_z(100).as_()))
+            * ((noise.get(p.with_z(200).as_()) + 1.0) * 0.5).powf(4.0)
+            * 30.0,
+        temperature: noise.get(p.with_z(300).as_()).powf(3.0) * 10.0 + 20.0, // 10 -> 30
+        moisture: BASE_HUMIDITY,
+        rain: ((noise.get(p.with_z(400).as_()) + 1.0) * 0.5).powf(4.0) * MAX_RAIN,
+    }
+}
+
+pub struct WeatherSim {
+    cells: Grid<Cell>,
+    constants: Grid<Constants>,
+
+    weather: Grid<Weather>, // The current weather.
+}
+
+const BASE_HUMIDITY: f32 = 1.6652;
+const BASE_TEMPERATURE: f32 = 20.0;
+const MAX_RAIN: f32 = 100.0;
+
+impl WeatherSim {
+    pub fn new(size: Vec2<u32>, world: &World) -> Self {
+        let size = size.as_();
+        Self {
+            cells: Grid::new(size, Cell::default()),
+            constants: Grid::from_raw(
+                size,
+                (0..size.x * size.y)
+                    .map(|i| Vec2::new(i as i32 % size.x, i as i32 / size.x))
+                    .map(|p| {
+                        let mut c = Constants::default();
+                        for y in 0..CHUNKS_PER_CELL as i32 {
+                            for x in 0..CHUNKS_PER_CELL as i32 {
+                                let chunk_pos = p * CHUNKS_PER_CELL as i32 + Vec2::new(x, y);
+                                if let Some(chunk) = world.sim().get(chunk_pos) {
+                                    c.drag +=
+                                        world.sim().get_gradient_approx(chunk_pos).unwrap_or(0.0);
+                                    if chunk.is_underwater() {
+                                        c.humidity =
+                                            Some(c.humidity.unwrap_or(0.0) + BASE_HUMIDITY);
+                                        c.temperature_night =
+                                            Some(c.temperature_night.unwrap_or(0.0) + 0.01);
+                                    } else {
+                                        c.temperature_day =
+                                            Some(c.temperature_day.unwrap_or(0.0) + 0.01);
+                                    }
+                                    match chunk.get_biome() {
+                                        BiomeKind::Desert => {
+                                            c.temperature_day =
+                                                Some(c.temperature_day.unwrap_or(0.0) + 0.01);
+                                            c.humidity = Some(
+                                                c.humidity.unwrap_or(0.0) - 10.0 * BASE_HUMIDITY,
+                                            );
+                                        },
+                                        BiomeKind::Swamp => {
+                                            c.humidity = Some(
+                                                c.humidity.unwrap_or(0.0) + 2.0 * BASE_HUMIDITY,
+                                            );
+                                        },
+                                        _ => {},
+                                    }
+                                }
+                            }
+                        }
+                        c
+                    })
+                    .collect_vec(),
+            ),
+            weather: Grid::new(size, Weather::default()),
+        }
+    }
+
+    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
+
+    pub fn get_weather_at(&self, chunk: Vec2<i32>) -> Option<&Weather> {
+        self.weather.get(chunk / CHUNKS_PER_CELL as i32)
+    }
+
+    fn get_cell(&self, p: Vec2<i32>, time: f64) -> Cell {
+        *self.cells.get(p).unwrap_or(&sample_cell(p, time))
+    }
+
+    // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
+    // Time step is cell size / maximum wind speed
+    pub fn tick(&mut self, world: &World, time_of_day: &TimeOfDay, dt: f32) {
+        const MAX_WIND_SPEED: f32 = 127.0;
+        let cell_size: Vec2<f32> = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE).as_();
+        let dt = cell_size.x / MAX_WIND_SPEED;
+        let mut swap = Grid::new(self.cells.size(), Cell::default());
+        // Dissipate wind, humidty and pressure
+        //Dispersion is represented by the target cell expanding into the 8 adjacent
+        // cells. The target cell’s contents are then distributed based the
+        // percentage that overlaps the surrounding cell.
+        for (point, cell) in self.cells.iter() {
+            swap[point] = {
+                let spread = [
+                    (*cell, 1. / 4.),
+                    (
+                        self.get_cell(point + Vec2::new(1, 0), time_of_day.0),
+                        1. / 8.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(-1, 0), time_of_day.0),
+                        1. / 8.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(0, 1), time_of_day.0),
+                        1. / 8.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(0, -1), time_of_day.0),
+                        1. / 8.,
+                    ),
+                    // Diagonal so less overlap
+                    (
+                        self.get_cell(point + Vec2::new(1, 1), time_of_day.0),
+                        1. / 16.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(1, -1), time_of_day.0),
+                        1. / 16.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(-1, 1), time_of_day.0),
+                        1. / 16.,
+                    ),
+                    (
+                        self.get_cell(point + Vec2::new(-1, -1), time_of_day.0),
+                        1. / 16.,
+                    ),
+                ];
+                let mut cell = Cell::default();
+
+                for (c, factor) in spread {
+                    cell.wind += c.wind * factor;
+                    cell.temperature += c.temperature * factor;
+                    cell.moisture += c.moisture * factor;
+                }
+
+                cell
+            }
+        }
+        self.cells = swap.clone();
+        swap.iter_mut().for_each(|(_, cell)| {
+            *cell = Cell::default();
+        });
+
+        // Wind spread
+        // Wind is modeled by taking the target cell
+        // contents and moving it to different cells.
+        // We assume wind will not travel more than 1 cell per tick.
+
+        // Need to spread wind from outside simulation to simulate that the veloren
+        // island is not in a box.
+        for y in -1..=self.cells.size().y {
+            for x in -1..=self.cells.size().x {
+                let point = Vec2::new(x, y);
+                let cell = self.get_cell(point, time_of_day.0);
+                let a = cell_size.x - cell.wind.x.abs();
+                let b = cell_size.y - cell.wind.y.abs();
+                let wind_dir = Vec2::new(cell.wind.x.signum(), cell.wind.y.signum()).as_();
+                let spread = [
+                    (point, a * b / (cell_size.x * cell_size.y)),
+                    (
+                        point + wind_dir.with_y(0),
+                        (cell_size.x - a) * b / (cell_size.x * cell_size.y),
+                    ),
+                    (
+                        point + wind_dir.with_x(0),
+                        a * (cell_size.y - b) / (cell_size.x * cell_size.y),
+                    ),
+                    (
+                        point + wind_dir,
+                        (cell_size.x - a) * (cell_size.y - b) / (cell_size.x * cell_size.y),
+                    ),
+                ];
+                for (p, factor) in spread {
+                    if let Some(c) = swap.get_mut(p) {
+                        c.wind += cell.wind * factor;
+                        c.temperature += cell.temperature * factor;
+                        c.moisture += cell.moisture * factor;
+                        c.rain += cell.rain * factor;
+                    }
+                }
+            }
+        }
+        self.cells = swap.clone();
+
+        // TODO: wind curl, rain condesnsing from moisture. And interacting with world
+        // elements.
+
+        // Evaporate moisture and condense clouds
+        for (point, cell) in self.cells.iter_mut() {
+            // r = rain, m = moisture
+            // ∆r = Rcond − Rrain.
+            // ∆m = −Rcond + V.
+            // Rcond = δ(T0(m) − T)H(T0(m) − T) − γ(T − T0(m))H(T − T0(m)).
+            // T0(m) = (100−m) / 5
+
+            // V = B(T − Tv(h))H(T − Tv(h))
+            // γ = 0.5, δ = 0.25, B = 0.5 and Tv(h) = 20◦C
+
+            // TODO: make these parameters depend on the world.
+            // TODO: figure out what these variables mean.
+            let gamma = 0.5;
+            let delta = 0.25;
+            let b = 0.5;
+            let h = 1.0;
+            let t_v = BASE_TEMPERATURE;
+
+            let rain_fall_max = 0.05;
+
+            let evaporation = b * (cell.temperature - t_v) * h * (cell.temperature - t_v);
+
+            let dew_point = (100.0 - cell.moisture) / 5.0;
+
+            let condensation =
+                delta * (dew_point - cell.temperature) * h * (dew_point - cell.temperature)
+                    - gamma * (cell.temperature - dew_point) * h * (cell.temperature - dew_point);
+            cell.rain += condensation;
+            if cell.rain > rain_fall_max {
+                cell.rain -= rain_fall_max;
+                self.weather[point].rain = 1.0;
+            } else {
+                self.weather[point].rain = 0.0;
+            }
+            cell.moisture += evaporation - condensation;
+
+            self.weather[point].cloud = (cell.rain / MAX_RAIN).clamp(0.0, 1.0);
+            self.weather[point].wind = cell.wind;
+        }
+
+        // Maybe moisture condenses to clouds, which if they have a certain
+        // amount they will release rain.
+    }
+
+    fn update_info(&mut self) {
+        let w = self
+            .cells
+            .iter()
+            .map(|(p, c)| {
+                (
+                    p,
+                    Weather::new(
+                        //if p.x % 2 == p.y % 2 { 1.0 } else { 0.0 },
+                        (self.constants[p].humidity.unwrap_or(0.0) * 100.0).clamp(0.0, 1.0),
+                        0.0,
+                        c.wind,
+                    ),
+                )
+            })
+            .collect_vec();
+        w.iter().for_each(|&(p, w)| {
+            self.weather[p] = w;
+        });
+    }
+}
diff --git a/server/src/weather/sync.rs b/server/src/weather/sync.rs
new file mode 100644
index 0000000000..f98080b30e
--- /dev/null
+++ b/server/src/weather/sync.rs
@@ -0,0 +1,36 @@
+use common_ecs::{Origin, Phase, System};
+use common_net::msg::ServerGeneral;
+use specs::{Join, ReadExpect, ReadStorage, Write};
+
+use crate::{client::Client, sys::SysScheduler};
+
+use super::sim::WeatherSim;
+
+#[derive(Default)]
+pub struct Sys;
+
+impl<'a> System<'a> for Sys {
+    type SystemData = (
+        ReadExpect<'a, WeatherSim>,
+        Write<'a, SysScheduler<Self>>,
+        ReadStorage<'a, Client>,
+    );
+
+    const NAME: &'static str = "weather::sync";
+    const ORIGIN: Origin = Origin::Server;
+    const PHASE: Phase = Phase::Create;
+
+    fn run(job: &mut common_ecs::Job<Self>, (sim, mut scheduler, clients): Self::SystemData) {
+        if scheduler.should_run() {
+            let mut lazy_msg = None;
+            for client in clients.join() {
+                if lazy_msg.is_none() {
+                    lazy_msg = Some(
+                        client.prepare(ServerGeneral::WeatherUpdate(sim.get_weather().clone())),
+                    );
+                }
+                lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
+            }
+        }
+    }
+}
diff --git a/server/src/weather/tick.rs b/server/src/weather/tick.rs
new file mode 100644
index 0000000000..d95c4ef260
--- /dev/null
+++ b/server/src/weather/tick.rs
@@ -0,0 +1,34 @@
+use std::sync::Arc;
+
+use common::resources::TimeOfDay;
+use common_ecs::{Origin, Phase, System};
+use specs::{Read, ReadExpect, Write, WriteExpect};
+
+use crate::sys::SysScheduler;
+
+use super::sim::WeatherSim;
+
+#[derive(Default)]
+pub struct Sys;
+
+impl<'a> System<'a> for Sys {
+    type SystemData = (
+        Read<'a, TimeOfDay>,
+        ReadExpect<'a, Arc<world::World>>,
+        WriteExpect<'a, WeatherSim>,
+        Write<'a, SysScheduler<Self>>,
+    );
+
+    const NAME: &'static str = "weather::tick";
+    const ORIGIN: Origin = Origin::Server;
+    const PHASE: Phase = Phase::Create;
+
+    fn run(
+        job: &mut common_ecs::Job<Self>,
+        (game_time, world, mut sim, mut scheduler): Self::SystemData,
+    ) {
+        if scheduler.should_run() {
+            sim.tick(&*world, &*game_time, 1.0);
+        }
+    }
+}
diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs
index 4257428dbd..7d53baa89a 100644
--- a/voxygen/src/render/pipelines/lod_terrain.rs
+++ b/voxygen/src/render/pipelines/lod_terrain.rs
@@ -36,6 +36,7 @@ pub struct LodData {
     pub alt: Texture,
     pub horizon: Texture,
     pub tgt_detail: u32,
+    pub clouds: Texture,
 }
 
 impl LodData {
@@ -52,6 +53,7 @@ impl LodData {
             &map_image,
             &alt_image,
             &horizon_image,
+            Vec2::new(1, 1),
             1,
             //map_border.into(),
         )
@@ -63,6 +65,7 @@ impl LodData {
         lod_base: &[u32],
         lod_alt: &[u32],
         lod_horizon: &[u32],
+        clouds_size: Vec2<u32>,
         tgt_detail: u32,
         //border_color: gfx::texture::PackedColor,
     ) -> Self {
@@ -132,12 +135,57 @@ impl LodData {
         );
         //             SamplerInfo {
         //                 border: [1.0, 0.0, 1.0, 0.0].into(),
+        let clouds = {
+            let texture_info = wgpu::TextureDescriptor {
+                label: None,
+                size: wgpu::Extent3d {
+                    width: clouds_size.x,
+                    height: clouds_size.y,
+                    depth_or_array_layers: 1,
+                },
+                mip_level_count: 1,
+                sample_count: 1,
+                dimension: wgpu::TextureDimension::D2,
+                format: wgpu::TextureFormat::Rgba8Unorm,
+                usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
+            };
 
+            let sampler_info = wgpu::SamplerDescriptor {
+                label: None,
+                address_mode_u: wgpu::AddressMode::ClampToEdge,
+                address_mode_v: wgpu::AddressMode::ClampToEdge,
+                address_mode_w: wgpu::AddressMode::ClampToEdge,
+                mag_filter: wgpu::FilterMode::Linear,
+                min_filter: wgpu::FilterMode::Linear,
+                mipmap_filter: wgpu::FilterMode::Nearest,
+                border_color: Some(wgpu::SamplerBorderColor::TransparentBlack),
+                ..Default::default()
+            };
+
+            let view_info = wgpu::TextureViewDescriptor {
+                label: None,
+                format: Some(wgpu::TextureFormat::Rgba8Unorm),
+                dimension: Some(wgpu::TextureViewDimension::D2),
+                aspect: wgpu::TextureAspect::All,
+                base_mip_level: 0,
+                mip_level_count: None,
+                base_array_layer: 0,
+                array_layer_count: None,
+            };
+
+            renderer.create_texture_with_data_raw(
+                &texture_info,
+                &view_info,
+                &sampler_info,
+                vec![0; clouds_size.x as usize * clouds_size.y as usize * 4].as_slice(),
+            )
+        };
         Self {
             map,
             alt,
             horizon,
             tgt_detail,
+            clouds,
         }
     }
 }
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index 0fad8f5598..5a913dd44f 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -401,6 +401,26 @@ impl GlobalsLayouts {
                 },
                 count: None,
             },
+            // clouds t_clouds
+            wgpu::BindGroupLayoutEntry {
+                binding: 12,
+                visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                ty: wgpu::BindingType::Texture {
+                    sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                    view_dimension: wgpu::TextureViewDimension::D2,
+                    multisampled: false,
+                },
+                count: None,
+            },
+            wgpu::BindGroupLayoutEntry {
+                binding: 13,
+                visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                ty: wgpu::BindingType::Sampler {
+                    filtering: true,
+                    comparison: false,
+                },
+                count: None,
+            },
         ]
     }
 
@@ -552,6 +572,14 @@ impl GlobalsLayouts {
                 binding: 11,
                 resource: wgpu::BindingResource::Sampler(&lod_data.map.sampler),
             },
+            wgpu::BindGroupEntry {
+                binding: 12,
+                resource: wgpu::BindingResource::TextureView(&lod_data.clouds.view),
+            },
+            wgpu::BindGroupEntry {
+                binding: 13,
+                resource: wgpu::BindingResource::Sampler(&lod_data.clouds.sampler),
+            },
         ]
     }
 
diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs
index fd0514bfcf..504d481f6a 100644
--- a/voxygen/src/render/pipelines/sprite.rs
+++ b/voxygen/src/render/pipelines/sprite.rs
@@ -176,11 +176,11 @@ pub struct SpriteLayout {
 impl SpriteLayout {
     pub fn new(device: &wgpu::Device) -> Self {
         let mut entries = GlobalsLayouts::base_globals_layout();
-        debug_assert_eq!(12, entries.len()); // To remember to adjust the bindings below
+        debug_assert_eq!(14, entries.len()); // To remember to adjust the bindings below
         entries.extend_from_slice(&[
             // sprite_verts
             wgpu::BindGroupLayoutEntry {
-                binding: 12,
+                binding: 14,
                 visibility: wgpu::ShaderStage::VERTEX,
                 ty: wgpu::BindingType::Buffer {
                     ty: wgpu::BufferBindingType::Storage { read_only: true },
@@ -214,7 +214,7 @@ impl SpriteLayout {
         entries.extend_from_slice(&[
             // sprite_verts
             wgpu::BindGroupEntry {
-                binding: 12,
+                binding: 14,
                 resource: sprite_verts.0.buf.as_entire_binding(),
             },
         ]);
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index d8ce54672a..5953498d76 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -52,6 +52,7 @@ impl Lod {
             client.world_data().lod_base.raw(),
             client.world_data().lod_alt.raw(),
             client.world_data().lod_horizon.raw(),
+            client.world_data().chunk_size().as_() / 16, // TODO: Send this from the server.
             settings.graphics.lod_detail.max(100).min(2500),
             /* TODO: figure out how we want to do this without color borders?
              * water_color().into_array().into(), */
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 7176bc8786..5a89a19a7f 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -4,6 +4,7 @@ mod target;
 
 use std::{cell::RefCell, collections::HashSet, rc::Rc, result::Result, time::Duration};
 
+use itertools::Itertools;
 #[cfg(not(target_os = "macos"))]
 use mumble_link::SharedLink;
 use ordered_float::OrderedFloat;
@@ -324,6 +325,28 @@ impl SessionState {
                 client::Event::MapMarker(event) => {
                     self.hud.show.update_map_markers(event);
                 },
+                client::Event::WeatherUpdate(weather) => {
+                    //weather
+                    //    .iter_mut()
+                    //    .for_each(|(p, c)| *c = if (p.x + p.y) % 2 == 0 { 1.0 } else { 0.0 });
+                    global_state.window.renderer_mut().update_texture(
+                        &self.scene.lod.get_data().clouds,
+                        [0, 0],
+                        [weather.size().x as u32, weather.size().y as u32],
+                        weather
+                            .iter()
+                            .map(|(_, w)| {
+                                [
+                                    (w.cloud * 255.0) as u8,
+                                    (w.rain + 128.0).clamp(0.0, 255.0) as u8,
+                                    (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
+                                    (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
+                                ]
+                            })
+                            .collect_vec()
+                            .as_slice(),
+                    );
+                },
             }
         }
 

From 9cb67e628394ce9895c7579035c4dc6b8314c105 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 6 Feb 2022 19:56:45 +0100
Subject: [PATCH 02/71] Rain shader

---
 assets/voxygen/shaders/clouds-frag.glsl       | 57 +++++++++++++++++++
 assets/voxygen/shaders/fluid-frag/shiny.glsl  | 43 +++++++++++---
 .../shaders/include/cloud/regular.glsl        |  6 +-
 assets/voxygen/shaders/include/globals.glsl   |  2 +
 assets/voxygen/shaders/include/sky.glsl       |  2 -
 assets/voxygen/shaders/terrain-frag.glsl      | 29 ++++++++++
 6 files changed, 126 insertions(+), 13 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 057dabe157..67869492f3 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -62,6 +62,19 @@ vec3 wpos_at(vec2 uv) {
     }
 }
 
+mat4 spin_in_axis(vec3 axis, float angle)
+{
+    axis = normalize(axis);
+    float s = sin(angle);
+    float c = cos(angle);
+    float oc = 1.0 - c;
+
+    return mat4(oc * axis.x * axis.x + c,  oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0,
+        oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c,          oc * axis.y * axis.z - axis.x * s, 0,
+        oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c,          0,
+        0,                                 0,                                 0,                                 1);
+}
+
 void main() {
     vec4 color = texture(sampler2D(t_src_color, s_src_color), uv);
 
@@ -86,5 +99,49 @@ void main() {
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
     #endif
 
+    vec3 old_color = color.rgb;
+
+    float fall_rate = 20.0;
+
+    dir.xy += wind_vel * dir.z / fall_rate;
+    dir = normalize(dir);
+
+    float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+    vec2 dir_2d = normalize(dir.xy);
+    vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
+
+    vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+    float rain_density = rain_density_at(cam_wpos.xy);
+    if (rain_density > 0) {
+        float rain_dist = 50.0;
+        for (int i = 0; i < 5; i ++) {
+            rain_dist *= 0.3;
+
+            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
+            float dist_to_rain = length(rpos);
+
+            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+                continue;
+            }
+
+            float drop_density = 3;
+            vec2 drop_size = vec2(0.0025, 0.17);
+
+            vec2 rain_pos = (view_pos * rain_dist);
+            rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
+
+            vec2 cell = floor(rain_pos * drop_density) / drop_density;
+            if (hash(fract(vec4(cell, rain_dist, 0) * 0.01)) > rain_density) {
+                continue;
+            }
+            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+            float avg_alpha = (drop_size.x * drop_size.y) / 1;
+            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size), 0));
+            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
+        }
+    }
+
     tgt_color = vec4(color.rgb, 1);
 }
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index e07f8cc92d..3d13a7cef8 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -58,7 +58,7 @@ vec3 warp_normal(vec3 norm, vec3 pos, float time) {
         + smooth_rand(pos * 0.25, time * 0.25) * 0.1);
 }
 
-float wave_height(vec3 pos) {
+float wave_height(vec3 pos, vec3 surf_norm) {
     float timer = tick.x * 0.75;
 
     pos *= 0.5;
@@ -103,7 +103,8 @@ void main() {
     uint norm_dir = ((f_pos_norm >> 29) & 0x1u) * 3u;
     // Use an array to avoid conditional branching
     // Temporarily assume all water faces up (this is incorrect but looks better)
-    vec3 f_norm = vec3(0, 0, 1);//normals[norm_axis + norm_dir];
+    vec3 surf_norm = normals[norm_axis + norm_dir];
+    vec3 f_norm = vec3(0, 0, 1);//surf_norm;
     vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
 
     // vec4 light_pos[2];
@@ -131,10 +132,11 @@ void main() {
     }
     vec3 c_norm = cross(f_norm, b_norm);
 
-    vec3 wave_pos = f_pos + focus_off.xyz;
-    float wave00 = wave_height(wave_pos);
-    float wave10 = wave_height(wave_pos + vec3(0.1, 0, 0));
-    float wave01 = wave_height(wave_pos + vec3(0, 0.1, 0));
+    vec3 wave_pos = mod(f_pos + focus_off.xyz, vec3(100.0));
+    float wave_sample_dist = 0.025;
+    float wave00 = wave_height(wave_pos, surf_norm);
+    float wave10 = wave_height(wave_pos + vec3(wave_sample_dist, 0, 0), surf_norm);
+    float wave01 = wave_height(wave_pos + vec3(0, wave_sample_dist, 0), surf_norm);
 
     // Possibility of div by zero when slope = 0,
     // however this only results in no water surface appearing
@@ -142,11 +144,34 @@ void main() {
     float slope = abs((wave00 - wave10) * (wave00 - wave01)) + 0.001;
 
     vec3 nmap = vec3(
-        -(wave10 - wave00) / 0.1,
-        -(wave01 - wave00) / 0.1,
-        0.1 / slope
+        -(wave10 - wave00) / wave_sample_dist,
+        -(wave01 - wave00) / wave_sample_dist,
+        wave_sample_dist / slope
     );
 
+    float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+    if (rain_density > 0 && surf_norm.z > 0.5) {
+        vec3 drop_density = vec3(2, 2, 1);
+        vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
+        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+
+        if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+
+            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+            float drop_rad = 0.125;
+            nmap.xy += (drop_pos - near_cell).xy
+                * max(1.0 - abs(dist - drop_rad) * 50, 0)
+                * 2500
+                * sign(dist - drop_rad)
+                * max(drop_pos.z - near_cell.z, 0);
+        }
+    }
+
     nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1));
 
     //float suppress_waves = max(dot(), 0);
diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index cddf83003e..d3f461098e 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -256,13 +256,15 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
             float step = (ldist - cdist) * 0.01;
             float cloud_darken = pow(1.0 / (1.0 + cloud_scatter_factor), step);
             float global_darken = pow(1.0 / (1.0 + global_scatter_factor), step);
+            // Proportion of light diffusely scattered instead of absorbed
+            float cloud_diffuse = 0.25;
 
             surf_color =
                 // Attenuate light passing through the clouds
                 surf_color * cloud_darken * global_darken +
                 // Add the directed light light scattered into the camera by the clouds and the atmosphere (global illumination)
-                sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken) /*+ sky_color * global_scatter_factor*/) +
-                moon_color * moon_scatter * get_moon_brightness() * (moon_access * (1.0 - cloud_darken) /*+ sky_color * global_scatter_factor*/) +
+                sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken)  * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
+                moon_color * moon_scatter * get_moon_brightness() * (moon_access * (1.0 - cloud_darken) * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
                 sky_light * (1.0 - global_darken) * not_underground +
                 emission * density_integrals.y * step;
         }
diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl
index cc75645b39..af2fcff835 100644
--- a/assets/voxygen/shaders/include/globals.glsl
+++ b/assets/voxygen/shaders/include/globals.glsl
@@ -37,4 +37,6 @@ mat4 threshold_matrix = mat4(
 float distance_divider = 2;
 float shadow_dithering = 0.5;
 
+vec2 wind_vel = vec2(0.0);
+
 #endif
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 260deb081c..65dd47a3d8 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -115,8 +115,6 @@ float cloud_tendency_at(vec2 pos) {
     return nz;
 }
 
-// vec2 get_wind(vec2 pos) {}
-
 const float RAIN_CLOUD = 0.05;
 float rain_density_at(vec2 pos) {
     return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index 034c98edcb..77a83f81db 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -231,6 +231,35 @@ void main() {
     vec3 k_d = vec3(1.0);
     vec3 k_s = vec3(R_s);
 
+    float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+    if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
+        vec3 pos = f_pos + focus_off.xyz;
+        vec3 drop_density = vec3(2, 2, 1);
+        vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
+        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+
+        if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+
+            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+            float drop_rad = 0.1;
+            float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
+            k_a += distort;
+            k_d += distort;
+            k_s += distort;
+            f_norm.xy += (drop_pos - near_cell).xy
+                * max(1.0 - abs(dist - drop_rad) * 30, 0)
+                * 500.0
+                * max(drop_pos.z - near_cell.z, 0)
+                * sign(dist - drop_rad)
+                * max(drop_pos.z - near_cell.z, 0);
+        }
+    }
+
     // float sun_light = get_sun_brightness(sun_dir);
     // float moon_light = get_moon_brightness(moon_dir);
     /* float sun_shade_frac = horizon_at(f_pos, sun_dir);

From 102156c4b37c03a272e465610cbc4c067388c99c Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 6 Feb 2022 20:16:08 +0100
Subject: [PATCH 03/71] Better particle light

---
 assets/voxygen/shaders/particle-vert.glsl | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl
index 0e595f09d9..b230abb7d7 100644
--- a/assets/voxygen/shaders/particle-vert.glsl
+++ b/assets/voxygen/shaders/particle-vert.glsl
@@ -215,7 +215,7 @@ void main() {
                     vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0)
                 ),
                 vec3(1.0),
-                vec4(2, 1.5 + rand5 * 0.5, 0, start_end(1.0, 0.0)),
+                vec4(6, 3 + rand5 * 0.3 - 0.8 * percent(), 0.4, 1),
                 spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3)
             );
             break;
@@ -431,16 +431,16 @@ void main() {
             attr = Attr(
                 (inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
                 vec3((2.5 * (1 - slow_start(0.2)))),
-                vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
+                vec4(6, 3 + rand5 * 0.6 - 0.8 * percent(), 0.4, 1),
                 spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
             );
             break;
         case EXPLOSION:
             f_reflect = 0.0; // Fire doesn't reflect light, it emits it
             attr = Attr(
-                inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(2.0) + 0.3 * grav_vel(earth_gravity),
+                inst_dir * ((rand0+1.0)/2 + 0.4) * slow_end(0.25) + 0.3 * grav_vel(earth_gravity),
                 vec3((3 * (1 - slow_start(0.1)))),
-                vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
+                vec4(6, 3 + rand5 * 0.3 - 0.8 * percent(), 0.4, 1),
                 spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
             );
             break;
@@ -459,7 +459,7 @@ void main() {
             attr = Attr(
                 vec3(rand0, rand1, lifetime * 10 + rand2),
                 vec3((5 * (1 - slow_start(0.5)))),
-                vec4(3, 1.6 + rand5 * 0.3 - 0.4 * percent(), 0.2, 1),
+                vec4(6, 3 + rand5 * 0.6 - 0.8 * percent(), 0.4, 1),
                 spin_in_axis(vec3(rand3, rand4, rand5), rand6)
             );
             break;

From 6585ef15135bc8a0131b14c46b95067b126af117 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 20 Feb 2022 17:04:05 +0100
Subject: [PATCH 04/71] weather dependant music

---
 assets/voxygen/audio/soundtrack.ron           | 58 ++++++++++++++++++-
 .../soundtrack/overworld/a_heros_sorrow.ogg   |  3 +
 .../soundtrack/overworld/the_heavens_weep.ogg |  3 +
 client/src/lib.rs                             | 22 ++++++-
 common/src/weather.rs                         |  5 +-
 server/src/weather/mod.rs                     |  2 +-
 server/src/weather/sim.rs                     |  4 +-
 voxygen/src/audio/music.rs                    | 10 ++++
 voxygen/src/session/mod.rs                    |  8 ++-
 9 files changed, 104 insertions(+), 11 deletions(-)
 create mode 100644 assets/voxygen/audio/soundtrack/overworld/a_heros_sorrow.ogg
 create mode 100644 assets/voxygen/audio/soundtrack/overworld/the_heavens_weep.ogg

diff --git a/assets/voxygen/audio/soundtrack.ron b/assets/voxygen/audio/soundtrack.ron
index 7e81ee0614..ed5c016050 100644
--- a/assets/voxygen/audio/soundtrack.ron
+++ b/assets/voxygen/audio/soundtrack.ron
@@ -122,6 +122,7 @@
             path: "voxygen.audio.soundtrack.town.rest_assured",
             length: 189.0,
             timing: Some(Day),
+            weather: None,
             biomes: [],
             site: Some(Settlement),
             music_state: Activity(Explore),
@@ -132,6 +133,7 @@
             path: "voxygen.audio.soundtrack.town.im_home",
             length: 125.0,
             timing: Some(Night),
+            weather: None,
             biomes: [],
             site: Some(Settlement),
             music_state: Activity(Explore),
@@ -142,6 +144,7 @@
             path: "voxygen.audio.soundtrack.dungeon.dank_dungeon",
             length: 130.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Dungeon),
             music_state: Activity(Explore),
@@ -152,6 +155,7 @@
             path: "voxygen.audio.soundtrack.overworld.calming_hills",
             length: 101.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Mountain, 1),
             ],
@@ -164,6 +168,7 @@
             path: "voxygen.audio.soundtrack.town.fiesta_del_pueblo",
             length: 183.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Desert, 1)
             ],
@@ -176,6 +181,7 @@
             path: "voxygen.audio.soundtrack.dungeon.ruination",
             length: 135.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Dungeon),
             music_state: Activity(Explore),
@@ -186,6 +192,7 @@
             path: "voxygen.audio.soundtrack.cave.saturated_hallows",
             length: 227.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Cave),
             music_state: Activity(Explore),
@@ -196,6 +203,7 @@
             path: "voxygen.audio.soundtrack.dungeon.vast_onslaught",
             length: 237.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Dungeon),
             music_state: Activity(Explore),
@@ -206,6 +214,7 @@
             path: "voxygen.audio.soundtrack.dungeon.sacred_temple",
             length: 75.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Dungeon),
             music_state: Activity(Explore),
@@ -216,6 +225,7 @@
             path: "voxygen.audio.soundtrack.overworld.true_nature",
             length: 169.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Forest, 2),
             ],
@@ -228,6 +238,7 @@
             path: "voxygen.audio.soundtrack.overworld.jungle_ambient",
             length: 218.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Jungle, 1),
             ],
@@ -240,6 +251,7 @@
             path: "voxygen.audio.soundtrack.overworld.ethereal_bonds",
             length: 59.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Mountain, 1),
             ],
@@ -252,6 +264,7 @@
             path: "voxygen.audio.soundtrack.overworld.leap_of_faith",
             length: 269.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Ocean, 1),
                 (Lake, 1),
@@ -265,6 +278,7 @@
             path: "voxygen.audio.soundtrack.overworld.highland_of_the_hawk",
             length: 283.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Desert, 1),
                 (Savannah, 1),
@@ -278,6 +292,7 @@
             path: "voxygen.audio.soundtrack.overworld.verdant_glades",
             length: 97.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Grassland, 1),
             ],
@@ -290,6 +305,7 @@
             path: "voxygen.audio.soundtrack.overworld.calling_wild",
             length: 160.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Grassland, 1),
                 (Savannah, 1),
@@ -303,6 +319,7 @@
             path: "voxygen.audio.soundtrack.overworld.drifting_along",
             length: 164.0,
             timing: None,
+            weather: None,
             biomes: [
                 (Lake, 1),
                 (Ocean, 1),
@@ -316,6 +333,7 @@
             path: "voxygen.audio.soundtrack.overworld.winter_falls",
             length: 215.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Snowland, 1),
                 (Taiga, 1),
@@ -329,6 +347,7 @@
             path: "voxygen.audio.soundtrack.overworld.short_meandering",
             length: 147.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Desert, 1),
                 (Mountain, 1),
@@ -342,6 +361,7 @@
             path: "voxygen.audio.soundtrack.overworld.oceania",
             length: 135.0,
             timing: None,
+            weather: None,
             biomes: [
                 (Lake, 1),
                 (Ocean, 1),
@@ -355,6 +375,7 @@
             path: "voxygen.audio.soundtrack.overworld.a_solemn_quest",
             length: 206.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Forest, 2),
             ],
@@ -367,6 +388,7 @@
             path: "voxygen.audio.soundtrack.overworld.into_the_dark_forest",
             length: 184.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Forest, 2),
                 (Jungle, 1),
@@ -380,6 +402,7 @@
             path: "voxygen.audio.soundtrack.overworld.field_grazing",
             length: 154.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Grassland, 1),
                 (Forest, 2),
@@ -393,6 +416,7 @@
             path: "voxygen.audio.soundtrack.overworld.wandering_voices",
             length: 137.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Grassland, 1),
             ],
@@ -405,6 +429,7 @@
             path: "voxygen.audio.soundtrack.overworld.snowtop_volume",
             length: 89.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Snowland, 1),
                 (Taiga, 1),
@@ -418,6 +443,7 @@
             path: "voxygen.audio.soundtrack.cave.mineral_deposits",
             length: 148.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Cave),
             music_state: Activity(Explore),
@@ -428,6 +454,7 @@
             path: "voxygen.audio.soundtrack.overworld.moonbeams",
             length: 158.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Snowland, 1),
                 (Taiga, 1),
@@ -441,6 +468,7 @@
             path: "voxygen.audio.soundtrack.overworld.serene_meadows",
             length: 173.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Grassland, 1),
             ],
@@ -453,6 +481,7 @@
             path: "voxygen.audio.soundtrack.overworld.just_the_beginning",
             length: 188.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Grassland, 1),
             ],
@@ -465,6 +494,7 @@
             path: "voxygen.audio.soundtrack.overworld.campfire_stories",
             length: 100.0,
             timing: Some(Night),
+            weather: None,
             biomes: [
                 (Forest, 2),
             ],
@@ -477,6 +507,7 @@
             path: "voxygen.audio.soundtrack.overworld.limits",
             length: 203.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Mountain, 1),
             ],
@@ -489,6 +520,7 @@
             path: "voxygen.audio.soundtrack.dungeon.down_the_rabbit_hole",
             length: 244.0,
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Cave),
             music_state: Activity(Explore),
@@ -499,13 +531,36 @@
             path: "voxygen.audio.soundtrack.overworld.between_the_fairies",
             length: 175.0,
             timing: Some(Day),
+            weather: None,
             biomes: [
                 (Forest, 2),
             ],
             site: Some(Void),
             music_state: Activity(Explore),
             artist: "badbbad",
-       )),
+        )),
+        Individual((
+            title: "The Heavens Weep",
+            path: "voxygen.audio.soundtrack.overworld.the_heavens_weep",
+            length: 209.0,
+            timing: None,
+            weather: Some(Rain),
+            biomes: [],
+            site: None,
+            music_state: Activity(Explore),
+            artist: "Oolnokk",
+        )),
+        Individual((
+            title: "A Heroes Sorrow",
+            path: "voxygen.audio.soundtrack.overworld.a_heroes_sorrow",
+            length: 251.0,
+            timing: None,
+            weather: Some(Rain),
+            biomes: [],
+            site: None,
+            music_state: Activity(Explore),
+            artist: "Oolnokk",
+        )),
 
         // Combat Music
 
@@ -513,6 +568,7 @@
             title: "Barred Paths",
             author: "DaforLynx",
             timing: None,
+            weather: None,
             biomes: [],
             site: Some(Dungeon),
             segments: [
diff --git a/assets/voxygen/audio/soundtrack/overworld/a_heros_sorrow.ogg b/assets/voxygen/audio/soundtrack/overworld/a_heros_sorrow.ogg
new file mode 100644
index 0000000000..bb1a8bae6e
--- /dev/null
+++ b/assets/voxygen/audio/soundtrack/overworld/a_heros_sorrow.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b288c7e767b064b7df6b25165141c0d49f4c5db1e312f8537791f28bea034a42
+size 6734031
diff --git a/assets/voxygen/audio/soundtrack/overworld/the_heavens_weep.ogg b/assets/voxygen/audio/soundtrack/overworld/the_heavens_weep.ogg
new file mode 100644
index 0000000000..fe2d956f92
--- /dev/null
+++ b/assets/voxygen/audio/soundtrack/overworld/the_heavens_weep.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8cb54b3992d09b489701c9d55b25eae1ad0dc5b7648e849aed0768da01fecac0
+size 2993026
diff --git a/client/src/lib.rs b/client/src/lib.rs
index ec4ebd59c1..16812bdab1 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -48,7 +48,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
     uid::{Uid, UidAllocator},
     vol::RectVolSize,
-    weather::Weather,
+    weather::{self, Weather},
 };
 #[cfg(feature = "tracy")] use common_base::plot;
 use common_base::{prof_span, span};
@@ -107,7 +107,7 @@ pub enum Event {
     CharacterEdited(CharacterId),
     CharacterError(String),
     MapMarker(comp::MapMarkerUpdate),
-    WeatherUpdate(Grid<Weather>),
+    WeatherUpdate,
 }
 
 pub struct WorldData {
@@ -159,6 +159,7 @@ pub struct Client {
     runtime: Arc<Runtime>,
     server_info: ServerInfo,
     world_data: WorldData,
+    weather: Grid<Weather>,
     player_list: HashMap<Uid, PlayerInfo>,
     character_list: CharacterList,
     sites: HashMap<SiteId, SiteInfoRich>,
@@ -610,6 +611,7 @@ impl Client {
                 lod_horizon,
                 map: world_map,
             },
+            weather: Grid::new(Vec2::new(1, 1), Weather::default()),
             player_list: HashMap::new(),
             character_list: CharacterList::default(),
             sites: sites
@@ -1415,6 +1417,19 @@ impl Client {
             .map(|v| v.0)
     }
 
+    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
+
+    pub fn current_weather(&self) -> Weather {
+        if let Some(position) = self.position() {
+            let cell_pos = (position.xy()
+                / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_()))
+            .as_();
+            *self.weather.get(cell_pos).unwrap_or(&Weather::default())
+        } else {
+            Weather::default()
+        }
+    }
+
     pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
         let chunk_pos = Vec2::from(self.position()?)
             .map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
@@ -2196,7 +2211,8 @@ impl Client {
                 frontend_events.push(Event::MapMarker(event));
             },
             ServerGeneral::WeatherUpdate(weather) => {
-                frontend_events.push(Event::WeatherUpdate(weather));
+                self.weather = weather;
+                frontend_events.push(Event::WeatherUpdate);
             },
             _ => unreachable!("Not a in_game message"),
         }
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 64634146c4..2645412620 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -1,6 +1,7 @@
 use serde::{Deserialize, Serialize};
 use vek::Vec2;
 
+pub const CHUNKS_PER_CELL: u32 = 16;
 // Weather::default is Clear, 0 degrees C and no wind
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
 pub struct Weather {
@@ -18,7 +19,8 @@ impl Weather {
             (self.rain * 10.0) as i32,
             (self.wind.magnitude() * 10.0) as i32,
         ) {
-            (_, _, 2455..) => WeatherKind::Storm,
+            // Over 24.5 m/s wind is a storm
+            (_, _, 245..) => WeatherKind::Storm,
             (_, 1..=10, _) => WeatherKind::Rain,
             (4..=10, _, _) => WeatherKind::Cloudy,
             _ => WeatherKind::Clear,
@@ -26,6 +28,7 @@ impl Weather {
     }
 }
 
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
 pub enum WeatherKind {
     Clear,
     Cloudy,
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index 393b8f5a4f..5c95cd09bf 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -1,3 +1,4 @@
+use common::weather::CHUNKS_PER_CELL;
 use common_ecs::{dispatch, System};
 use common_state::State;
 use specs::DispatcherBuilder;
@@ -13,7 +14,6 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
     dispatch::<tick::Sys>(dispatch_builder, &[]);
     dispatch::<sync::Sys>(dispatch_builder, &[&tick::Sys::sys_name()]);
 }
-const CHUNKS_PER_CELL: u32 = 16;
 
 pub fn init(state: &mut State, world: &world::World) {
     // How many chunks wide a weather cell is.
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 149b3cea90..6578da7660 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -8,7 +8,7 @@ use common::{
     resources::TimeOfDay,
     terrain::{BiomeKind, TerrainChunkSize},
     vol::RectVolSize,
-    weather::Weather,
+    weather::{Weather, CHUNKS_PER_CELL},
 };
 use itertools::Itertools;
 use vek::*;
@@ -261,7 +261,7 @@ impl WeatherSim {
                 cell.rain -= rain_fall_max;
                 self.weather[point].rain = 1.0;
             } else {
-                self.weather[point].rain = 0.0;
+                self.weather[point].rain = 0.5;
             }
             cell.moisture += evaporation - condensation;
 
diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs
index d4c974b1bb..a884c29c66 100644
--- a/voxygen/src/audio/music.rs
+++ b/voxygen/src/audio/music.rs
@@ -48,6 +48,7 @@ use client::Client;
 use common::{
     assets::{self, AssetExt, AssetHandle},
     terrain::{BiomeKind, SitesKind},
+    weather::WeatherKind,
 };
 use common_state::State;
 use hashbrown::HashMap;
@@ -78,6 +79,8 @@ pub struct SoundtrackItem {
     length: f32,
     /// Whether this track should play during day or night
     timing: Option<DayPeriod>,
+    /// Whether this track should play during a certain weather
+    weather: Option<WeatherKind>,
     /// What biomes this track should play in with chance of play
     biomes: Vec<(BiomeKind, u8)>,
     /// Whether this track should play in a specific site
@@ -98,6 +101,7 @@ enum RawSoundtrackItem {
     Segmented {
         title: String,
         timing: Option<DayPeriod>,
+        weather: Option<WeatherKind>,
         biomes: Vec<(BiomeKind, u8)>,
         site: Option<SitesKind>,
         segments: Vec<(String, f32, MusicState, Option<MusicActivity>)>,
@@ -328,6 +332,7 @@ impl MusicMgr {
 
         let is_dark = (state.get_day_period().is_dark()) as bool;
         let current_period_of_day = Self::get_current_day_period(is_dark);
+        let current_weather = client.current_weather();
         let current_biome = client.current_biome();
         let current_site = client.current_site();
 
@@ -348,6 +353,9 @@ impl MusicMgr {
                 }) && match &track.site {
                     Some(site) => site == &current_site,
                     None => true,
+                } && match &track.weather {
+                    Some(weather) => weather == &current_weather.get_kind(),
+                    None => true,
                 }
             })
             .filter(|track| {
@@ -457,6 +465,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
                     RawSoundtrackItem::Segmented {
                         title,
                         timing,
+                        weather,
                         biomes,
                         site,
                         segments,
@@ -467,6 +476,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
                                 path,
                                 length,
                                 timing: timing.clone(),
+                                weather: weather.clone(),
                                 biomes: biomes.clone(),
                                 site,
                                 music_state,
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 5a89a19a7f..f60963d12f 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -325,15 +325,17 @@ impl SessionState {
                 client::Event::MapMarker(event) => {
                     self.hud.show.update_map_markers(event);
                 },
-                client::Event::WeatherUpdate(weather) => {
+                client::Event::WeatherUpdate => {
                     //weather
                     //    .iter_mut()
                     //    .for_each(|(p, c)| *c = if (p.x + p.y) % 2 == 0 { 1.0 } else { 0.0 });
+                    let size = client.get_weather().size();
                     global_state.window.renderer_mut().update_texture(
                         &self.scene.lod.get_data().clouds,
                         [0, 0],
-                        [weather.size().x as u32, weather.size().y as u32],
-                        weather
+                        [size.x as u32, size.y as u32],
+                        client
+                            .get_weather()
                             .iter()
                             .map(|(_, w)| {
                                 [

From 78c1de19cf7d8f5d4576915256748de035783f46 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 20 Feb 2022 17:04:58 +0100
Subject: [PATCH 05/71] More accurate weather sim

---
 assets/voxygen/shaders/include/globals.glsl |   3 +-
 assets/voxygen/shaders/include/lod.glsl     |   2 -
 assets/voxygen/shaders/include/sky.glsl     |  18 +-
 assets/voxygen/shaders/sprite-vert.glsl     |   1 +
 client/src/lib.rs                           |   6 +
 common/src/weather.rs                       |  16 +
 server/src/weather/mod.rs                   |   4 +-
 server/src/weather/sim.rs                   | 375 ++++++++++++--------
 server/src/weather/tick.rs                  |   8 +-
 voxygen/src/hud/mod.rs                      |  22 +-
 voxygen/src/render/pipelines/mod.rs         |   6 +-
 voxygen/src/scene/mod.rs                    |   1 +
 voxygen/src/scene/simple.rs                 |   1 +
 voxygen/src/session/mod.rs                  |   4 +-
 14 files changed, 294 insertions(+), 173 deletions(-)

diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl
index af2fcff835..8e822cf988 100644
--- a/assets/voxygen/shaders/include/globals.glsl
+++ b/assets/voxygen/shaders/include/globals.glsl
@@ -19,6 +19,7 @@ layout(std140, set = 0, binding = 0) uniform u_globals {
     uvec4 medium;
     ivec4 select_pos;
     vec4 gamma_exposure;
+    vec2 wind_vel;
     float ambiance;
     // 0 - FirstPerson
     // 1 - ThirdPerson
@@ -37,6 +38,4 @@ mat4 threshold_matrix = mat4(
 float distance_divider = 2;
 float shadow_dithering = 0.5;
 
-vec2 wind_vel = vec2(0.0);
-
 #endif
diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index ef9143b05a..2235ced5d6 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -5,8 +5,6 @@
 #include <sky.glsl>
 #include <srgb.glsl>
 
-layout(set = 0, binding = 5) uniform texture2D t_alt;
-layout(set = 0, binding = 6) uniform sampler s_alt;
 layout(set = 0, binding = 7) uniform texture2D t_horizon;
 layout(set = 0, binding = 8) uniform sampler s_horizon;
 
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 65dd47a3d8..580d6f94db 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -97,6 +97,9 @@ vec2 wind_offset = vec2(time_of_day.x * wind_speed);
 
 float cloud_scale = view_distance.z / 150.0;
 
+layout(set = 0, binding = 5) uniform texture2D t_alt;
+layout(set = 0, binding = 6) uniform sampler s_alt;
+
 vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) {
     // Want: (pixel + 0.5) / W
     vec2 texSize = textureSize(sampler2D(tex, s), 0);
@@ -105,19 +108,22 @@ vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) {
 }
 
 // Weather texture
-layout(set = 0, binding = 12) uniform texture2D t_clouds;
-layout(set = 0, binding = 13) uniform sampler s_clouds;
+layout(set = 0, binding = 12) uniform texture2D t_weather;
+layout(set = 0, binding = 13) uniform sampler s_weather;
+
+vec4 sample_weather(vec2 wpos) {
+    return textureLod(sampler2D(t_weather, s_weather), pos_to_uv(t_alt, s_alt, wpos - focus_off.xy), 0); // TODO: make this work for any world size
+}
 
 float cloud_tendency_at(vec2 pos) {
-    float nz = textureLod/*textureBicubic16*/(sampler2D(t_clouds, s_clouds), pos / 33500 , 0).r;
     //float nz = textureLod(sampler2D(t_noise, s_noise), (pos + wind_offset) / 60000.0 / cloud_scale, 0).x - 0.3;
-    nz = pow(nz, 3);
-    return nz;
+    return sample_weather(pos).r;
 }
 
 const float RAIN_CLOUD = 0.05;
 float rain_density_at(vec2 pos) {
-    return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
+    return sample_weather(pos).g;
+    //return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
 }
 
 float cloud_shadow(vec3 pos, vec3 light_dir) {
diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl
index bdd402a41e..f36fd6a79b 100644
--- a/assets/voxygen/shaders/sprite-vert.glsl
+++ b/assets/voxygen/shaders/sprite-vert.glsl
@@ -92,6 +92,7 @@ void main() {
     #endif
 
     #ifndef EXPERIMENTAL_BAREMINIMUM
+        // TODO: take wind_vel into account
         // Wind sway effect
         f_pos += model_wind_sway * vec3(
             sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35),
diff --git a/client/src/lib.rs b/client/src/lib.rs
index 16812bdab1..60a2e47ae9 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -1430,6 +1430,12 @@ impl Client {
         }
     }
 
+    pub fn current_weather_wpos(&self, wpos: Vec2<f32>) -> Weather {
+        let cell_pos =
+            (wpos / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_())).as_();
+        *self.weather.get(cell_pos).unwrap_or(&Weather::default())
+    }
+
     pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
         let chunk_pos = Vec2::from(self.position()?)
             .map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 2645412620..486c66258c 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -1,3 +1,5 @@
+use std::fmt;
+
 use serde::{Deserialize, Serialize};
 use vek::Vec2;
 
@@ -5,8 +7,11 @@ pub const CHUNKS_PER_CELL: u32 = 16;
 // Weather::default is Clear, 0 degrees C and no wind
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
 pub struct Weather {
+    /// Clouds currently in the area between 0 and 1
     pub cloud: f32,
+    /// Rain per time, between 0 and 1
     pub rain: f32,
+    // Wind direction in block / second
     pub wind: Vec2<f32>,
 }
 
@@ -35,3 +40,14 @@ pub enum WeatherKind {
     Rain,
     Storm,
 }
+
+impl fmt::Display for WeatherKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            WeatherKind::Clear => write!(f, "Clear"),
+            WeatherKind::Cloudy => write!(f, "Cloudy"),
+            WeatherKind::Rain => write!(f, "Rain"),
+            WeatherKind::Storm => write!(f, "Storm"),
+        }
+    }
+}
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index 5c95cd09bf..bbf2a98085 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -24,11 +24,11 @@ pub fn init(state: &mut State, world: &world::World) {
     state
         .ecs_mut()
         .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(
-            0.1,
+            sim::DT / 100.0,
         )));
     state
         .ecs_mut()
         .insert(SysScheduler::<sync::Sys>::every(Duration::from_secs_f32(
-            0.1,
+            sim::DT / 100.0,
         )));
 }
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 6578da7660..39599ff152 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -1,12 +1,8 @@
-use std::{
-    ops::{Add, Deref, DerefMut, Div, Mul},
-    sync::Arc,
-};
-
 use common::{
     grid::Grid,
     resources::TimeOfDay,
     terrain::{BiomeKind, TerrainChunkSize},
+    time::DayPeriod,
     vol::RectVolSize,
     weather::{Weather, CHUNKS_PER_CELL},
 };
@@ -19,10 +15,10 @@ use world::{
 
 #[derive(Default)]
 pub struct Constants {
-    drag: f32, // How much drag therfe is on wind. Caused by slopes.
-    humidity: Option<f32>,
-    temperature_day: Option<f32>,
-    temperature_night: Option<f32>,
+    altitude: f32,
+    water: f32,
+    temperature_day: f32,
+    temperature_night: f32,
 }
 
 #[derive(Clone, Copy, Default)]
@@ -30,83 +26,173 @@ struct Cell {
     wind: Vec2<f32>,
     temperature: f32,
     moisture: f32,
-    rain: f32,
+    cloud: f32,
 }
 /// Used to sample weather that isn't simulated
 fn sample_cell(p: Vec2<i32>, time: f64) -> Cell {
-    let noise = FastNoise::new(0b10110_101_1100_1111_10010_101_1110);
-
+    /*let noise = FastNoise::new(0b10110_101_1100_1111_10010_101_1110);
+    // Feeding sensible values into the simulation
     Cell {
-        wind: Vec2::new(noise.get(p.with_z(0).as_()), noise.get(p.with_z(100).as_()))
-            * ((noise.get(p.with_z(200).as_()) + 1.0) * 0.5).powf(4.0)
-            * 30.0,
-        temperature: noise.get(p.with_z(300).as_()).powf(3.0) * 10.0 + 20.0, // 10 -> 30
-        moisture: BASE_HUMIDITY,
-        rain: ((noise.get(p.with_z(400).as_()) + 1.0) * 0.5).powf(4.0) * MAX_RAIN,
+        wind: Vec2::new(
+            noise.get(p.as_().with_z(time / 1000.0)),
+            noise.get(p.as_().with_z(-time / 1000.0)),
+        ) * ((noise.get(p.as_().with_z(time / 1000.0 + 200.0)) + 1.0) * 0.5).powf(2.0)
+            * 30.0
+            + 5.0,
+        temperature: noise.get(p.as_().with_z(time / 1000.0 + 300.0)).powf(3.0) * 10.0
+            + BASE_TEMPERATURE
+            + 273.3, /* 10 -> 30 C */
+        moisture: BASE_MOISTURE + noise.get(p.as_().with_z(time / 100.0 + 400.0)).powf(3.0) * 0.2,
+        cloud: noise
+            .get((p.as_() / 2.0).with_z(time / 100.0 + 400.0))
+            .powf(2.0)
+            * 1.0,
+    } */
+    Cell {
+        wind: Vec2::new(10.0, 0.0),
+        temperature: 20.0,
+        moisture: 0.0,
+        cloud: 0.0,
     }
 }
 
 pub struct WeatherSim {
-    cells: Grid<Cell>,
-    constants: Grid<Constants>,
-
-    weather: Grid<Weather>, // The current weather.
+    cells: Grid<Cell>,          // The variables used for simulation
+    constants: Grid<Constants>, // The constants from the world used for simulation
+    weather: Grid<Weather>,     // The current weather.
 }
 
-const BASE_HUMIDITY: f32 = 1.6652;
+const BASE_MOISTURE: f32 = 1.0;
 const BASE_TEMPERATURE: f32 = 20.0;
-const MAX_RAIN: f32 = 100.0;
+const WATER_BOILING_POINT: f32 = 373.3;
+const MAX_WIND_SPEED: f32 = 60.0;
+const CELL_SIZE: f32 = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x) as f32;
+pub(crate) const DT: f32 = CELL_SIZE / MAX_WIND_SPEED;
 
 impl WeatherSim {
     pub fn new(size: Vec2<u32>, world: &World) -> Self {
         let size = size.as_();
-        Self {
+        let mut this = Self {
             cells: Grid::new(size, Cell::default()),
             constants: Grid::from_raw(
                 size,
                 (0..size.x * size.y)
                     .map(|i| Vec2::new(i as i32 % size.x, i as i32 / size.x))
                     .map(|p| {
-                        let mut c = Constants::default();
+                        let add = |member: &mut f32, v| {
+                            *member += v;
+                            *member /= 2.0;
+                        };
+                        let mut temperature_night_r = 0.0;
+                        let mut temperature_night = |v| {
+                            add(&mut temperature_night_r, v);
+                        };
+                        let mut temperature_day_r = 0.0;
+                        let mut temperature_day = |v| {
+                            add(&mut temperature_day_r, v);
+                        };
+                        let mut altitude_r = 0.0;
+                        let mut altitude = |v| {
+                            add(&mut altitude_r, v);
+                        };
+                        let mut water_r = 0.0;
                         for y in 0..CHUNKS_PER_CELL as i32 {
                             for x in 0..CHUNKS_PER_CELL as i32 {
                                 let chunk_pos = p * CHUNKS_PER_CELL as i32 + Vec2::new(x, y);
                                 if let Some(chunk) = world.sim().get(chunk_pos) {
-                                    c.drag +=
+                                    let a =
                                         world.sim().get_gradient_approx(chunk_pos).unwrap_or(0.0);
-                                    if chunk.is_underwater() {
-                                        c.humidity =
-                                            Some(c.humidity.unwrap_or(0.0) + BASE_HUMIDITY);
-                                        c.temperature_night =
-                                            Some(c.temperature_night.unwrap_or(0.0) + 0.01);
-                                    } else {
-                                        c.temperature_day =
-                                            Some(c.temperature_day.unwrap_or(0.0) + 0.01);
-                                    }
+                                    altitude(a);
+
+                                    let height_p = 1.0
+                                        - world.sim().get_alt_approx(chunk_pos).unwrap_or(0.0)
+                                            / world.sim().max_height;
+
+                                    let mut water = |v| {
+                                        add(&mut water_r, v * height_p);
+                                    };
+
                                     match chunk.get_biome() {
                                         BiomeKind::Desert => {
-                                            c.temperature_day =
-                                                Some(c.temperature_day.unwrap_or(0.0) + 0.01);
-                                            c.humidity = Some(
-                                                c.humidity.unwrap_or(0.0) - 10.0 * BASE_HUMIDITY,
-                                            );
+                                            water(0.0);
+                                            temperature_night(11.0);
+                                            temperature_day(30.0);
+                                        },
+                                        BiomeKind::Savannah => {
+                                            water(0.02);
+                                            temperature_night(13.0);
+                                            temperature_day(26.0);
                                         },
                                         BiomeKind::Swamp => {
-                                            c.humidity = Some(
-                                                c.humidity.unwrap_or(0.0) + 2.0 * BASE_HUMIDITY,
-                                            );
+                                            water(0.8);
+                                            temperature_night(16.0);
+                                            temperature_day(24.0);
+                                        },
+                                        BiomeKind::Mountain => {
+                                            water(0.01);
+                                            temperature_night(13.0);
+                                            temperature_day(14.0);
+                                        },
+                                        BiomeKind::Grassland => {
+                                            water(0.05);
+                                            temperature_night(15.0);
+                                            temperature_day(20.0);
+                                        },
+                                        BiomeKind::Snowland => {
+                                            water(0.005);
+                                            temperature_night(-8.0);
+                                            temperature_day(-1.0);
+                                        },
+                                        BiomeKind::Jungle => {
+                                            water(0.4);
+                                            temperature_night(20.0);
+                                            temperature_day(27.0);
+                                        },
+                                        BiomeKind::Forest => {
+                                            water(0.1);
+                                            temperature_night(16.0);
+                                            temperature_day(19.0);
+                                        },
+                                        BiomeKind::Taiga => {
+                                            water(0.02);
+                                            temperature_night(1.0);
+                                            temperature_day(10.0);
+                                        },
+                                        BiomeKind::Lake => {
+                                            water(1.0);
+                                            temperature_night(20.0);
+                                            temperature_day(18.0);
+                                        },
+                                        BiomeKind::Ocean => {
+                                            water(0.98);
+                                            temperature_night(19.0);
+                                            temperature_day(17.0);
+                                        },
+                                        BiomeKind::Void => {
+                                            water(0.0);
+                                            temperature_night(20.0);
+                                            temperature_day(20.0);
                                         },
-                                        _ => {},
                                     }
                                 }
                             }
                         }
-                        c
+                        Constants {
+                            altitude: altitude_r,
+                            water: water_r,
+                            temperature_day: temperature_day_r,
+                            temperature_night: temperature_day_r,
+                        }
                     })
                     .collect_vec(),
             ),
             weather: Grid::new(size, Weather::default()),
-        }
+        };
+        this.cells.iter_mut().for_each(|(point, cell)| {
+            let time = 0.0;
+            *cell = sample_cell(point, time);
+        });
+        this
     }
 
     pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
@@ -121,61 +207,67 @@ impl WeatherSim {
 
     // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
     // Time step is cell size / maximum wind speed
-    pub fn tick(&mut self, world: &World, time_of_day: &TimeOfDay, dt: f32) {
-        const MAX_WIND_SPEED: f32 = 127.0;
-        let cell_size: Vec2<f32> = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE).as_();
-        let dt = cell_size.x / MAX_WIND_SPEED;
+    pub fn tick(&mut self, time_of_day: &TimeOfDay, dt: f32) {
+        let time = time_of_day.0;
         let mut swap = Grid::new(self.cells.size(), Cell::default());
         // Dissipate wind, humidty and pressure
-        //Dispersion is represented by the target cell expanding into the 8 adjacent
+        // Dissipation is represented by the target cell expanding into the 8 adjacent
         // cells. The target cell’s contents are then distributed based the
         // percentage that overlaps the surrounding cell.
         for (point, cell) in self.cells.iter() {
             swap[point] = {
                 let spread = [
-                    (*cell, 1. / 4.),
-                    (
-                        self.get_cell(point + Vec2::new(1, 0), time_of_day.0),
-                        1. / 8.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(-1, 0), time_of_day.0),
-                        1. / 8.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(0, 1), time_of_day.0),
-                        1. / 8.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(0, -1), time_of_day.0),
-                        1. / 8.,
-                    ),
+                    (*cell, 0),
+                    (self.get_cell(point + Vec2::new(1, 0), time), 1),
+                    (self.get_cell(point + Vec2::new(-1, 0), time), 1),
+                    (self.get_cell(point + Vec2::new(0, 1), time), 1),
+                    (self.get_cell(point + Vec2::new(0, -1), time), 1),
                     // Diagonal so less overlap
-                    (
-                        self.get_cell(point + Vec2::new(1, 1), time_of_day.0),
-                        1. / 16.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(1, -1), time_of_day.0),
-                        1. / 16.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(-1, 1), time_of_day.0),
-                        1. / 16.,
-                    ),
-                    (
-                        self.get_cell(point + Vec2::new(-1, -1), time_of_day.0),
-                        1. / 16.,
-                    ),
+                    (self.get_cell(point + Vec2::new(1, 1), time), 2),
+                    (self.get_cell(point + Vec2::new(1, -1), time), 2),
+                    (self.get_cell(point + Vec2::new(-1, 1), time), 2),
+                    (self.get_cell(point + Vec2::new(-1, -1), time), 2),
                 ];
                 let mut cell = Cell::default();
 
-                for (c, factor) in spread {
-                    cell.wind += c.wind * factor;
-                    cell.temperature += c.temperature * factor;
-                    cell.moisture += c.moisture * factor;
-                }
+                for (c, p) in spread {
+                    let factor = |rate: f32| {
+                        let rate = (1.0 + rate).powf(DT);
+                        //              1.0
+                        //      ___________________
+                        //     /                   \
+                        // +---+-------------------+---+
+                        // | 2 |         1         | 2 |
+                        // +---+-------------------+---+
+                        // |   |                   |   |
+                        // |   |                   |   |
+                        // |   |                   |   |
+                        // | 1 |         0         | 1 |
+                        // |   |                   |   |
+                        // |   |                   |   |
+                        // |   |                   |   |
+                        // +---+-------------------+---+  \
+                        // | 2 |         1         | 2 |  | rate
+                        // +---+-------------------+---+  /
+                        // \___________________________/
+                        //        2.0 * rate + 1.0
+                        // area_0 = 1.0 * 1.0
+                        // area_1 = rate * 1.0
+                        // area_2 = rate * rate
 
+                        let area = (1.0 + 2.0 * rate).powf(2.0);
+                        match p {
+                            0 => 1.0 * 1.0 / area,   // area_0 / area
+                            1 => rate * 1.0 / area,  // area_1 / area
+                            _ => rate * rate / area, // area_2/ area
+                        }
+                    };
+                    //  0.0 <= dissipation rate <= 1.0 because we only spread to direct neighbours.
+                    cell.wind += c.wind * factor(0.0055);
+                    cell.temperature += c.temperature * factor(0.007);
+                    cell.moisture += c.moisture * factor(0.003);
+                    cell.cloud += c.cloud * factor(0.001);
+                }
                 cell
             }
         }
@@ -194,23 +286,23 @@ impl WeatherSim {
         for y in -1..=self.cells.size().y {
             for x in -1..=self.cells.size().x {
                 let point = Vec2::new(x, y);
-                let cell = self.get_cell(point, time_of_day.0);
-                let a = cell_size.x - cell.wind.x.abs();
-                let b = cell_size.y - cell.wind.y.abs();
+                let cell = self.get_cell(point, time);
+                let a = CELL_SIZE - cell.wind.x.abs() * DT;
+                let b = CELL_SIZE - cell.wind.y.abs() * DT;
                 let wind_dir = Vec2::new(cell.wind.x.signum(), cell.wind.y.signum()).as_();
                 let spread = [
-                    (point, a * b / (cell_size.x * cell_size.y)),
+                    (point, a * b / (CELL_SIZE * CELL_SIZE)),
                     (
                         point + wind_dir.with_y(0),
-                        (cell_size.x - a) * b / (cell_size.x * cell_size.y),
+                        (CELL_SIZE - a) * b / (CELL_SIZE * CELL_SIZE),
                     ),
                     (
                         point + wind_dir.with_x(0),
-                        a * (cell_size.y - b) / (cell_size.x * cell_size.y),
+                        a * (CELL_SIZE - b) / (CELL_SIZE * CELL_SIZE),
                     ),
                     (
                         point + wind_dir,
-                        (cell_size.x - a) * (cell_size.y - b) / (cell_size.x * cell_size.y),
+                        (CELL_SIZE - a) * (CELL_SIZE - b) / (CELL_SIZE * CELL_SIZE),
                     ),
                 ];
                 for (p, factor) in spread {
@@ -218,79 +310,58 @@ impl WeatherSim {
                         c.wind += cell.wind * factor;
                         c.temperature += cell.temperature * factor;
                         c.moisture += cell.moisture * factor;
-                        c.rain += cell.rain * factor;
+                        c.cloud += cell.cloud * factor;
                     }
                 }
             }
         }
         self.cells = swap.clone();
 
-        // TODO: wind curl, rain condesnsing from moisture. And interacting with world
-        // elements.
+        // TODO: wind curl
 
         // Evaporate moisture and condense clouds
         for (point, cell) in self.cells.iter_mut() {
-            // r = rain, m = moisture
-            // ∆r = Rcond − Rrain.
-            // ∆m = −Rcond + V.
-            // Rcond = δ(T0(m) − T)H(T0(m) − T) − γ(T − T0(m))H(T − T0(m)).
-            // T0(m) = (100−m) / 5
+            let dt = 1.0 - 0.96f32.powf(DT);
+            let day_light = (1.0 - (1.0 - time_of_day.0 as f32 / 12.0 * 60.0 * 60.0).abs())
+                * self.weather[point].cloud;
 
-            // V = B(T − Tv(h))H(T − Tv(h))
-            // γ = 0.5, δ = 0.25, B = 0.5 and Tv(h) = 20◦C
+            cell.temperature = cell.temperature * (1.0 - dt)
+                + dt * (self.constants[point].temperature_day * day_light
+                    + self.constants[point].temperature_night * (1.0 - day_light));
 
-            // TODO: make these parameters depend on the world.
-            // TODO: figure out what these variables mean.
-            let gamma = 0.5;
-            let delta = 0.25;
-            let b = 0.5;
-            let h = 1.0;
-            let t_v = BASE_TEMPERATURE;
+            // Evaporate from ground.
+            let temp_part = (cell.temperature / WATER_BOILING_POINT)
+                //.powf(2.0)
+                .clamp(0.0, 1.0);
+            cell.moisture += (self.constants[point].water / 10.0) * temp_part * DT;
 
-            let rain_fall_max = 0.05;
-
-            let evaporation = b * (cell.temperature - t_v) * h * (cell.temperature - t_v);
-
-            let dew_point = (100.0 - cell.moisture) / 5.0;
-
-            let condensation =
-                delta * (dew_point - cell.temperature) * h * (dew_point - cell.temperature)
-                    - gamma * (cell.temperature - dew_point) * h * (cell.temperature - dew_point);
-            cell.rain += condensation;
-            if cell.rain > rain_fall_max {
-                cell.rain -= rain_fall_max;
-                self.weather[point].rain = 1.0;
+            // If positive condense moisture to clouds, if negative evaporate clouds into
+            // moisture.
+            let condensation = if cell.moisture > 1.0 {
+                cell.moisture.powf(2.0 / 3.0)
             } else {
-                self.weather[point].rain = 0.5;
-            }
-            cell.moisture += evaporation - condensation;
+                cell.moisture
+            } * temp_part
+                * DT;
 
-            self.weather[point].cloud = (cell.rain / MAX_RAIN).clamp(0.0, 1.0);
+            cell.moisture -= condensation;
+            cell.cloud += condensation;
+
+            const CLOUD_MAX: f32 = 15.0;
+            const RAIN_P: f32 = 0.96;
+            let rain_cloud = temp_part * CLOUD_MAX;
+
+            let rain_p_t = ((cell.cloud - rain_cloud) / (CLOUD_MAX - rain_cloud)).max(0.0);
+            let rain = rain_p_t * (1.0 - RAIN_P.powf(DT));
+            cell.cloud -= rain;
+            cell.cloud = cell.cloud.max(0.0);
+
+            self.weather[point].cloud = (cell.cloud / CLOUD_MAX).clamp(0.0, 1.0);
+            self.weather[point].rain = rain_p_t;
             self.weather[point].wind = cell.wind;
         }
 
         // Maybe moisture condenses to clouds, which if they have a certain
         // amount they will release rain.
     }
-
-    fn update_info(&mut self) {
-        let w = self
-            .cells
-            .iter()
-            .map(|(p, c)| {
-                (
-                    p,
-                    Weather::new(
-                        //if p.x % 2 == p.y % 2 { 1.0 } else { 0.0 },
-                        (self.constants[p].humidity.unwrap_or(0.0) * 100.0).clamp(0.0, 1.0),
-                        0.0,
-                        c.wind,
-                    ),
-                )
-            })
-            .collect_vec();
-        w.iter().for_each(|&(p, w)| {
-            self.weather[p] = w;
-        });
-    }
 }
diff --git a/server/src/weather/tick.rs b/server/src/weather/tick.rs
index d95c4ef260..2f6f0033da 100644
--- a/server/src/weather/tick.rs
+++ b/server/src/weather/tick.rs
@@ -14,7 +14,6 @@ pub struct Sys;
 impl<'a> System<'a> for Sys {
     type SystemData = (
         Read<'a, TimeOfDay>,
-        ReadExpect<'a, Arc<world::World>>,
         WriteExpect<'a, WeatherSim>,
         Write<'a, SysScheduler<Self>>,
     );
@@ -23,12 +22,9 @@ impl<'a> System<'a> for Sys {
     const ORIGIN: Origin = Origin::Server;
     const PHASE: Phase = Phase::Create;
 
-    fn run(
-        job: &mut common_ecs::Job<Self>,
-        (game_time, world, mut sim, mut scheduler): Self::SystemData,
-    ) {
+    fn run(job: &mut common_ecs::Job<Self>, (game_time, mut sim, mut scheduler): Self::SystemData) {
         if scheduler.should_run() {
-            sim.tick(&*world, &*game_time, 1.0);
+            sim.tick(&*game_time, 1.0);
         }
     }
 }
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index 3021f68b71..e0d04f3353 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -101,6 +101,7 @@ use common::{
     uid::Uid,
     util::{srgba_to_linear, Dir},
     vol::RectRasterableVol,
+    weather::Weather,
 };
 use common_base::{prof_span, span};
 use common_net::{
@@ -264,6 +265,7 @@ widget_ids! {
         current_site,
         graphics_backend,
         gpu_timings[],
+        weather,
 
         // Game Version
         version,
@@ -492,6 +494,7 @@ pub struct DebugInfo {
     pub num_figures_visible: u32,
     pub num_particles: u32,
     pub num_particles_visible: u32,
+    pub weather: Weather,
 }
 
 pub struct HudInfo {
@@ -2386,11 +2389,27 @@ impl Hud {
                 .font_size(self.fonts.cyri.scale(14))
                 .set(self.ids.time, ui_widgets);
 
+            let weather = client.current_weather();
+            Text::new(&format!(
+                "Weather({kind:.5}): {{cloud: {cloud:.5}, rain: {rain:.5}, wind: <{wind_x:.5}, \
+                 {wind_y:.2}>}}",
+                kind = weather.get_kind(),
+                cloud = weather.cloud,
+                rain = weather.rain,
+                wind_x = weather.wind.x,
+                wind_y = weather.wind.y
+            ))
+            .color(TEXT_COLOR)
+            .down_from(self.ids.time, V_PAD)
+            .font_id(self.fonts.cyri.conrod_id)
+            .font_size(self.fonts.cyri.scale(14))
+            .set(self.ids.weather, ui_widgets);
+
             // Number of entities
             let entity_count = client.state().ecs().entities().join().count();
             Text::new(&format!("Entity count: {}", entity_count))
                 .color(TEXT_COLOR)
-                .down_from(self.ids.time, V_PAD)
+                .down_from(self.ids.weather, V_PAD)
                 .font_id(self.fonts.cyri.conrod_id)
                 .font_size(self.fonts.cyri.scale(14))
                 .set(self.ids.entity_count, ui_widgets);
@@ -2503,6 +2522,7 @@ impl Hud {
 
             // Set debug box dimensions, only timings height is dynamic
             // TODO: Make the background box size fully dynamic
+
             let debug_bg_size = [320.0, 370.0 + timings_height];
 
             Rectangle::fill(debug_bg_size)
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index 5a913dd44f..abee155b9f 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -63,6 +63,7 @@ pub struct Globals {
     medium: [u32; 4],
     select_pos: [i32; 4],
     gamma_exposure: [f32; 4],
+    wind_vel: [f32; 2],
     ambiance: f32,
     cam_mode: u32,
     sprite_render_distance: f32,
@@ -108,6 +109,7 @@ impl Globals {
         ambiance: f32,
         cam_mode: CameraMode,
         sprite_render_distance: f32,
+        wind_vel: Vec2<f32>,
     ) -> Self {
         Self {
             view_mat: view_mat.into_col_arrays(),
@@ -153,6 +155,7 @@ impl Globals {
                 .unwrap_or_else(Vec4::zero)
                 .into_array(),
             gamma_exposure: [gamma, exposure, 0.0, 0.0],
+            wind_vel: [wind_vel.x, wind_vel.y],
             ambiance: ambiance.clamped(0.0, 1.0),
             cam_mode: cam_mode as u32,
             sprite_render_distance,
@@ -202,6 +205,7 @@ impl Default for Globals {
             1.0,
             CameraMode::ThirdPerson,
             250.0,
+            Vec2::zero(),
         )
     }
 }
@@ -401,7 +405,7 @@ impl GlobalsLayouts {
                 },
                 count: None,
             },
-            // clouds t_clouds
+            // clouds t_weather
             wgpu::BindGroupLayoutEntry {
                 binding: 12,
                 visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 9041035479..6f29dc0434 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -677,6 +677,7 @@ impl Scene {
             scene_data.ambiance,
             self.camera.get_mode(),
             scene_data.sprite_render_distance as f32 - 20.0,
+            client.current_weather_wpos(cam_pos.xy()).wind,
         )]);
         renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
         renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs
index 4bda1f2d92..cf7be2cb9f 100644
--- a/voxygen/src/scene/simple.rs
+++ b/voxygen/src/scene/simple.rs
@@ -274,6 +274,7 @@ impl Scene {
             scene_data.ambiance,
             self.camera.get_mode(),
             250.0,
+            Vec2::zero(),
         )]);
 
         self.figure_model_cache
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index f60963d12f..50e997c868 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -338,9 +338,10 @@ impl SessionState {
                             .get_weather()
                             .iter()
                             .map(|(_, w)| {
+                                //println!("{}, ", w.cloud);
                                 [
                                     (w.cloud * 255.0) as u8,
-                                    (w.rain + 128.0).clamp(0.0, 255.0) as u8,
+                                    (w.rain * 255.0) as u8,
                                     (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
                                     (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
                                 ]
@@ -1126,6 +1127,7 @@ impl PlayState for SessionState {
                     num_particles: self.scene.particle_mgr().particle_count() as u32,
                     num_particles_visible: self.scene.particle_mgr().particle_count_visible()
                         as u32,
+                    weather: client.current_weather(),
                 }
             });
 

From 547301ff5f8dbc029e42c94a5a281f611885a5ff Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 6 Feb 2022 20:33:35 +0100
Subject: [PATCH 06/71] rainbows

---
 .../shaders/include/cloud/regular.glsl        | 25 ++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index d3f461098e..d8291d6396 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -237,12 +237,18 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
         // i is an emergency brake
         float min_dist = clamp(max_dist / 4, 0.25, 24);
         int i;
+
+        float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
+        int rainbow_c = int(floor(rainbow_t));
+        rainbow_t = fract(rainbow_t);
+
         for (i = 0; cdist > min_dist && i < 250; i ++) {
             ldist = cdist;
             cdist = step_to_dist(trunc(dist_to_step(cdist - 0.25, quality)), quality);
 
             vec3 emission;
             float not_underground; // Used to prevent sunlight leaking underground
+            vec3 pos = origin + dir * ldist * splay;
             // `sample` is a reserved keyword
             vec4 sample_ = cloud_at(origin + dir * ldist * splay, ldist, emission, not_underground);
 
@@ -263,10 +269,27 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
                 // Attenuate light passing through the clouds
                 surf_color * cloud_darken * global_darken +
                 // Add the directed light light scattered into the camera by the clouds and the atmosphere (global illumination)
-                sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken)  * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
+                sun_color * sun_scatter * get_sun_brightness() * (sun_access * (1.0 - cloud_darken) * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
                 moon_color * moon_scatter * get_moon_brightness() * (moon_access * (1.0 - cloud_darken) * cloud_diffuse /*+ sky_color * global_scatter_factor*/) +
                 sky_light * (1.0 - global_darken) * not_underground +
                 emission * density_integrals.y * step;
+
+            // Rainbow
+            if (rainbow_c >= 0 && rainbow_c < 8) {
+                float rain = rain_density_at(pos.xy);
+                vec3 colors[9] = {
+                    surf_color,
+                    vec3(0.9, 0.5, 0.9),
+                    vec3(0.25, 0.0, 0.5),
+                    vec3(0.0, 0.0, 1.0),
+                    vec3(0.0, 0.5, 0.0),
+                    vec3(1.0, 1.0, 0.0),
+                    vec3(1.0, 0.6, 0.0),
+                    vec3(1.0, 0.0, 0.0),
+                    surf_color,
+                };
+                surf_color = mix(surf_color, mix(colors[rainbow_c], colors[rainbow_c + 1], rainbow_t), rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0));
+            }
         }
     #ifdef IS_POSTPROCESS
         }

From 0c804af7736cd91c5b96df19a7c8f90e094160bf Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Fri, 26 Nov 2021 21:51:16 +0100
Subject: [PATCH 07/71] reasonable delta time

---
 server/src/weather/mod.rs |  4 ++--
 server/src/weather/sim.rs | 18 ------------------
 2 files changed, 2 insertions(+), 20 deletions(-)

diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index bbf2a98085..35137620d9 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -24,11 +24,11 @@ pub fn init(state: &mut State, world: &world::World) {
     state
         .ecs_mut()
         .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(
-            sim::DT / 100.0,
+            sim::DT,
         )));
     state
         .ecs_mut()
         .insert(SysScheduler::<sync::Sys>::every(Duration::from_secs_f32(
-            sim::DT / 100.0,
+            sim::DT,
         )));
 }
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 39599ff152..add64e42ea 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -30,24 +30,6 @@ struct Cell {
 }
 /// Used to sample weather that isn't simulated
 fn sample_cell(p: Vec2<i32>, time: f64) -> Cell {
-    /*let noise = FastNoise::new(0b10110_101_1100_1111_10010_101_1110);
-    // Feeding sensible values into the simulation
-    Cell {
-        wind: Vec2::new(
-            noise.get(p.as_().with_z(time / 1000.0)),
-            noise.get(p.as_().with_z(-time / 1000.0)),
-        ) * ((noise.get(p.as_().with_z(time / 1000.0 + 200.0)) + 1.0) * 0.5).powf(2.0)
-            * 30.0
-            + 5.0,
-        temperature: noise.get(p.as_().with_z(time / 1000.0 + 300.0)).powf(3.0) * 10.0
-            + BASE_TEMPERATURE
-            + 273.3, /* 10 -> 30 C */
-        moisture: BASE_MOISTURE + noise.get(p.as_().with_z(time / 100.0 + 400.0)).powf(3.0) * 0.2,
-        cloud: noise
-            .get((p.as_() / 2.0).with_z(time / 100.0 + 400.0))
-            .powf(2.0)
-            * 1.0,
-    } */
     Cell {
         wind: Vec2::new(10.0, 0.0),
         temperature: 20.0,

From 9c84a20cef943d4f6db5888c5349f728890ba48c Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 6 Feb 2022 20:34:21 +0100
Subject: [PATCH 08/71] better sim

---
 .../shaders/include/cloud/regular.glsl        |   1 +
 server/src/weather/sim.rs                     | 313 +++++++++---------
 server/src/weather/sync.rs                    |   2 +-
 server/src/weather/tick.rs                    |  11 +-
 voxygen/src/audio/music.rs                    |   2 +-
 5 files changed, 169 insertions(+), 160 deletions(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index d8291d6396..9fb365fb23 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -238,6 +238,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
         float min_dist = clamp(max_dist / 4, 0.25, 24);
         int i;
 
+        // TODO: Make it a double rainbow
         float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
         int rainbow_c = int(floor(rainbow_t));
         rainbow_t = fract(rainbow_t);
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index add64e42ea..2fa6951149 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -1,24 +1,20 @@
 use common::{
     grid::Grid,
     resources::TimeOfDay,
-    terrain::{BiomeKind, TerrainChunkSize},
-    time::DayPeriod,
+    terrain::TerrainChunkSize,
     vol::RectVolSize,
     weather::{Weather, CHUNKS_PER_CELL},
 };
 use itertools::Itertools;
 use vek::*;
-use world::{
-    util::{FastNoise, Sampler},
-    World,
-};
+use world::World;
 
 #[derive(Default)]
 pub struct Constants {
-    altitude: f32,
-    water: f32,
-    temperature_day: f32,
-    temperature_night: f32,
+    alt: f32,
+    normal: Vec3<f32>,
+    humid: f32,
+    temp: f32,
 }
 
 #[derive(Clone, Copy, Default)]
@@ -29,146 +25,117 @@ struct Cell {
     cloud: f32,
 }
 /// Used to sample weather that isn't simulated
-fn sample_cell(p: Vec2<i32>, time: f64) -> Cell {
+fn sample_cell(_p: Vec2<i32>, _time: f64) -> Cell {
     Cell {
-        wind: Vec2::new(10.0, 0.0),
-        temperature: 20.0,
-        moisture: 0.0,
+        wind: Vec2::new(20.0, 20.0),
+        temperature: 0.5,
+        moisture: 0.1,
         cloud: 0.0,
     }
 }
 
-pub struct WeatherSim {
-    cells: Grid<Cell>,          // The variables used for simulation
-    constants: Grid<Constants>, // The constants from the world used for simulation
-    weather: Grid<Weather>,     // The current weather.
+#[derive(Clone, Copy, Default)]
+pub struct WeatherInfo {
+    pub lightning_chance: f32,
 }
 
-const BASE_MOISTURE: f32 = 1.0;
-const BASE_TEMPERATURE: f32 = 20.0;
-const WATER_BOILING_POINT: f32 = 373.3;
-const MAX_WIND_SPEED: f32 = 60.0;
-const CELL_SIZE: f32 = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x) as f32;
+pub struct WeatherSim {
+    cells: Grid<Cell>,       // The variables used for simulation
+    consts: Grid<Constants>, // The constants from the world used for simulation
+    weather: Grid<Weather>,  // The current weather.
+    info: Grid<WeatherInfo>,
+}
+
+const WATER_BOILING_POINT: f32 = 2.5;
+const MAX_WIND_SPEED: f32 = 128.0;
+pub(crate) const CELL_SIZE: f32 = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x) as f32;
 pub(crate) const DT: f32 = CELL_SIZE / MAX_WIND_SPEED;
 
+fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
+    if points.len() < 3 {
+        return None;
+    }
+    let sum = points.iter().cloned().sum::<Vec3<f32>>();
+    let centroid = sum / (points.len() as f32);
+
+    let (xx, xy, xz, yy, yz, zz) = {
+        let (mut xx, mut xy, mut xz, mut yy, mut yz, mut zz) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+        for p in points {
+            let p = *p - centroid;
+            xx += p.x * p.x;
+            xy += p.x * p.y;
+            xz += p.x * p.z;
+            yy += p.y * p.y;
+            yz += p.y * p.z;
+            zz += p.z * p.z;
+        }
+        (xx, xy, xz, yy, yz, zz)
+    };
+
+    let det_x: f32 = yy * zz - yz * yz;
+    let det_y: f32 = xx * zz - xz * xz;
+    let det_z: f32 = xx * yy - xy * xy;
+
+    let det_max = det_x.max(det_y).max(det_z);
+    if det_max <= 0.0 {
+        None
+    } else if det_max == det_x {
+        Some(Vec3::new(det_x, xz * yz - xy * zz, xy * yz - xz * yy).normalized())
+    } else if det_max == det_y {
+        Some(Vec3::new(xy * yz - xy * zz, det_y, xy * xz - yz * xx).normalized())
+    } else {
+        Some(Vec3::new(xy * yz - xz * yy, xy * xz - yz * xx, det_z).normalized())
+    }
+}
+
 impl WeatherSim {
     pub fn new(size: Vec2<u32>, world: &World) -> Self {
         let size = size.as_();
         let mut this = Self {
             cells: Grid::new(size, Cell::default()),
-            constants: Grid::from_raw(
+            consts: Grid::from_raw(
                 size,
                 (0..size.x * size.y)
-                    .map(|i| Vec2::new(i as i32 % size.x, i as i32 / size.x))
+                    .map(|i| Vec2::new(i % size.x, i / size.x))
                     .map(|p| {
-                        let add = |member: &mut f32, v| {
-                            *member += v;
-                            *member /= 2.0;
-                        };
-                        let mut temperature_night_r = 0.0;
-                        let mut temperature_night = |v| {
-                            add(&mut temperature_night_r, v);
-                        };
-                        let mut temperature_day_r = 0.0;
-                        let mut temperature_day = |v| {
-                            add(&mut temperature_day_r, v);
-                        };
-                        let mut altitude_r = 0.0;
-                        let mut altitude = |v| {
-                            add(&mut altitude_r, v);
-                        };
-                        let mut water_r = 0.0;
+                        let mut temp_sum = 0.0;
+
+                        let mut alt_sum = 0.0;
+
+                        let mut humid_sum = 1000.0;
+
+                        let mut points: Vec<Vec3<f32>> =
+                            Vec::with_capacity((CHUNKS_PER_CELL * CHUNKS_PER_CELL) as usize);
                         for y in 0..CHUNKS_PER_CELL as i32 {
                             for x in 0..CHUNKS_PER_CELL as i32 {
                                 let chunk_pos = p * CHUNKS_PER_CELL as i32 + Vec2::new(x, y);
                                 if let Some(chunk) = world.sim().get(chunk_pos) {
-                                    let a =
-                                        world.sim().get_gradient_approx(chunk_pos).unwrap_or(0.0);
-                                    altitude(a);
+                                    let wpos = chunk_pos * TerrainChunkSize::RECT_SIZE.as_();
+                                    let a = world.sim().get_alt_approx(wpos).unwrap_or(0.0);
+                                    alt_sum += a;
 
-                                    let height_p = 1.0
-                                        - world.sim().get_alt_approx(chunk_pos).unwrap_or(0.0)
-                                            / world.sim().max_height;
+                                    let wpos = wpos.as_().with_z(a);
+                                    points.push(wpos);
 
-                                    let mut water = |v| {
-                                        add(&mut water_r, v * height_p);
-                                    };
+                                    let height_p = 1.0 - a / world.sim().max_height;
 
-                                    match chunk.get_biome() {
-                                        BiomeKind::Desert => {
-                                            water(0.0);
-                                            temperature_night(11.0);
-                                            temperature_day(30.0);
-                                        },
-                                        BiomeKind::Savannah => {
-                                            water(0.02);
-                                            temperature_night(13.0);
-                                            temperature_day(26.0);
-                                        },
-                                        BiomeKind::Swamp => {
-                                            water(0.8);
-                                            temperature_night(16.0);
-                                            temperature_day(24.0);
-                                        },
-                                        BiomeKind::Mountain => {
-                                            water(0.01);
-                                            temperature_night(13.0);
-                                            temperature_day(14.0);
-                                        },
-                                        BiomeKind::Grassland => {
-                                            water(0.05);
-                                            temperature_night(15.0);
-                                            temperature_day(20.0);
-                                        },
-                                        BiomeKind::Snowland => {
-                                            water(0.005);
-                                            temperature_night(-8.0);
-                                            temperature_day(-1.0);
-                                        },
-                                        BiomeKind::Jungle => {
-                                            water(0.4);
-                                            temperature_night(20.0);
-                                            temperature_day(27.0);
-                                        },
-                                        BiomeKind::Forest => {
-                                            water(0.1);
-                                            temperature_night(16.0);
-                                            temperature_day(19.0);
-                                        },
-                                        BiomeKind::Taiga => {
-                                            water(0.02);
-                                            temperature_night(1.0);
-                                            temperature_day(10.0);
-                                        },
-                                        BiomeKind::Lake => {
-                                            water(1.0);
-                                            temperature_night(20.0);
-                                            temperature_day(18.0);
-                                        },
-                                        BiomeKind::Ocean => {
-                                            water(0.98);
-                                            temperature_night(19.0);
-                                            temperature_day(17.0);
-                                        },
-                                        BiomeKind::Void => {
-                                            water(0.0);
-                                            temperature_night(20.0);
-                                            temperature_day(20.0);
-                                        },
-                                    }
+                                    let env = chunk.get_environment();
+                                    temp_sum += env.temp;
+                                    humid_sum += (env.humid * (1.0 + env.near_water)) * height_p;
                                 }
                             }
                         }
                         Constants {
-                            altitude: altitude_r,
-                            water: water_r,
-                            temperature_day: temperature_day_r,
-                            temperature_night: temperature_day_r,
+                            alt: alt_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
+                            humid: humid_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
+                            temp: temp_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
+                            normal: sample_plane_normal(&points).unwrap(),
                         }
                     })
                     .collect_vec(),
             ),
             weather: Grid::new(size, Weather::default()),
+            info: Grid::new(size, WeatherInfo::default()),
         };
         this.cells.iter_mut().for_each(|(point, cell)| {
             let time = 0.0;
@@ -179,17 +146,13 @@ impl WeatherSim {
 
     pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
 
-    pub fn get_weather_at(&self, chunk: Vec2<i32>) -> Option<&Weather> {
-        self.weather.get(chunk / CHUNKS_PER_CELL as i32)
-    }
-
     fn get_cell(&self, p: Vec2<i32>, time: f64) -> Cell {
         *self.cells.get(p).unwrap_or(&sample_cell(p, time))
     }
 
     // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
     // Time step is cell size / maximum wind speed
-    pub fn tick(&mut self, time_of_day: &TimeOfDay, dt: f32) {
+    pub fn tick(&mut self, time_of_day: &TimeOfDay) {
         let time = time_of_day.0;
         let mut swap = Grid::new(self.cells.size(), Cell::default());
         // Dissipate wind, humidty and pressure
@@ -241,14 +204,14 @@ impl WeatherSim {
                         match p {
                             0 => 1.0 * 1.0 / area,   // area_0 / area
                             1 => rate * 1.0 / area,  // area_1 / area
-                            _ => rate * rate / area, // area_2/ area
+                            _ => rate * rate / area, // area_2 / area
                         }
                     };
                     //  0.0 <= dissipation rate <= 1.0 because we only spread to direct neighbours.
-                    cell.wind += c.wind * factor(0.0055);
-                    cell.temperature += c.temperature * factor(0.007);
-                    cell.moisture += c.moisture * factor(0.003);
-                    cell.cloud += c.cloud * factor(0.001);
+                    cell.wind += c.wind * factor(0.009);
+                    cell.temperature += c.temperature * factor(0.01);
+                    cell.moisture += c.moisture * factor(0.008);
+                    cell.cloud += c.cloud * factor(0.005);
                 }
                 cell
             }
@@ -297,37 +260,73 @@ impl WeatherSim {
                 }
             }
         }
-        self.cells = swap.clone();
-
-        // TODO: wind curl
 
         // Evaporate moisture and condense clouds
-        for (point, cell) in self.cells.iter_mut() {
+        for (point, cell) in swap.iter() {
             let dt = 1.0 - 0.96f32.powf(DT);
-            let day_light = (1.0 - (1.0 - time_of_day.0 as f32 / 12.0 * 60.0 * 60.0).abs())
-                * self.weather[point].cloud;
-
-            cell.temperature = cell.temperature * (1.0 - dt)
-                + dt * (self.constants[point].temperature_day * day_light
-                    + self.constants[point].temperature_night * (1.0 - day_light));
-
-            // Evaporate from ground.
-            let temp_part = (cell.temperature / WATER_BOILING_POINT)
-                //.powf(2.0)
+            let day_light = ((2.0
+                * ((time_of_day.0 as f32 / (24.0 * 60.0 * 60.0)) % 1.0 - 0.5).abs())
+                * (1.0 - self.weather[point].cloud / CLOUD_MAX))
                 .clamp(0.0, 1.0);
-            cell.moisture += (self.constants[point].water / 10.0) * temp_part * DT;
+
+            self.cells[point].temperature =
+                cell.temperature * (1.0 - dt) + dt * (self.consts[point].temp + 0.1 * day_light);
+
+            let temp_part = ((cell.temperature + WATER_BOILING_POINT)
+                / (WATER_BOILING_POINT * 2.0))
+                .powf(4.0)
+                .clamp(0.0, 1.0);
+
+            // Drag wind based on pressure difference.
+            // note: pressure scales linearly with temperature in this simulation
+            self.cells[point].wind = cell.wind
+                + [
+                    Vec2::new(1, 0),
+                    Vec2::new(1, 1),
+                    Vec2::new(0, 1),
+                    Vec2::new(-1, 0),
+                    Vec2::new(-1, -1),
+                    Vec2::new(0, -1),
+                    Vec2::new(1, -1),
+                    Vec2::new(-1, 1),
+                ]
+                .iter()
+                .filter(|&&p| swap.get(p).is_some())
+                .map(|&p| {
+                    let diff =
+                        (swap.get(p).unwrap().temperature - cell.temperature) / WATER_BOILING_POINT;
+                    p.as_().normalized() * diff * DT
+                })
+                .sum::<Vec2<f32>>();
+
+            // Curve wind based on topography
+            if let Some(xy) = if self.consts[point].normal.z < 1.0 {
+                Some(self.consts[point].normal.xy().normalized())
+            } else {
+                None
+            } {
+                if self.cells[point].wind.dot(xy) > 0.0 {
+                    const WIND_CHECK_START: f32 = 500.0;
+                    const WIND_CHECK_STOP: f32 = 3000.0;
+                    let alt_m = (self.consts[point].alt - WIND_CHECK_START)
+                        / (WIND_CHECK_STOP - WIND_CHECK_START);
+                    let reflected = self.cells[point].wind.reflected(xy) * (1.0 - 0.9f32.powf(DT));
+                    if reflected.x.is_nan() || reflected.y.is_nan() {
+                        panic!("ref is nan");
+                    }
+                    if self.cells[point].wind.x.is_nan() || self.cells[point].wind.y.is_nan() {
+                        panic!("wind is nan");
+                    }
+                    let drag = (1.0 - alt_m) * self.consts[point].normal.z;
+                    self.cells[point].wind = self.cells[point].wind * drag
+                        + reflected * alt_m * (1.0 - self.consts[point].normal.z);
+                }
+            }
 
             // If positive condense moisture to clouds, if negative evaporate clouds into
             // moisture.
-            let condensation = if cell.moisture > 1.0 {
-                cell.moisture.powf(2.0 / 3.0)
-            } else {
-                cell.moisture
-            } * temp_part
-                * DT;
-
-            cell.moisture -= condensation;
-            cell.cloud += condensation;
+            let condensation = cell.moisture * (1.0 - 0.99f32.powf((1.0 - temp_part) * DT))
+                - cell.cloud * (1.0 - 0.98f32.powf(temp_part * DT)) / CLOUD_MAX;
 
             const CLOUD_MAX: f32 = 15.0;
             const RAIN_P: f32 = 0.96;
@@ -335,12 +334,20 @@ impl WeatherSim {
 
             let rain_p_t = ((cell.cloud - rain_cloud) / (CLOUD_MAX - rain_cloud)).max(0.0);
             let rain = rain_p_t * (1.0 - RAIN_P.powf(DT));
-            cell.cloud -= rain;
-            cell.cloud = cell.cloud.max(0.0);
+
+            // Evaporate from ground.
+            self.cells[point].moisture =
+                cell.moisture + (self.consts[point].humid / 100.0) * temp_part * DT - condensation;
+
+            self.cells[point].cloud = (cell.cloud + condensation - rain).max(0.0);
 
             self.weather[point].cloud = (cell.cloud / CLOUD_MAX).clamp(0.0, 1.0);
             self.weather[point].rain = rain_p_t;
             self.weather[point].wind = cell.wind;
+
+            self.info[point].lightning_chance = self.weather[point].cloud.powf(2.0)
+                * (self.weather[point].rain * 0.9 + 0.1)
+                * temp_part;
         }
 
         // Maybe moisture condenses to clouds, which if they have a certain
diff --git a/server/src/weather/sync.rs b/server/src/weather/sync.rs
index f98080b30e..8114c33cfd 100644
--- a/server/src/weather/sync.rs
+++ b/server/src/weather/sync.rs
@@ -20,7 +20,7 @@ impl<'a> System<'a> for Sys {
     const ORIGIN: Origin = Origin::Server;
     const PHASE: Phase = Phase::Create;
 
-    fn run(job: &mut common_ecs::Job<Self>, (sim, mut scheduler, clients): Self::SystemData) {
+    fn run(_job: &mut common_ecs::Job<Self>, (sim, mut scheduler, clients): Self::SystemData) {
         if scheduler.should_run() {
             let mut lazy_msg = None;
             for client in clients.join() {
diff --git a/server/src/weather/tick.rs b/server/src/weather/tick.rs
index 2f6f0033da..06285af235 100644
--- a/server/src/weather/tick.rs
+++ b/server/src/weather/tick.rs
@@ -1,8 +1,6 @@
-use std::sync::Arc;
-
 use common::resources::TimeOfDay;
 use common_ecs::{Origin, Phase, System};
-use specs::{Read, ReadExpect, Write, WriteExpect};
+use specs::{Read, Write, WriteExpect};
 
 use crate::sys::SysScheduler;
 
@@ -22,9 +20,12 @@ impl<'a> System<'a> for Sys {
     const ORIGIN: Origin = Origin::Server;
     const PHASE: Phase = Phase::Create;
 
-    fn run(job: &mut common_ecs::Job<Self>, (game_time, mut sim, mut scheduler): Self::SystemData) {
+    fn run(
+        _job: &mut common_ecs::Job<Self>,
+        (game_time, mut sim, mut scheduler): Self::SystemData,
+    ) {
         if scheduler.should_run() {
-            sim.tick(&*game_time, 1.0);
+            sim.tick(&*game_time);
         }
     }
 }
diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs
index a884c29c66..55c2c8e914 100644
--- a/voxygen/src/audio/music.rs
+++ b/voxygen/src/audio/music.rs
@@ -476,7 +476,7 @@ impl assets::Compound for SoundtrackCollection<SoundtrackItem> {
                                 path,
                                 length,
                                 timing: timing.clone(),
-                                weather: weather.clone(),
+                                weather,
                                 biomes: biomes.clone(),
                                 site,
                                 music_state,

From 67683f315f28fc298356727f8bcad26c062b2404 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 20 Feb 2022 17:05:25 +0100
Subject: [PATCH 09/71] Put rain behind expiremental shader

---
 assets/voxygen/shaders/clouds-frag.glsl      | 72 ++++++++++----------
 assets/voxygen/shaders/fluid-frag/shiny.glsl | 40 +++++------
 assets/voxygen/shaders/terrain-frag.glsl     | 54 ++++++++-------
 voxygen/src/render/mod.rs                    |  2 +
 4 files changed, 88 insertions(+), 80 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 67869492f3..c6aada235d 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -99,49 +99,51 @@ void main() {
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
     #endif
 
-    vec3 old_color = color.rgb;
+    #ifdef EXPERIMENTAL_RAIN
+        vec3 old_color = color.rgb;
 
-    float fall_rate = 20.0;
+        float fall_rate = 20.0;
 
-    dir.xy += wind_vel * dir.z / fall_rate;
-    dir = normalize(dir);
+        dir.xy += wind_vel * dir.z / fall_rate;
+        dir = normalize(dir);
 
-    float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
-    vec2 dir_2d = normalize(dir.xy);
-    vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
+        float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+        vec2 dir_2d = normalize(dir.xy);
+        vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
-    vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-    float rain_density = rain_density_at(cam_wpos.xy);
-    if (rain_density > 0) {
-        float rain_dist = 50.0;
-        for (int i = 0; i < 5; i ++) {
-            rain_dist *= 0.3;
+        vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+        float rain_density = rain_density_at(cam_wpos.xy);
+        if (rain_density > 0) {
+            float rain_dist = 50.0;
+            for (int i = 0; i < 5; i ++) {
+                rain_dist *= 0.3;
 
-            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
-            float dist_to_rain = length(rpos);
+                vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
+                float dist_to_rain = length(rpos);
 
-            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
-                continue;
+                if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+                    continue;
+                }
+
+                float drop_density = 3;
+                vec2 drop_size = vec2(0.0025, 0.17);
+
+                vec2 rain_pos = (view_pos * rain_dist);
+                rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
+
+                vec2 cell = floor(rain_pos * drop_density) / drop_density;
+                if (hash(fract(vec4(cell, rain_dist, 0) * 0.01)) > rain_density) {
+                    continue;
+                }
+                vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+                float avg_alpha = (drop_size.x * drop_size.y) / 1;
+                float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size), 0));
+                float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+                color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
             }
-
-            float drop_density = 3;
-            vec2 drop_size = vec2(0.0025, 0.17);
-
-            vec2 rain_pos = (view_pos * rain_dist);
-            rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
-
-            vec2 cell = floor(rain_pos * drop_density) / drop_density;
-            if (hash(fract(vec4(cell, rain_dist, 0) * 0.01)) > rain_density) {
-                continue;
-            }
-            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
-
-            float avg_alpha = (drop_size.x * drop_size.y) / 1;
-            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size), 0));
-            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
-            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
         }
-    }
+    #endif
 
     tgt_color = vec4(color.rgb, 1);
 }
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index 3d13a7cef8..fa5d485f85 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -149,28 +149,30 @@ void main() {
         wave_sample_dist / slope
     );
 
-    float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
-    if (rain_density > 0 && surf_norm.z > 0.5) {
-        vec3 drop_density = vec3(2, 2, 1);
-        vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
-        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
-        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+    #ifdef EXPERIMENTAL_RAIN
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+        if (rain_density > 0 && surf_norm.z > 0.5) {
+            vec3 drop_density = vec3(2, 2, 1);
+            vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
+            drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-        if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
-            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+            if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-            float drop_rad = 0.125;
-            nmap.xy += (drop_pos - near_cell).xy
-                * max(1.0 - abs(dist - drop_rad) * 50, 0)
-                * 2500
-                * sign(dist - drop_rad)
-                * max(drop_pos.z - near_cell.z, 0);
+                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+                float drop_rad = 0.125;
+                nmap.xy += (drop_pos - near_cell).xy
+                    * max(1.0 - abs(dist - drop_rad) * 50, 0)
+                    * 2500
+                    * sign(dist - drop_rad)
+                    * max(drop_pos.z - near_cell.z, 0);
+            }
         }
-    }
+    #endif
 
     nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1));
 
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index 77a83f81db..3a9afddc9e 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -230,35 +230,37 @@ void main() {
     vec3 k_a = vec3(1.0);
     vec3 k_d = vec3(1.0);
     vec3 k_s = vec3(R_s);
+    
+    #ifdef EXPERIMENTAL_RAIN
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+        if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
+            vec3 pos = f_pos + focus_off.xyz;
+            vec3 drop_density = vec3(2, 2, 1);
+            vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
+            drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-    float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
-    if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
-        vec3 pos = f_pos + focus_off.xyz;
-        vec3 drop_density = vec3(2, 2, 1);
-        vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
-        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
-        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+            if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-        if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
-            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
-
-            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-            float drop_rad = 0.1;
-            float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
-            k_a += distort;
-            k_d += distort;
-            k_s += distort;
-            f_norm.xy += (drop_pos - near_cell).xy
-                * max(1.0 - abs(dist - drop_rad) * 30, 0)
-                * 500.0
-                * max(drop_pos.z - near_cell.z, 0)
-                * sign(dist - drop_rad)
-                * max(drop_pos.z - near_cell.z, 0);
+                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+                float drop_rad = 0.1;
+                float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
+                k_a += distort;
+                k_d += distort;
+                k_s += distort;
+                f_norm.xy += (drop_pos - near_cell).xy
+                    * max(1.0 - abs(dist - drop_rad) * 30, 0)
+                    * 500.0
+                    * max(drop_pos.z - near_cell.z, 0)
+                    * sign(dist - drop_rad)
+                    * max(drop_pos.z - near_cell.z, 0);
+            }
         }
-    }
+    #endif
 
     // float sun_light = get_sun_brightness(sun_dir);
     // float moon_light = get_moon_brightness(moon_dir);
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index 9b50e4d4e8..b0a30b8b0b 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -467,4 +467,6 @@ pub enum ExperimentalShader {
     /// Display grid lines to visualize the distribution of shadow map texels
     /// for the directional light from the sun.
     DirectionalShadowMapTexelGrid,
+    // Enable rain, unfinished and goes through blocks
+    Rain,
 }

From 273c5ed2f084acc7de077f6e79eb65c47ba90cac Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 20 Feb 2022 18:23:37 +0100
Subject: [PATCH 10/71] Temporary noise solution

---
 Cargo.lock                                    |  1 +
 assets/voxygen/shaders/clouds-frag.glsl       |  4 +-
 .../shaders/include/cloud/regular.glsl        | 68 ++++++++++++-------
 server/Cargo.toml                             |  1 +
 server/src/weather/sim.rs                     | 46 +++++++++++--
 voxygen/src/render/pipelines/lod_terrain.rs   |  6 +-
 world/examples/turb.rs                        | 30 +++++---
 7 files changed, 108 insertions(+), 48 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e3b3ec9978..52dfcad198 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6635,6 +6635,7 @@ dependencies = [
  "humantime",
  "itertools",
  "lazy_static",
+ "noise",
  "num_cpus",
  "portpicker",
  "prometheus",
diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index c6aada235d..69ca56e5c3 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -102,7 +102,7 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         vec3 old_color = color.rgb;
 
-        float fall_rate = 20.0;
+        float fall_rate = 40.0;
 
         dir.xy += wind_vel * dir.z / fall_rate;
         dir = normalize(dir);
@@ -126,7 +126,7 @@ void main() {
                 }
 
                 float drop_density = 3;
-                vec2 drop_size = vec2(0.0025, 0.17);
+                vec2 drop_size = vec2(0.0015, 0.17);
 
                 vec2 rain_pos = (view_pos * rain_dist);
                 rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 9fb365fb23..0430878a42 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -61,13 +61,16 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
         ;
     }
 
+    float cloud_alt = CLOUD_AVG_ALT + alt * 0.5;
+
     //vec2 cloud_attr = get_cloud_heights(wind_pos.xy);
     float sun_access = 0.0;
     float moon_access = 0.0;
-    float cloud_sun_access = 0.0;
+    float cloud_sun_access = clamp((pos.z - cloud_alt) / 1500 + 0.5, 0, 1);
     float cloud_moon_access = 0.0;
     float cloud_broad_a = 0.0;
     float cloud_broad_b = 0.0;
+
     // This is a silly optimisation but it actually nets us a fair few fps by skipping quite a few expensive calcs
     if ((pos.z < CLOUD_AVG_ALT + 15000.0 && cloud_tendency > 0.0)) {
         // Turbulence (small variations in clouds/mist)
@@ -78,11 +81,10 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
         const float CLOUD_DENSITY = 10000.0;
         const float CLOUD_ALT_VARI_WIDTH = 100000.0;
         const float CLOUD_ALT_VARI_SCALE = 5000.0;
-        float cloud_alt = CLOUD_AVG_ALT + alt * 0.5;
 
         cloud_broad_a = cloud_broad(wind_pos + sun_dir.xyz * 250);
         cloud_broad_b = cloud_broad(wind_pos - sun_dir.xyz * 250);
-        cloud = cloud_tendency + (0.0
+        cloud = cloud_tendency + cloud_tendency * (0.0
             + 24 * (cloud_broad_a + cloud_broad_b) * 0.5
         #if (CLOUD_MODE >= CLOUD_MODE_MINIMAL)
             + 4 * (noise_3d((wind_pos + turb_offset) / 2000.0 / cloud_scale) - 0.5)
@@ -93,24 +95,30 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
         #if (CLOUD_MODE >= CLOUD_MODE_HIGH)
             + 0.75 * (noise_3d(wind_pos / 500.0 / cloud_scale) - 0.5)
         #endif
-        ) * 0.01;
+        ) * 0.1;
         cloud = pow(max(cloud, 0), 3) * sign(cloud);
-        cloud *= CLOUD_DENSITY * sqrt(cloud_tendency) * falloff(abs(pos.z - cloud_alt) / CLOUD_DEPTH);
+        cloud *= CLOUD_DENSITY * sqrt(cloud_tendency + 0.001) * falloff(abs(pos.z - cloud_alt) / CLOUD_DEPTH);
 
         // What proportion of sunlight is *not* being blocked by nearby cloud? (approximation)
         // Basically, just throw together a few values that roughly approximate this term and come up with an average
-        cloud_sun_access = exp((
+        cloud_sun_access = mix(cloud_sun_access, exp((
             // Cloud density gradient
             0.25 * (cloud_broad_a - cloud_broad_b + (0.25 * (noise_3d(wind_pos / 4000 / cloud_scale) - 0.5) + 0.1 * (noise_3d(wind_pos / 1000 / cloud_scale) - 0.5)))
         #if (CLOUD_MODE >= CLOUD_MODE_HIGH)
             // More noise
             + 0.01 * (noise_3d(wind_pos / 500) / cloud_scale - 0.5)
         #endif
-        ) * 15.0 - 1.5) * 1.5;
+        ) * 15.0 - 1.5) * 1.5, min(cloud_tendency * 10, 1));
         // Since we're assuming the sun/moon is always above (not always correct) it's the same for the moon
         cloud_moon_access = 1.0 - cloud_sun_access;
     }
 
+    #if (CLOUD_MODE >= CLOUD_MODE_LOW)
+        cloud += max(noise_3d((wind_pos) / 25000.0 / cloud_scale) - 0.75 + noise_3d((wind_pos) / 2500.0 / cloud_scale) * 0.1, 0)
+            * 0.1
+            / (abs(pos.z - cloud_alt) / 500.0 + 0.1);
+    #endif
+
     // Keeping this because it's something I'm likely to reenable later
     /*
     #if (CLOUD_MODE >= CLOUD_MODE_HIGH)
@@ -238,10 +246,12 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
         float min_dist = clamp(max_dist / 4, 0.25, 24);
         int i;
 
-        // TODO: Make it a double rainbow
-        float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
-        int rainbow_c = int(floor(rainbow_t));
-        rainbow_t = fract(rainbow_t);
+        #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
+            // TODO: Make it a double rainbow
+            float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
+            int rainbow_c = int(floor(rainbow_t));
+            rainbow_t = fract(rainbow_t);
+        #endif
 
         for (i = 0; cdist > min_dist && i < 250; i ++) {
             ldist = cdist;
@@ -276,21 +286,27 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
                 emission * density_integrals.y * step;
 
             // Rainbow
-            if (rainbow_c >= 0 && rainbow_c < 8) {
-                float rain = rain_density_at(pos.xy);
-                vec3 colors[9] = {
-                    surf_color,
-                    vec3(0.9, 0.5, 0.9),
-                    vec3(0.25, 0.0, 0.5),
-                    vec3(0.0, 0.0, 1.0),
-                    vec3(0.0, 0.5, 0.0),
-                    vec3(1.0, 1.0, 0.0),
-                    vec3(1.0, 0.6, 0.0),
-                    vec3(1.0, 0.0, 0.0),
-                    surf_color,
-                };
-                surf_color = mix(surf_color, mix(colors[rainbow_c], colors[rainbow_c + 1], rainbow_t), rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0));
-            }
+            #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
+                if (rainbow_c >= 0 && rainbow_c < 8) {
+                    float rain = rain_density_at(pos.xy);
+                    vec3 colors[9] = {
+                        surf_color,
+                        vec3(0.9, 0.5, 0.9),
+                        vec3(0.25, 0.0, 0.5),
+                        vec3(0.0, 0.0, 1.0),
+                        vec3(0.0, 0.5, 0.0),
+                        vec3(1.0, 1.0, 0.0),
+                        vec3(1.0, 0.6, 0.0),
+                        vec3(1.0, 0.0, 0.0),
+                        surf_color,
+                    };
+                    surf_color = mix(
+                        surf_color,
+                        mix(colors[rainbow_c], colors[rainbow_c + 1], rainbow_t),
+                        rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0),
+                    );
+                }
+            #endif
         }
     #ifdef IS_POSTPROCESS
         }
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 0ccd17c0aa..b24acdf414 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -58,6 +58,7 @@ authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253
 slab  = "0.4"
 rand_distr = "0.4.0"
 enumset = "1.0.8"
+noise = { version = "0.7", default-features = false }
 
 rusqlite = { version = "0.24.2", features = ["array", "vtab", "bundled", "trace"] }
 refinery = { git = "https://gitlab.com/veloren/refinery.git", rev = "8ecf4b4772d791e6c8c0a3f9b66a7530fad1af3e", features = ["rusqlite"] }
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 2fa6951149..8c916371f6 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -6,6 +6,7 @@ use common::{
     weather::{Weather, CHUNKS_PER_CELL},
 };
 use itertools::Itertools;
+use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
 use world::World;
 
@@ -46,10 +47,13 @@ pub struct WeatherSim {
     info: Grid<WeatherInfo>,
 }
 
-const WATER_BOILING_POINT: f32 = 2.5;
+/*
 const MAX_WIND_SPEED: f32 = 128.0;
-pub(crate) const CELL_SIZE: f32 = (CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x) as f32;
-pub(crate) const DT: f32 = CELL_SIZE / MAX_WIND_SPEED;
+*/
+pub(crate) const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
+
+/// How often the weather is updated, in seconds
+pub(crate) const DT: f32 = 5.0; // CELL_SIZE as f32 / MAX_WIND_SPEED;
 
 fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
     if points.len() < 3 {
@@ -88,6 +92,8 @@ fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
     }
 }
 
+fn cell_to_wpos(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 }
+
 impl WeatherSim {
     pub fn new(size: Vec2<u32>, world: &World) -> Self {
         let size = size.as_();
@@ -146,14 +152,44 @@ impl WeatherSim {
 
     pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
 
+    /*
     fn get_cell(&self, p: Vec2<i32>, time: f64) -> Cell {
         *self.cells.get(p).unwrap_or(&sample_cell(p, time))
     }
+    */
 
     // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
     // Time step is cell size / maximum wind speed
     pub fn tick(&mut self, time_of_day: &TimeOfDay) {
         let time = time_of_day.0;
+
+        let base_nz = Turbulence::new(
+            Turbulence::new(SuperSimplex::new())
+                .set_frequency(0.2)
+                .set_power(1.5),
+        )
+        .set_frequency(2.0)
+        .set_power(0.2);
+
+        let rain_nz = SuperSimplex::new();
+
+        for (point, cell) in self.weather.iter_mut() {
+            let wpos = cell_to_wpos(point);
+
+            let space_scale = 7500.0;
+            let time_scale = 25000.0;
+            let spos = (wpos.as_::<f64>() / space_scale).with_z(time as f64 / time_scale);
+
+            let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
+
+            cell.cloud = 1.0 - pressure;
+            cell.rain = (1.0 - pressure - 0.2).max(0.0).powf(1.5);
+            cell.wind = Vec2::new(
+                rain_nz.get(spos.into_array()) as f32,
+                rain_nz.get((spos + 1.0).into_array()) as f32,
+            ) * 250.0 * (1.0 - pressure);
+        }
+        /*
         let mut swap = Grid::new(self.cells.size(), Cell::default());
         // Dissipate wind, humidty and pressure
         // Dissipation is represented by the target cell expanding into the 8 adjacent
@@ -349,8 +385,6 @@ impl WeatherSim {
                 * (self.weather[point].rain * 0.9 + 0.1)
                 * temp_part;
         }
-
-        // Maybe moisture condenses to clouds, which if they have a certain
-        // amount they will release rain.
+        */
     }
 }
diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs
index 7d53baa89a..437ea73f11 100644
--- a/voxygen/src/render/pipelines/lod_terrain.rs
+++ b/voxygen/src/render/pipelines/lod_terrain.rs
@@ -152,9 +152,9 @@ impl LodData {
 
             let sampler_info = wgpu::SamplerDescriptor {
                 label: None,
-                address_mode_u: wgpu::AddressMode::ClampToEdge,
-                address_mode_v: wgpu::AddressMode::ClampToEdge,
-                address_mode_w: wgpu::AddressMode::ClampToEdge,
+                address_mode_u: wgpu::AddressMode::ClampToBorder,
+                address_mode_v: wgpu::AddressMode::ClampToBorder,
+                address_mode_w: wgpu::AddressMode::ClampToBorder,
                 mag_filter: wgpu::FilterMode::Linear,
                 min_filter: wgpu::FilterMode::Linear,
                 mipmap_filter: wgpu::FilterMode::Nearest,
diff --git a/world/examples/turb.rs b/world/examples/turb.rs
index 1eae349bc9..fe3d8924c5 100644
--- a/world/examples/turb.rs
+++ b/world/examples/turb.rs
@@ -1,4 +1,4 @@
-use noise::{Seedable, SuperSimplex};
+use noise::{NoiseFn, Seedable, SuperSimplex, Turbulence};
 
 use vek::*;
 
@@ -8,32 +8,40 @@ const H: usize = 640;
 fn main() {
     let mut win = minifb::Window::new("Turb", W, H, minifb::WindowOptions::default()).unwrap();
 
+    let nz = Turbulence::new(
+        Turbulence::new(SuperSimplex::new())
+            .set_frequency(0.2)
+            .set_power(1.5),
+    )
+    .set_frequency(2.0)
+    .set_power(0.2);
+
     let _nz_x = SuperSimplex::new().set_seed(0);
     let _nz_y = SuperSimplex::new().set_seed(1);
 
     let mut _time = 0.0f64;
 
+    let mut scale = 50.0;
+
     while win.is_open() {
         let mut buf = vec![0; W * H];
 
         for i in 0..W {
             for j in 0..H {
-                let pos = Vec2::new(i as f64 / W as f64, j as f64 / H as f64) * 0.5 - 0.25;
+                let pos = Vec2::new(i as f64, j as f64) / scale;
 
-                let pos = pos * 10.0;
-
-                let pos = (0..10).fold(pos, |pos, _| pos.map(|e| e.powi(3) - 1.0));
-
-                let val = if pos.map(|e| e.abs() < 0.5).reduce_and() {
-                    1.0f32
-                } else {
-                    0.0
-                };
+                let val = nz.get(pos.into_array());
 
                 buf[j * W + i] = u32::from_le_bytes([(val.max(0.0).min(1.0) * 255.0) as u8; 4]);
             }
         }
 
+        if win.is_key_pressed(minifb::Key::Right, minifb::KeyRepeat::No) {
+            scale *= 1.5;
+        } else if win.is_key_pressed(minifb::Key::Left, minifb::KeyRepeat::No) {
+            scale /= 1.5;
+        }
+
         win.update_with_buffer(&buf, W, H).unwrap();
 
         _time += 1.0 / 60.0;

From 4181331e6b2bc032740e193d3b65b5fcfe5f7c00 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Sun, 13 Mar 2022 16:35:52 +0000
Subject: [PATCH 11/71] Balanced rain density

---
 assets/voxygen/shaders/clouds-frag.glsl      | 4 ++--
 assets/voxygen/shaders/fluid-frag/shiny.glsl | 4 ++--
 assets/voxygen/shaders/terrain-frag.glsl     | 6 +++---
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 69ca56e5c3..655249cc3f 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -112,7 +112,7 @@ void main() {
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-        float rain_density = rain_density_at(cam_wpos.xy);
+        float rain_density = rain_density_at(cam_wpos.xy) * 100.0;
         if (rain_density > 0) {
             float rain_dist = 50.0;
             for (int i = 0; i < 5; i ++) {
@@ -132,7 +132,7 @@ void main() {
                 rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
 
                 vec2 cell = floor(rain_pos * drop_density) / drop_density;
-                if (hash(fract(vec4(cell, rain_dist, 0) * 0.01)) > rain_density) {
+                if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                     continue;
                 }
                 vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index fa5d485f85..bb57834e44 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -150,7 +150,7 @@ void main() {
     );
 
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
         if (rain_density > 0 && surf_norm.z > 0.5) {
             vec3 drop_density = vec3(2, 2, 1);
             vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
@@ -159,7 +159,7 @@ void main() {
             drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
             vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-            if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
                 vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
                 vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index 3a9afddc9e..8772554671 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -230,9 +230,9 @@ void main() {
     vec3 k_a = vec3(1.0);
     vec3 k_d = vec3(1.0);
     vec3 k_s = vec3(R_s);
-    
+
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy);
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
         if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
             vec3 pos = f_pos + focus_off.xyz;
             vec3 drop_density = vec3(2, 2, 1);
@@ -242,7 +242,7 @@ void main() {
             drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
             vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-            if (hash(fract(vec4(cell, 0) * 0.01)) < rain_density) {
+            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
                 vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
                 vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 

From dc90d637c10a61aae19031003f948776c7905bc6 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Sun, 13 Mar 2022 16:44:13 +0000
Subject: [PATCH 12/71] Smoother clouds

---
 assets/voxygen/shaders/include/cloud/regular.glsl | 4 ++--
 server/src/weather/sim.rs                         | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 0430878a42..4545a81f91 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -116,7 +116,7 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
     #if (CLOUD_MODE >= CLOUD_MODE_LOW)
         cloud += max(noise_3d((wind_pos) / 25000.0 / cloud_scale) - 0.75 + noise_3d((wind_pos) / 2500.0 / cloud_scale) * 0.1, 0)
             * 0.1
-            / (abs(pos.z - cloud_alt) / 500.0 + 0.1);
+            / (abs(pos.z - cloud_alt) / 500.0 + 0.2);
     #endif
 
     // Keeping this because it's something I'm likely to reenable later
@@ -303,7 +303,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
                     surf_color = mix(
                         surf_color,
                         mix(colors[rainbow_c], colors[rainbow_c + 1], rainbow_t),
-                        rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0),
+                        rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0)
                     );
                 }
             #endif
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 8c916371f6..fbc00e0457 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -182,7 +182,7 @@ impl WeatherSim {
 
             let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
 
-            cell.cloud = 1.0 - pressure;
+            cell.cloud = (1.0 - pressure) * 0.5;
             cell.rain = (1.0 - pressure - 0.2).max(0.0).powf(1.5);
             cell.wind = Vec2::new(
                 rain_nz.get(spos.into_array()) as f32,

From 502145a6f949bdc971b5adbd93c7bfe07454c6a5 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Sun, 13 Mar 2022 20:40:33 +0000
Subject: [PATCH 13/71] Cloud movement is more prominent

---
 assets/voxygen/shaders/clouds-frag.glsl      |  4 ++--
 assets/voxygen/shaders/fluid-frag/shiny.glsl |  2 +-
 assets/voxygen/shaders/terrain-frag.glsl     |  2 +-
 server/src/weather/sim.rs                    | 11 +++++++----
 4 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 655249cc3f..eab938f7b5 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -112,7 +112,7 @@ void main() {
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-        float rain_density = rain_density_at(cam_wpos.xy) * 100.0;
+        float rain_density = rain_density_at(cam_wpos.xy) * 10.0;
         if (rain_density > 0) {
             float rain_dist = 50.0;
             for (int i = 0; i < 5; i ++) {
@@ -125,7 +125,7 @@ void main() {
                     continue;
                 }
 
-                float drop_density = 3;
+                vec2 drop_density = vec2(30, 3);
                 vec2 drop_size = vec2(0.0015, 0.17);
 
                 vec2 rain_pos = (view_pos * rain_dist);
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index bb57834e44..e01e2982bf 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -152,7 +152,7 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
         if (rain_density > 0 && surf_norm.z > 0.5) {
-            vec3 drop_density = vec3(2, 2, 1);
+            vec3 drop_density = vec3(2, 2, 2);
             vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
             drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
             vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index 8772554671..168883728f 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -235,7 +235,7 @@ void main() {
         float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
         if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
             vec3 pos = f_pos + focus_off.xyz;
-            vec3 drop_density = vec3(2, 2, 1);
+            vec3 drop_density = vec3(2, 2, 2);
             vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
             drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
             vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index fbc00e0457..671da19498 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -176,9 +176,11 @@ impl WeatherSim {
         for (point, cell) in self.weather.iter_mut() {
             let wpos = cell_to_wpos(point);
 
-            let space_scale = 7500.0;
-            let time_scale = 25000.0;
-            let spos = (wpos.as_::<f64>() / space_scale).with_z(time as f64 / time_scale);
+            let pos = wpos.as_::<f64>() + time as f64 * 0.25;
+
+            let space_scale = 7_500.0;
+            let time_scale = 100_000.0;
+            let spos = (pos / space_scale).with_z(time as f64 / time_scale);
 
             let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
 
@@ -187,7 +189,8 @@ impl WeatherSim {
             cell.wind = Vec2::new(
                 rain_nz.get(spos.into_array()) as f32,
                 rain_nz.get((spos + 1.0).into_array()) as f32,
-            ) * 250.0 * (1.0 - pressure);
+            ) * 250.0
+                * (1.0 - pressure);
         }
         /*
         let mut swap = Grid::new(self.cells.size(), Cell::default());

From 99c76be29729f2afc765e363bc3285742c9e5414 Mon Sep 17 00:00:00 2001
From: "Benjam Soule H. Walker" <bennyjhall300@gmail.com>
Date: Sat, 20 Nov 2021 01:46:14 +0000
Subject: [PATCH 14/71] added sound for rain

---
 assets/voxygen/audio/ambient.ron              |   8 +-
 assets/voxygen/audio/ambient/rain.ogg         |   3 +
 assets/voxygen/audio/sfx/ambient/rain_sfx.ogg |   3 +
 voxygen/src/audio/ambient.rs                  | 156 +++++++++++++++---
 voxygen/src/audio/channel.rs                  |   5 +-
 voxygen/src/audio/mod.rs                      |  60 ++++---
 voxygen/src/scene/mod.rs                      |  17 +-
 7 files changed, 195 insertions(+), 57 deletions(-)
 create mode 100644 assets/voxygen/audio/ambient/rain.ogg
 create mode 100644 assets/voxygen/audio/sfx/ambient/rain_sfx.ogg

diff --git a/assets/voxygen/audio/ambient.ron b/assets/voxygen/audio/ambient.ron
index ddc2af2fbb..db088bb86c 100644
--- a/assets/voxygen/audio/ambient.ron
+++ b/assets/voxygen/audio/ambient.ron
@@ -2,8 +2,12 @@
     tracks: [
         (
             path: "voxygen.audio.ambient.wind",
-            length: 14.2,
+            length: 14.203,
             tag: Wind,
-        ),        
+        ),     (
+            path: "voxygen.audio.ambient.rain",
+            length: 17.0,
+            tag: Rain,
+        ),           
     ]
 )
diff --git a/assets/voxygen/audio/ambient/rain.ogg b/assets/voxygen/audio/ambient/rain.ogg
new file mode 100644
index 0000000000..f8d3570d79
--- /dev/null
+++ b/assets/voxygen/audio/ambient/rain.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b8a6062ad150d11cd40439f3e87881448069092b9f26a1ef3a60cb710a5b5320
+size 328599
diff --git a/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg b/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg
new file mode 100644
index 0000000000..61d4b62a32
--- /dev/null
+++ b/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:add12cd225a7e4a40a7b251c4586bdb80ec2acdbc8de40f91a5f0e3395179aa2
+size 564035
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 1c3941216b..d0c89e8c0d 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -29,18 +29,26 @@ pub struct AmbientItem {
     tag: AmbientChannelTag,
 }
 
-pub struct AmbientMgr {
-    soundtrack: AssetHandle<AmbientCollection>,
+pub struct AmbientWindMgr {
+    ambience: AssetHandle<AmbientCollection>,
     began_playing: Instant,
     next_track_change: f32,
     volume: f32,
     tree_multiplier: f32,
 }
 
-impl Default for AmbientMgr {
+pub struct AmbientRainMgr {
+    ambience: AssetHandle<AmbientCollection>,
+    began_playing: Instant,
+    next_track_change: f32,
+    volume: f32,
+    rain_intensity: f32,
+}
+
+impl Default for AmbientWindMgr {
     fn default() -> Self {
         Self {
-            soundtrack: Self::load_soundtrack_items(),
+            ambience: load_ambience_items(),
             began_playing: Instant::now(),
             next_track_change: 0.0,
             volume: 0.0,
@@ -49,7 +57,19 @@ impl Default for AmbientMgr {
     }
 }
 
-impl AmbientMgr {
+impl Default for AmbientRainMgr {
+    fn default() -> Self {
+        Self {
+            ambience: load_ambience_items(),
+            began_playing: Instant::now(),
+            next_track_change: 0.0,
+            volume: 0.0,
+            rain_intensity: 0.0,
+        }
+    }
+}
+
+impl AmbientWindMgr {
     /// Checks whether the previous track has completed. If so, sends a
     /// request to play the next (random) track
     pub fn maintain(
@@ -59,7 +79,7 @@ impl AmbientMgr {
         client: &Client,
         camera: &Camera,
     ) {
-        if audio.sfx_enabled() && !self.soundtrack.read().tracks.is_empty() {
+        if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() {
             let focus_off = camera.get_focus_pos().map(f32::trunc);
             let cam_pos = camera.dependents().cam_pos + focus_off;
 
@@ -95,6 +115,7 @@ impl AmbientMgr {
                 {
                     volume_multiplier *= 0.1;
                 }
+                // Is the camera roughly under the terrain?
                 if cam_pos.z < terrain_alt - 10.0 {
                     volume_multiplier = 0.0;
                 }
@@ -103,40 +124,121 @@ impl AmbientMgr {
             };
 
             // Transitions the ambient sounds (more) smoothly
-            self.volume = audio.get_ambient_volume();
-            audio.set_ambient_volume(Lerp::lerp(self.volume, target_volume, 0.01));
+            if audio.get_ambient_channel(AmbientChannelTag::Wind).is_none() {
+                audio.new_ambient_channel(AmbientChannelTag::Wind);
+            } else {
+                self.volume = audio.get_ambient_volume(AmbientChannelTag::Wind);
+                audio.set_ambient_volume(
+                    AmbientChannelTag::Wind,
+                    Lerp::lerp(self.volume, target_volume, 0.01),
+                );
+            }
 
             if self.began_playing.elapsed().as_secs_f32() > self.next_track_change {
-                // Right now there is only wind non-positional sfx so it is always
-                // selected. Modify this variable assignment when adding other non-
-                // positional sfx
-                let soundtrack = self.soundtrack.read();
-                let track = &soundtrack
+                let ambience = self.ambience.read();
+                let wind_track = &ambience
                     .tracks
                     .iter()
                     .find(|track| track.tag == AmbientChannelTag::Wind);
-
-                if let Some(track) = track {
-                    self.began_playing = Instant::now();
-                    self.next_track_change = track.length;
-
-                    audio.play_ambient(AmbientChannelTag::Wind, &track.path, target_volume);
+                self.began_playing = Instant::now();
+                if let Some(wind_track) = wind_track {
+                    self.next_track_change = wind_track.length;
+                    audio.play_ambient(AmbientChannelTag::Wind, &wind_track.path, target_volume);
                 }
             }
         }
     }
+}
 
-    fn load_soundtrack_items() -> AssetHandle<AmbientCollection> {
-        AmbientCollection::load_or_insert_with("voxygen.audio.ambient", |error| {
-            warn!(
-                "Error reading ambience config file, ambience will not be available: {:#?}",
-                error
-            );
-            AmbientCollection::default()
-        })
+impl AmbientRainMgr {
+    /// Checks whether the previous track has completed. If so, sends a
+    /// request to play the next (random) track
+    pub fn maintain(
+        &mut self,
+        audio: &mut AudioFrontend,
+        state: &State,
+        client: &Client,
+        camera: &Camera,
+    ) {
+        if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() {
+            let focus_off = camera.get_focus_pos().map(f32::trunc);
+            let cam_pos = camera.dependents().cam_pos + focus_off;
+
+            let terrain_alt = if let Some(chunk) = client.current_chunk() {
+                chunk.meta().alt()
+            } else {
+                0.0
+            };
+
+            // multipler at end will have to change depending on how intense rain normally
+            // is
+            self.rain_intensity = client.current_weather().rain * 5.0;
+
+            let mut volume_multiplier = self.rain_intensity;
+
+            // TODO: make rain diminish with distance above terrain
+            let target_volume = {
+                // Checks if the camera is underwater to stop ambient sounds
+                if state
+                    .terrain()
+                    .get((cam_pos).map(|e| e.floor() as i32))
+                    .map(|b| b.is_liquid())
+                    .unwrap_or(false)
+                {
+                    volume_multiplier *= 0.1;
+                }
+                // Is the camera roughly under the terrain?
+                if cam_pos.z < terrain_alt - 10.0 {
+                    volume_multiplier = 0.0;
+                }
+
+                volume_multiplier = volume_multiplier.clamped(0.0, 1.0);
+
+                // possibly remove noise
+                if volume_multiplier < 0.05 {
+                    0.0
+                } else {
+                    volume_multiplier
+                }
+            };
+
+            // Transitions the ambient sounds (more) smoothly
+            if audio.get_ambient_channel(AmbientChannelTag::Rain).is_none() {
+                audio.new_ambient_channel(AmbientChannelTag::Rain);
+            } else {
+                self.volume = audio.get_ambient_volume(AmbientChannelTag::Rain);
+                audio.set_ambient_volume(
+                    AmbientChannelTag::Rain,
+                    Lerp::lerp(self.volume, target_volume, 0.01),
+                );
+            }
+
+            if self.began_playing.elapsed().as_secs_f32() > self.next_track_change {
+                let ambience = self.ambience.read();
+                let rain_track = &ambience
+                    .tracks
+                    .iter()
+                    .find(|track| track.tag == AmbientChannelTag::Rain);
+                self.began_playing = Instant::now();
+                if let Some(rain_track) = rain_track {
+                    self.next_track_change = rain_track.length;
+                    audio.play_ambient(AmbientChannelTag::Rain, &rain_track.path, target_volume);
+                }
+            }
+        }
     }
 }
 
+fn load_ambience_items() -> AssetHandle<AmbientCollection> {
+    AmbientCollection::load_or_insert_with("voxygen.audio.ambient", |error| {
+        warn!(
+            "Error reading ambience config file, ambience will not be available: {:#?}",
+            error
+        );
+        AmbientCollection::default()
+    })
+}
+
 impl assets::Asset for AmbientCollection {
     type Loader = assets::RonLoader;
 
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index 91c53d6b63..5995a36a89 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -160,6 +160,7 @@ impl MusicChannel {
 #[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
 pub enum AmbientChannelTag {
     Wind,
+    Rain,
 }
 /// A AmbientChannel uses a non-positional audio sink designed to play sounds
 /// which are always heard at the camera's position.
@@ -179,7 +180,7 @@ impl AmbientChannel {
                 sink,
             },
             Err(_) => {
-                warn!("Failed to create rodio sink. May not play wind sounds.");
+                warn!("Failed to create rodio sink. May not play ambient sounds.");
                 Self {
                     tag,
                     multiplier,
@@ -207,6 +208,8 @@ impl AmbientChannel {
 
     pub fn get_volume(&mut self) -> f32 { self.sink.volume() }
 
+    pub fn get_multiplier(&mut self) -> f32 { self.multiplier }
+
     pub fn get_tag(&self) -> AmbientChannelTag { self.tag }
 }
 
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 005cb91683..bb84132fa5 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -287,6 +287,7 @@ impl AudioFrontend {
         Ok(())
     }
 
+    // plays a file at a given volume in the channel with a given tag
     fn play_ambient(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -294,52 +295,63 @@ impl AudioFrontend {
         volume_multiplier: f32,
     ) {
         if self.audio_stream.is_some() {
-            if let Some(channel) = self.get_ambient_channel(channel_tag, volume_multiplier) {
+            if let Some(channel) = self.get_ambient_channel(channel_tag) {
+                channel.set_volume(volume_multiplier);
                 channel.play(load_ogg(sound));
             }
         }
     }
 
+    // adds a new ambient channel of the given tag at zero volume
+    fn new_ambient_channel(&mut self, channel_tag: AmbientChannelTag) {
+        if let Some(audio_stream) = &self.audio_stream {
+            let ambient_channel = AmbientChannel::new(audio_stream, channel_tag, 0.0);
+            self.ambient_channels.push(ambient_channel);
+        }
+    }
+
+    // retrieves the channel currently having the given tag
     fn get_ambient_channel(
         &mut self,
         channel_tag: AmbientChannelTag,
-        volume_multiplier: f32,
     ) -> Option<&mut AmbientChannel> {
-        if let Some(audio_stream) = &self.audio_stream {
-            if self.ambient_channels.is_empty() {
-                let mut ambient_channel =
-                    AmbientChannel::new(audio_stream, channel_tag, volume_multiplier);
-                ambient_channel.set_volume(self.get_sfx_volume());
-                self.ambient_channels.push(ambient_channel);
-            } else {
-                let sfx_volume = self.get_sfx_volume();
-                for channel in self.ambient_channels.iter_mut() {
-                    if channel.get_tag() == channel_tag {
-                        channel.set_multiplier(volume_multiplier);
-                        channel.set_volume(sfx_volume);
-                        return Some(channel);
-                    }
+        if self.audio_stream.is_some() {
+            let mut tag_match = false;
+            let mut found_channel = None;
+            for channel in self.ambient_channels.iter_mut() {
+                if channel.get_tag() == channel_tag {
+                    tag_match = true;
+                    found_channel = Some(channel);
+                    break;
                 }
             }
+            if tag_match {
+                return found_channel;
+            } else {
+                return None;
+            }
+        } else {
+            return None;
         }
-
-        None
     }
 
-    fn set_ambient_volume(&mut self, volume_multiplier: f32) {
+    // sets the volume of the channel with the given tag to the given volume
+    fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag, volume_multiplier: f32) {
         if self.audio_stream.is_some() {
             let sfx_volume = self.get_sfx_volume();
-            if let Some(channel) = self.ambient_channels.iter_mut().last() {
+            if let Some(channel) = self.get_ambient_channel(channel_tag) {
                 channel.set_multiplier(volume_multiplier);
                 channel.set_volume(sfx_volume);
             }
         }
     }
 
-    fn get_ambient_volume(&mut self) -> f32 {
+    // retrieves volume (pre-sfx-setting) of the channel with a given tag
+    fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 {
         if self.audio_stream.is_some() {
-            if let Some(channel) = self.ambient_channels.iter_mut().last() {
-                channel.get_volume() / self.get_sfx_volume()
+            if let Some(channel) = self.get_ambient_channel(channel_tag) {
+                let channel_multiplier = channel.get_multiplier();
+                channel_multiplier
             } else {
                 0.0
             }
@@ -406,8 +418,10 @@ impl AudioFrontend {
         }
     }
 
+    // this retrieves the current setting for sfx volume
     pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume * self.master_volume }
 
+    // this retrieves the current setting for music volume
     pub fn get_music_volume(&self) -> f32 { self.music_volume * self.master_volume }
 
     pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 6f29dc0434..9f8d446388 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -19,7 +19,12 @@ pub use self::{
     trail::TrailMgr,
 };
 use crate::{
-    audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
+    audio::{
+        ambient::{AmbientRainMgr, AmbientWindMgr},
+        music::MusicMgr,
+        sfx::SfxMgr,
+        AudioFrontend,
+    },
     render::{
         create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup,
         Light, Model, PointLightMatrix, PostProcessLocals, Renderer, Shadow, ShadowLocals,
@@ -102,7 +107,8 @@ pub struct Scene {
     figure_mgr: FigureMgr,
     pub sfx_mgr: SfxMgr,
     music_mgr: MusicMgr,
-    ambient_mgr: AmbientMgr,
+    ambient_wind_mgr: AmbientWindMgr,
+    ambient_rain_mgr: AmbientRainMgr,
 }
 
 pub struct SceneData<'a> {
@@ -314,7 +320,8 @@ impl Scene {
             figure_mgr: FigureMgr::new(renderer),
             sfx_mgr: SfxMgr::default(),
             music_mgr: MusicMgr::default(),
-            ambient_mgr: AmbientMgr::default(),
+            ambient_wind_mgr: AmbientWindMgr::default(),
+            ambient_rain_mgr: AmbientRainMgr::default(),
         }
     }
 
@@ -1066,7 +1073,9 @@ impl Scene {
             client,
         );
         self.music_mgr.maintain(audio, scene_data.state, client);
-        self.ambient_mgr
+        self.ambient_wind_mgr
+            .maintain(audio, scene_data.state, client, &self.camera);
+        self.ambient_rain_mgr
             .maintain(audio, scene_data.state, client, &self.camera);
     }
 

From 86814eba04eae373c20fdfaacc8daf51578f79e4 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Sun, 13 Mar 2022 21:20:08 +0000
Subject: [PATCH 15/71] Made rain look better

---
 assets/voxygen/shaders/clouds-frag.glsl      | 20 ++++++++++++--------
 assets/voxygen/shaders/fluid-frag/shiny.glsl |  2 +-
 assets/voxygen/shaders/terrain-frag.glsl     |  2 +-
 3 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index eab938f7b5..85787e238a 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -102,9 +102,9 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         vec3 old_color = color.rgb;
 
-        float fall_rate = 40.0;
+        float fall_rate = 70.0;
 
-        dir.xy += wind_vel * dir.z / fall_rate;
+        dir.xy += wind_vel * dir.z / fall_rate * 0;
         dir = normalize(dir);
 
         float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
@@ -114,8 +114,8 @@ void main() {
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
         float rain_density = rain_density_at(cam_wpos.xy) * 10.0;
         if (rain_density > 0) {
-            float rain_dist = 50.0;
-            for (int i = 0; i < 5; i ++) {
+            float rain_dist = 150.0;
+            for (int i = 0; i < 6; i ++) {
                 rain_dist *= 0.3;
 
                 vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
@@ -125,8 +125,12 @@ void main() {
                     continue;
                 }
 
-                vec2 drop_density = vec2(30, 3);
-                vec2 drop_size = vec2(0.0015, 0.17);
+                if (dot(rpos * vec3(1, 1, 0.3), rpos) < 1) {
+                    break;
+                }
+
+                vec2 drop_density = vec2(30, 1);
+                vec2 drop_size = vec2(0.0008, 0.05);
 
                 vec2 rain_pos = (view_pos * rain_dist);
                 rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
@@ -137,8 +141,8 @@ void main() {
                 }
                 vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
 
-                float avg_alpha = (drop_size.x * drop_size.y) / 1;
-                float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size), 0));
+                float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
+                float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
                 float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
                 color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
             }
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index e01e2982bf..39538ba87b 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -150,7 +150,7 @@ void main() {
     );
 
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 50.0;
         if (rain_density > 0 && surf_norm.z > 0.5) {
             vec3 drop_density = vec3(2, 2, 2);
             vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index 168883728f..df76c504b7 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -232,7 +232,7 @@ void main() {
     vec3 k_s = vec3(R_s);
 
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 100.0;
+        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 50.0;
         if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
             vec3 pos = f_pos + focus_off.xyz;
             vec3 drop_density = vec3(2, 2, 2);

From e37f2be8204952b4973fd26d59f8075f545f2ec8 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Mon, 14 Mar 2022 13:01:53 +0100
Subject: [PATCH 16/71] Put rainbows behind experimental

---
 assets/voxygen/shaders/include/cloud/regular.glsl | 4 ++++
 voxygen/src/render/mod.rs                         | 4 +++-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 4545a81f91..0281c4c238 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -247,11 +247,13 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
         int i;
 
         #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
+        #ifdef EXPERIMENTAL_RAINBOWS
             // TODO: Make it a double rainbow
             float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
             int rainbow_c = int(floor(rainbow_t));
             rainbow_t = fract(rainbow_t);
         #endif
+        #endif
 
         for (i = 0; cdist > min_dist && i < 250; i ++) {
             ldist = cdist;
@@ -287,6 +289,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
 
             // Rainbow
             #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
+            #ifdef EXPERIMENTAL_RAINBOWS
                 if (rainbow_c >= 0 && rainbow_c < 8) {
                     float rain = rain_density_at(pos.xy);
                     vec3 colors[9] = {
@@ -307,6 +310,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
                     );
                 }
             #endif
+            #endif
         }
     #ifdef IS_POSTPROCESS
         }
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index b0a30b8b0b..923af4c49f 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -467,6 +467,8 @@ pub enum ExperimentalShader {
     /// Display grid lines to visualize the distribution of shadow map texels
     /// for the directional light from the sun.
     DirectionalShadowMapTexelGrid,
-    // Enable rain, unfinished and goes through blocks
+    /// Enable rain, unfinished and goes through blocks
     Rain,
+    /// Enable rainbows
+    Rainbows,
 }

From 4fa26445074c4ab87b0c7800b558ef24586961ac Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 15 Mar 2022 09:34:01 +0100
Subject: [PATCH 17/71] Interpolate weather in voxygen

---
 common/src/weather.rs                       | 10 ++-
 voxygen/src/render/pipelines/lod_terrain.rs | 74 +++++++++++++++++++--
 voxygen/src/render/pipelines/mod.rs         |  4 +-
 voxygen/src/scene/lod.rs                    |  7 ++
 voxygen/src/session/mod.rs                  | 25 +------
 5 files changed, 89 insertions(+), 31 deletions(-)

diff --git a/common/src/weather.rs b/common/src/weather.rs
index 486c66258c..750fc0c68a 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -1,7 +1,7 @@
 use std::fmt;
 
 use serde::{Deserialize, Serialize};
-use vek::Vec2;
+use vek::{Lerp, Vec2};
 
 pub const CHUNKS_PER_CELL: u32 = 16;
 // Weather::default is Clear, 0 degrees C and no wind
@@ -31,6 +31,14 @@ impl Weather {
             _ => WeatherKind::Clear,
         }
     }
+
+    pub fn lerp(from: &Self, to: &Self, t: f32) -> Self {
+        Self {
+            cloud: f32::lerp(from.cloud, to.cloud, t),
+            rain: f32::lerp(from.rain, to.rain, t),
+            wind: Vec2::<f32>::lerp(from.wind, to.wind, t),
+        }
+    }
 }
 
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs
index 437ea73f11..22aabfa26b 100644
--- a/voxygen/src/render/pipelines/lod_terrain.rs
+++ b/voxygen/src/render/pipelines/lod_terrain.rs
@@ -1,6 +1,7 @@
 use super::super::{AaMode, GlobalsLayouts, Renderer, Texture, Vertex as VertexTrait};
 use bytemuck::{Pod, Zeroable};
-use std::mem;
+use common::{grid::Grid, weather::Weather};
+use std::{mem, time::Instant};
 use vek::*;
 
 #[repr(C)]
@@ -31,12 +32,77 @@ impl VertexTrait for Vertex {
     const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
 }
 
+pub struct WeatherTexture {
+    a: (Grid<Weather>, Instant),
+    b: (Grid<Weather>, Instant),
+    pub tex: Texture,
+}
+
+impl WeatherTexture {
+    fn new(tex: Texture) -> Self {
+        Self {
+            a: (Grid::new(Vec2::zero(), Default::default()), Instant::now()),
+            b: (Grid::new(Vec2::zero(), Default::default()), Instant::now()),
+            tex,
+        }
+    }
+
+    pub fn update_weather(&mut self, weather: Grid<Weather>) {
+        self.a = mem::replace(&mut self.b, (weather, Instant::now()));
+    }
+
+    pub fn update_texture(&self, renderer: &mut Renderer) {
+        let a = &self.a.0;
+        let b = &self.b.0;
+        let size = self.b.0.size().as_::<u32>();
+        if a.size() != b.size() {
+            renderer.update_texture(
+                &self.tex,
+                [0, 0],
+                [size.x, size.y],
+                &b.iter()
+                    .map(|(_, w)| {
+                        [
+                            (w.cloud * 255.0) as u8,
+                            (w.rain * 255.0) as u8,
+                            (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
+                            (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
+                        ]
+                    })
+                    .collect::<Vec<_>>(),
+            );
+        } else {
+            // Assume updates are regular
+            let t = (self.b.1.elapsed().as_secs_f32()
+                / self.b.1.duration_since(self.a.1).as_secs_f32())
+            .clamp(0.0, 1.0);
+            renderer.update_texture(
+                &self.tex,
+                [0, 0],
+                [size.x, size.y],
+                &a.iter()
+                    .zip(b.iter())
+                    .map(|((_, a), (_, b))| {
+                        let w = Weather::lerp(a, b, t);
+                        [
+                            (w.cloud * 255.0) as u8,
+                            (w.rain * 255.0) as u8,
+                            (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
+                            (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
+                        ]
+                    })
+                    .collect::<Vec<_>>(),
+            );
+        }
+    }
+}
+
 pub struct LodData {
     pub map: Texture,
     pub alt: Texture,
     pub horizon: Texture,
     pub tgt_detail: u32,
-    pub clouds: Texture,
+    pub weather: WeatherTexture,
 }
 
 impl LodData {
@@ -135,7 +201,7 @@ impl LodData {
         );
         //             SamplerInfo {
         //                 border: [1.0, 0.0, 1.0, 0.0].into(),
-        let clouds = {
+        let weather = {
             let texture_info = wgpu::TextureDescriptor {
                 label: None,
                 size: wgpu::Extent3d {
@@ -185,7 +251,7 @@ impl LodData {
             alt,
             horizon,
             tgt_detail,
-            clouds,
+            weather: WeatherTexture::new(weather),
         }
     }
 }
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index abee155b9f..b211162249 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -578,11 +578,11 @@ impl GlobalsLayouts {
             },
             wgpu::BindGroupEntry {
                 binding: 12,
-                resource: wgpu::BindingResource::TextureView(&lod_data.clouds.view),
+                resource: wgpu::BindingResource::TextureView(&lod_data.weather.tex.view),
             },
             wgpu::BindGroupEntry {
                 binding: 13,
-                resource: wgpu::BindingResource::Sampler(&lod_data.clouds.sampler),
+                resource: wgpu::BindingResource::Sampler(&lod_data.weather.tex.sampler),
             },
         ]
     }
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 5953498d76..471439a82f 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -10,9 +10,11 @@ use crate::{
 use client::Client;
 use common::{
     assets::{AssetExt, ObjAsset},
+    grid::Grid,
     lod,
     spiral::Spiral2d,
     util::srgba_to_linear,
+    weather::Weather,
 };
 use hashbrown::HashMap;
 use std::ops::Range;
@@ -77,6 +79,10 @@ impl Lod {
 
     pub fn get_data(&self) -> &LodData { &self.data }
 
+    pub fn update_weather(&mut self, weather: Grid<Weather>) {
+        self.data.weather.update_weather(weather);
+    }
+
     pub fn set_detail(&mut self, detail: u32) {
         // Make sure the recorded detail is even.
         self.data.tgt_detail = (detail - detail % 2).max(100).min(2500);
@@ -89,6 +95,7 @@ impl Lod {
         focus_pos: Vec3<f32>,
         camera: &Camera,
     ) {
+        self.data.weather.update_texture(renderer);
         // Update LoD terrain mesh according to detail
         if self
             .model
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 50e997c868..c5e7b23c17 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -4,7 +4,6 @@ mod target;
 
 use std::{cell::RefCell, collections::HashSet, rc::Rc, result::Result, time::Duration};
 
-use itertools::Itertools;
 #[cfg(not(target_os = "macos"))]
 use mumble_link::SharedLink;
 use ordered_float::OrderedFloat;
@@ -326,29 +325,7 @@ impl SessionState {
                     self.hud.show.update_map_markers(event);
                 },
                 client::Event::WeatherUpdate => {
-                    //weather
-                    //    .iter_mut()
-                    //    .for_each(|(p, c)| *c = if (p.x + p.y) % 2 == 0 { 1.0 } else { 0.0 });
-                    let size = client.get_weather().size();
-                    global_state.window.renderer_mut().update_texture(
-                        &self.scene.lod.get_data().clouds,
-                        [0, 0],
-                        [size.x as u32, size.y as u32],
-                        client
-                            .get_weather()
-                            .iter()
-                            .map(|(_, w)| {
-                                //println!("{}, ", w.cloud);
-                                [
-                                    (w.cloud * 255.0) as u8,
-                                    (w.rain * 255.0) as u8,
-                                    (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
-                                    (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
-                                ]
-                            })
-                            .collect_vec()
-                            .as_slice(),
-                    );
+                    self.scene.lod.update_weather(client.get_weather().clone());
                 },
             }
         }

From 6093e1972a15c0fda14d966a1af892b8ea8aad86 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Tue, 15 Mar 2022 14:18:16 +0000
Subject: [PATCH 18/71] Add directional rain back

---
 assets/voxygen/shaders/clouds-frag.glsl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 85787e238a..0bdd58a900 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -104,7 +104,7 @@ void main() {
 
         float fall_rate = 70.0;
 
-        dir.xy += wind_vel * dir.z / fall_rate * 0;
+        dir.xy += wind_vel * dir.z / fall_rate;
         dir = normalize(dir);
 
         float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);

From 767093105f5eec3b5f439171006d51eb6e586b6b Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Tue, 15 Mar 2022 14:59:15 +0000
Subject: [PATCH 19/71] Rebalanced rain

---
 assets/voxygen/shaders/clouds-frag.glsl           | 4 ++--
 assets/voxygen/shaders/include/cloud/regular.glsl | 2 +-
 server/src/weather/sim.rs                         | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 0bdd58a900..f7ae9feaef 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -115,7 +115,7 @@ void main() {
         float rain_density = rain_density_at(cam_wpos.xy) * 10.0;
         if (rain_density > 0) {
             float rain_dist = 150.0;
-            for (int i = 0; i < 6; i ++) {
+            for (int i = 0; i < 7; i ++) {
                 rain_dist *= 0.3;
 
                 vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
@@ -125,7 +125,7 @@ void main() {
                     continue;
                 }
 
-                if (dot(rpos * vec3(1, 1, 0.3), rpos) < 1) {
+                if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
                     break;
                 }
 
diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 0281c4c238..62d8893582 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -61,7 +61,7 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
         ;
     }
 
-    float cloud_alt = CLOUD_AVG_ALT + alt * 0.5;
+    float cloud_alt = alt + 1500;
 
     //vec2 cloud_attr = get_cloud_heights(wind_pos.xy);
     float sun_access = 0.0;
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 671da19498..ce37bd7bb8 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -176,7 +176,7 @@ impl WeatherSim {
         for (point, cell) in self.weather.iter_mut() {
             let wpos = cell_to_wpos(point);
 
-            let pos = wpos.as_::<f64>() + time as f64 * 0.25;
+            let pos = wpos.as_::<f64>() + time as f64 * 0.1;
 
             let space_scale = 7_500.0;
             let time_scale = 100_000.0;
@@ -185,7 +185,7 @@ impl WeatherSim {
             let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
 
             cell.cloud = (1.0 - pressure) * 0.5;
-            cell.rain = (1.0 - pressure - 0.2).max(0.0).powf(1.5);
+            cell.rain = (1.0 - pressure - 0.15).max(0.0).powf(1.5);
             cell.wind = Vec2::new(
                 rain_nz.get(spos.into_array()) as f32,
                 rain_nz.get((spos + 1.0).into_array()) as f32,

From 48117988e1f2287865b85c3541ee2498736ae3fb Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Tue, 15 Mar 2022 15:51:41 +0000
Subject: [PATCH 20/71] Better rain threshold

---
 assets/voxygen/shaders/include/cloud/regular.glsl | 2 +-
 server/src/weather/sim.rs                         | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 62d8893582..01118f853f 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -61,7 +61,7 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
         ;
     }
 
-    float cloud_alt = alt + 1500;
+    float cloud_alt = alt + 1800;
 
     //vec2 cloud_attr = get_cloud_heights(wind_pos.xy);
     float sun_access = 0.0;
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index ce37bd7bb8..fac7b330a7 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -184,8 +184,9 @@ impl WeatherSim {
 
             let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
 
+            const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
             cell.cloud = (1.0 - pressure) * 0.5;
-            cell.rain = (1.0 - pressure - 0.15).max(0.0).powf(1.5);
+            cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
             cell.wind = Vec2::new(
                 rain_nz.get(spos.into_array()) as f32,
                 rain_nz.get((spos + 1.0).into_array()) as f32,

From b578f0231f60aaa5765f5bce406a2e5761168965 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 15 Mar 2022 20:26:27 +0100
Subject: [PATCH 21/71] Interpolate in client

---
 client/src/lib.rs                           | 103 ++++++++++++++++----
 voxygen/src/render/pipelines/lod_terrain.rs |  72 +-------------
 voxygen/src/render/pipelines/mod.rs         |   4 +-
 voxygen/src/scene/lod.rs                    |  26 +++--
 voxygen/src/session/mod.rs                  |   3 -
 5 files changed, 110 insertions(+), 98 deletions(-)

diff --git a/client/src/lib.rs b/client/src/lib.rs
index 60a2e47ae9..95229f5570 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -107,7 +107,6 @@ pub enum Event {
     CharacterEdited(CharacterId),
     CharacterError(String),
     MapMarker(comp::MapMarkerUpdate),
-    WeatherUpdate,
 }
 
 pub struct WorldData {
@@ -153,13 +152,60 @@ pub struct SiteInfoRich {
     pub economy: Option<EconomyInfo>,
 }
 
+struct WeatherLerp {
+    old: (Grid<Weather>, Instant),
+    new: (Grid<Weather>, Instant),
+    current: Grid<Weather>,
+}
+
+impl WeatherLerp {
+    fn weather_update(&mut self, weather: Grid<Weather>) {
+        self.old = mem::replace(&mut self.new, (weather, Instant::now()));
+    }
+
+    fn update(&mut self) {
+        let old = &self.old.0;
+        let new = &self.new.0;
+        if old.size() != new.size() {
+            if self.current.size() != new.size() {
+                self.current = new.clone();
+            }
+        } else {
+            // Assume updates are regular
+            let t = (self.new.1.elapsed().as_secs_f32()
+                / self.new.1.duration_since(self.old.1).as_secs_f32())
+            .clamp(0.0, 1.0);
+
+            old.iter().zip(new.iter()).for_each(|((p, old), (_, new))| {
+                self.current[p] = Weather::lerp(old, new, t);
+            });
+        }
+    }
+}
+
+impl Default for WeatherLerp {
+    fn default() -> Self {
+        Self {
+            old: (
+                Grid::new(Vec2::new(0, 0), Weather::default()),
+                Instant::now(),
+            ),
+            new: (
+                Grid::new(Vec2::new(0, 0), Weather::default()),
+                Instant::now(),
+            ),
+            current: Grid::new(Vec2::new(0, 0), Weather::default()),
+        }
+    }
+}
+
 pub struct Client {
     registered: bool,
     presence: Option<PresenceKind>,
     runtime: Arc<Runtime>,
     server_info: ServerInfo,
     world_data: WorldData,
-    weather: Grid<Weather>,
+    weather: WeatherLerp,
     player_list: HashMap<Uid, PlayerInfo>,
     character_list: CharacterList,
     sites: HashMap<SiteId, SiteInfoRich>,
@@ -611,7 +657,7 @@ impl Client {
                 lod_horizon,
                 map: world_map,
             },
-            weather: Grid::new(Vec2::new(1, 1), Weather::default()),
+            weather: WeatherLerp::default(),
             player_list: HashMap::new(),
             character_list: CharacterList::default(),
             sites: sites
@@ -1417,23 +1463,45 @@ impl Client {
             .map(|v| v.0)
     }
 
-    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
+    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather.current }
 
     pub fn current_weather(&self) -> Weather {
-        if let Some(position) = self.position() {
-            let cell_pos = (position.xy()
-                / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_()))
-            .as_();
-            *self.weather.get(cell_pos).unwrap_or(&Weather::default())
-        } else {
-            Weather::default()
-        }
+        self.position()
+            .map(|wpos| self.current_weather_wpos(wpos.xy()))
+            .unwrap_or_default()
     }
 
     pub fn current_weather_wpos(&self, wpos: Vec2<f32>) -> Weather {
-        let cell_pos =
-            (wpos / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_())).as_();
-        *self.weather.get(cell_pos).unwrap_or(&Weather::default())
+        let cell_pos = wpos / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_());
+        let rpos = cell_pos.map(|e| e.fract());
+        let cell_pos = cell_pos.map(|e| e.floor());
+
+        let wpos = cell_pos.as_::<i32>();
+        Weather::lerp(
+            &Weather::lerp(
+                self.weather
+                    .current
+                    .get(wpos)
+                    .unwrap_or(&Weather::default()),
+                self.weather
+                    .current
+                    .get(wpos + Vec2::unit_x())
+                    .unwrap_or(&Weather::default()),
+                rpos.x,
+            ),
+            &Weather::lerp(
+                self.weather
+                    .current
+                    .get(wpos + Vec2::unit_x())
+                    .unwrap_or(&Weather::default()),
+                self.weather
+                    .current
+                    .get(wpos + Vec2::one())
+                    .unwrap_or(&Weather::default()),
+                rpos.x,
+            ),
+            rpos.y,
+        )
     }
 
     pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
@@ -1681,6 +1749,8 @@ impl Client {
 
         // 5) Terrain
         self.tick_terrain()?;
+        // TODO: put this somewhere else?
+        self.weather.update();
 
         // Send a ping to the server once every second
         if self.state.get_time() - self.last_server_ping > 1. {
@@ -2217,8 +2287,7 @@ impl Client {
                 frontend_events.push(Event::MapMarker(event));
             },
             ServerGeneral::WeatherUpdate(weather) => {
-                self.weather = weather;
-                frontend_events.push(Event::WeatherUpdate);
+                self.weather.weather_update(weather);
             },
             _ => unreachable!("Not a in_game message"),
         }
diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs
index 22aabfa26b..7cabfd4ef7 100644
--- a/voxygen/src/render/pipelines/lod_terrain.rs
+++ b/voxygen/src/render/pipelines/lod_terrain.rs
@@ -1,7 +1,6 @@
 use super::super::{AaMode, GlobalsLayouts, Renderer, Texture, Vertex as VertexTrait};
 use bytemuck::{Pod, Zeroable};
-use common::{grid::Grid, weather::Weather};
-use std::{mem, time::Instant};
+use std::mem;
 use vek::*;
 
 #[repr(C)]
@@ -32,77 +31,12 @@ impl VertexTrait for Vertex {
     const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() as wgpu::BufferAddress;
 }
 
-pub struct WeatherTexture {
-    a: (Grid<Weather>, Instant),
-    b: (Grid<Weather>, Instant),
-    pub tex: Texture,
-}
-
-impl WeatherTexture {
-    fn new(tex: Texture) -> Self {
-        Self {
-            a: (Grid::new(Vec2::zero(), Default::default()), Instant::now()),
-            b: (Grid::new(Vec2::zero(), Default::default()), Instant::now()),
-            tex,
-        }
-    }
-
-    pub fn update_weather(&mut self, weather: Grid<Weather>) {
-        self.a = mem::replace(&mut self.b, (weather, Instant::now()));
-    }
-
-    pub fn update_texture(&self, renderer: &mut Renderer) {
-        let a = &self.a.0;
-        let b = &self.b.0;
-        let size = self.b.0.size().as_::<u32>();
-        if a.size() != b.size() {
-            renderer.update_texture(
-                &self.tex,
-                [0, 0],
-                [size.x, size.y],
-                &b.iter()
-                    .map(|(_, w)| {
-                        [
-                            (w.cloud * 255.0) as u8,
-                            (w.rain * 255.0) as u8,
-                            (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
-                            (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
-                        ]
-                    })
-                    .collect::<Vec<_>>(),
-            );
-        } else {
-            // Assume updates are regular
-            let t = (self.b.1.elapsed().as_secs_f32()
-                / self.b.1.duration_since(self.a.1).as_secs_f32())
-            .clamp(0.0, 1.0);
-            renderer.update_texture(
-                &self.tex,
-                [0, 0],
-                [size.x, size.y],
-                &a.iter()
-                    .zip(b.iter())
-                    .map(|((_, a), (_, b))| {
-                        let w = Weather::lerp(a, b, t);
-                        [
-                            (w.cloud * 255.0) as u8,
-                            (w.rain * 255.0) as u8,
-                            (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
-                            (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
-                        ]
-                    })
-                    .collect::<Vec<_>>(),
-            );
-        }
-    }
-}
-
 pub struct LodData {
     pub map: Texture,
     pub alt: Texture,
     pub horizon: Texture,
     pub tgt_detail: u32,
-    pub weather: WeatherTexture,
+    pub weather: Texture,
 }
 
 impl LodData {
@@ -251,7 +185,7 @@ impl LodData {
             alt,
             horizon,
             tgt_detail,
-            weather: WeatherTexture::new(weather),
+            weather,
         }
     }
 }
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index b211162249..879d22857a 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -578,11 +578,11 @@ impl GlobalsLayouts {
             },
             wgpu::BindGroupEntry {
                 binding: 12,
-                resource: wgpu::BindingResource::TextureView(&lod_data.weather.tex.view),
+                resource: wgpu::BindingResource::TextureView(&lod_data.weather.view),
             },
             wgpu::BindGroupEntry {
                 binding: 13,
-                resource: wgpu::BindingResource::Sampler(&lod_data.weather.tex.sampler),
+                resource: wgpu::BindingResource::Sampler(&lod_data.weather.sampler),
             },
         ]
     }
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 471439a82f..5c8cd58079 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -10,11 +10,9 @@ use crate::{
 use client::Client;
 use common::{
     assets::{AssetExt, ObjAsset},
-    grid::Grid,
     lod,
     spiral::Spiral2d,
     util::srgba_to_linear,
-    weather::Weather,
 };
 use hashbrown::HashMap;
 use std::ops::Range;
@@ -79,10 +77,6 @@ impl Lod {
 
     pub fn get_data(&self) -> &LodData { &self.data }
 
-    pub fn update_weather(&mut self, weather: Grid<Weather>) {
-        self.data.weather.update_weather(weather);
-    }
-
     pub fn set_detail(&mut self, detail: u32) {
         // Make sure the recorded detail is even.
         self.data.tgt_detail = (detail - detail % 2).max(100).min(2500);
@@ -95,7 +89,6 @@ impl Lod {
         focus_pos: Vec3<f32>,
         camera: &Camera,
     ) {
-        self.data.weather.update_texture(renderer);
         // Update LoD terrain mesh according to detail
         if self
             .model
@@ -188,6 +181,25 @@ impl Lod {
                 }
             }
         }
+        // Update weather texture
+        let weather = client.get_weather();
+        let size = weather.size().as_::<u32>();
+        renderer.update_texture(
+            &self.data.weather,
+            [0, 0],
+            [size.x, size.y],
+            &weather
+                .iter()
+                .map(|(_, w)| {
+                    [
+                        (w.cloud * 255.0) as u8,
+                        (w.rain * 255.0) as u8,
+                        (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
+                        (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
+                    ]
+                })
+                .collect::<Vec<_>>(),
+        );
     }
 
     pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index c5e7b23c17..ac764b6669 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -324,9 +324,6 @@ impl SessionState {
                 client::Event::MapMarker(event) => {
                     self.hud.show.update_map_markers(event);
                 },
-                client::Event::WeatherUpdate => {
-                    self.scene.lod.update_weather(client.get_weather().clone());
-                },
             }
         }
 

From ca815f25a14fac08e5a77aac88514bc1bf6f60ae Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Tue, 15 Mar 2022 11:56:18 -0700
Subject: [PATCH 22/71] merging some UI sfx from a now-dead branch

Merge part 2

merge part 3

Merge part 4

merge part 5
---
 common/src/outcome.rs                    | 11 +++--
 server/src/events/entity_manipulation.rs | 11 +----
 server/src/events/interaction.rs         |  7 +--
 voxygen/src/audio/channel.rs             | 35 ++++++++++++-
 voxygen/src/audio/mod.rs                 | 54 ++++++++++++++++++--
 voxygen/src/audio/sfx/mod.rs             | 63 +++++++++++++++---------
 voxygen/src/main.rs                      |  2 +-
 voxygen/src/scene/mod.rs                 |  9 +++-
 voxygen/src/scene/particle.rs            |  3 +-
 voxygen/src/session/mod.rs               |  7 ++-
 voxygen/src/settings/audio.rs            |  4 +-
 11 files changed, 148 insertions(+), 58 deletions(-)

diff --git a/common/src/outcome.rs b/common/src/outcome.rs
index 3604c49a52..85b5ae95fe 100644
--- a/common/src/outcome.rs
+++ b/common/src/outcome.rs
@@ -52,8 +52,6 @@ pub enum Outcome {
         uid: Uid,
         skill_tree: comp::skillset::SkillGroupKind,
         total_points: u16,
-        // TODO: Access ECS to get position from Uid to conserve bandwidth
-        pos: Vec3<f32>,
     },
     ComboChange {
         uid: Uid,
@@ -95,6 +93,9 @@ pub enum Outcome {
         pos: Vec3<f32>,
         wielded: bool,
     },
+    Jump {
+        pos: Vec3<f32>,
+    }
 }
 
 impl Outcome {
@@ -104,7 +105,6 @@ impl Outcome {
             | Outcome::ProjectileShot { pos, .. }
             | Outcome::ProjectileHit { pos, .. }
             | Outcome::Beam { pos, .. }
-            | Outcome::SkillPointGain { pos, .. }
             | Outcome::SummonedCreature { pos, .. }
             | Outcome::HealthChange { pos, .. }
             | Outcome::Death { pos, .. }
@@ -112,9 +112,10 @@ impl Outcome {
             | Outcome::PoiseChange { pos, .. }
             | Outcome::GroundSlam { pos }
             | Outcome::Utterance { pos, .. }
-            | Outcome::Glider { pos, .. } => Some(*pos),
+            | Outcome::Glider { pos, .. }
+            | Outcome::Jump { pos, .. } => Some(*pos),
             Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
-            Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
+            Outcome::ExpChange { .. } | Outcome::ComboChange { .. } | Outcome::SkillPointGain { .. } => None,
         }
     }
 }
diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs
index d79963f320..2bd1e04a90 100644
--- a/server/src/events/entity_manipulation.rs
+++ b/server/src/events/entity_manipulation.rs
@@ -379,23 +379,16 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, last_change: Healt
 
         exp_awards.iter().for_each(|(attacker, exp_reward, _)| {
             // Process the calculated EXP rewards
-            if let (
-                Some(mut attacker_skill_set),
-                Some(attacker_uid),
-                Some(attacker_inventory),
-                Some(pos),
-            ) = (
+            if let (Some(mut attacker_skill_set), Some(attacker_uid), Some(attacker_inventory)) = (
                 skill_sets.get_mut(*attacker),
                 uids.get(*attacker),
                 inventories.get(*attacker),
-                positions.get(*attacker),
             ) {
                 handle_exp_gain(
                     *exp_reward,
                     attacker_inventory,
                     &mut attacker_skill_set,
                     attacker_uid,
-                    pos,
                     &mut outcomes,
                 );
             }
@@ -1178,7 +1171,6 @@ fn handle_exp_gain(
     inventory: &Inventory,
     skill_set: &mut SkillSet,
     uid: &Uid,
-    pos: &Pos,
     outcomes: &mut EventBus<Outcome>,
 ) {
     use comp::inventory::{item::ItemKind, slot::EquipSlot};
@@ -1219,7 +1211,6 @@ fn handle_exp_gain(
                 uid: *uid,
                 skill_tree: *pool,
                 total_points: level_outcome,
-                pos: pos.0,
             });
         }
     }
diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs
index e374f8f8f5..7f5f1e8ed3 100644
--- a/server/src/events/interaction.rs
+++ b/server/src/events/interaction.rs
@@ -186,16 +186,11 @@ pub fn handle_mine_block(
                     ) {
                         let skill_group = SkillGroupKind::Weapon(tool);
                         let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>();
-                        let positions = state.ecs().read_component::<comp::Pos>();
-                        if let (Some(level_outcome), Some(pos)) = (
-                            skillset.add_experience(skill_group, exp_reward),
-                            positions.get(entity),
-                        ) {
+                        if let Some(level_outcome) = skillset.add_experience(skill_group, exp_reward) {
                             outcome_bus.emit_now(Outcome::SkillPointGain {
                                 uid,
                                 skill_tree: skill_group,
                                 total_points: level_outcome,
-                                pos: pos.0,
                             });
                         }
                         outcome_bus.emit_now(Outcome::ExpChange {
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index 5995a36a89..ab34564933 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -255,6 +255,8 @@ impl SfxChannel {
 
     pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); }
 
+    pub fn stop(&mut self) { self.sink.stop(); }
+
     pub fn is_done(&self) -> bool { self.sink.empty() }
 
     pub fn set_pos(&mut self, pos: Vec3<f32>) { self.pos = pos; }
@@ -262,11 +264,40 @@ impl SfxChannel {
     pub fn update(&mut self, listener: &Listener) {
         const FALLOFF: f32 = 0.13;
 
-        self.sink
-            .set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array());
+        self.sink.set_emitter_position(
+            ((self.pos - listener.pos) * FALLOFF).into_array(),
+        );
         self.sink
             .set_left_ear_position(listener.ear_left_rpos.into_array());
         self.sink
             .set_right_ear_position(listener.ear_right_rpos.into_array());
     }
 }
+
+pub struct UIChannel {
+    sink: Sink,
+}
+
+impl UIChannel {
+    pub fn new(stream: &OutputStreamHandle) -> Self {
+        Self {
+            sink: Sink::try_new(stream).unwrap()
+        }
+    }
+
+    pub fn play<S>(&mut self, source: S)
+    where
+        S: Source + Send + 'static,
+        S::Item: Sample,
+        S::Item: Send,
+        <S as std::iter::Iterator>::Item: std::fmt::Debug,
+    {
+        self.sink.append(source);
+    }
+
+    pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume); }
+
+    pub fn stop(&mut self) { self.sink.stop(); }
+
+    pub fn is_done(&self) -> bool { self.sink.empty() }
+}
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index bb84132fa5..9656f0c4d7 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -7,7 +7,7 @@ pub mod music;
 pub mod sfx;
 pub mod soundcache;
 
-use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel};
+use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel};
 use fader::Fader;
 use music::MusicTransitionManifest;
 use sfx::{SfxEvent, SfxTriggerItem};
@@ -43,6 +43,7 @@ pub struct AudioFrontend {
     music_channels: Vec<MusicChannel>,
     ambient_channels: Vec<AmbientChannel>,
     sfx_channels: Vec<SfxChannel>,
+    ui_channels: Vec<UIChannel>,
     sfx_volume: f32,
     music_volume: f32,
     master_volume: f32,
@@ -53,7 +54,7 @@ pub struct AudioFrontend {
 
 impl AudioFrontend {
     /// Construct with given device
-    pub fn new(/* dev: String, */ num_sfx_channels: usize) -> Self {
+    pub fn new(/* dev: String, */ num_sfx_channels: usize, num_ui_channels: usize) -> Self {
         // Commented out until audio device switcher works
         //let audio_device = get_device_raw(&dev);
 
@@ -81,6 +82,11 @@ impl AudioFrontend {
             sfx_channels.resize_with(num_sfx_channels, || SfxChannel::new(audio_stream));
         };
 
+        let mut ui_channels = Vec::with_capacity(num_ui_channels);
+        if let Some(audio_stream) = &audio_stream {
+            ui_channels.resize_with(num_ui_channels, || UIChannel::new(audio_stream))
+        }
+
         Self {
             // The following is for the disabled device switcher
             //device,
@@ -90,6 +96,7 @@ impl AudioFrontend {
             audio_stream,
             music_channels: Vec::new(),
             sfx_channels,
+            ui_channels,
             ambient_channels: Vec::new(),
             sfx_volume: 1.0,
             music_volume: 1.0,
@@ -119,6 +126,7 @@ impl AudioFrontend {
             audio_stream: None,
             music_channels: Vec::new(),
             sfx_channels: Vec::new(),
+            ui_channels: Vec::new(),
             ambient_channels: Vec::new(),
             sfx_volume: 1.0,
             music_volume: 1.0,
@@ -151,6 +159,19 @@ impl AudioFrontend {
         None
     }
 
+    fn get_ui_channel(&mut self) -> Option<&mut UIChannel> {
+        if self.audio_stream.is_some() {
+            let sfx_volume = self.get_sfx_volume();
+            if let Some(channel) = self.ui_channels.iter_mut().find(|c| c.is_done()) {
+                channel.set_volume(sfx_volume);
+
+                return Some(channel);
+            }
+        }
+
+        None
+    }
+
     /// Retrieve a music channel from the channel list. This inspects the
     /// MusicChannelTag to determine whether we are transitioning between
     /// music types and acts accordingly. For example transitioning between
@@ -195,7 +216,7 @@ impl AudioFrontend {
 
     /// Function to play sfx from external places. Useful for UI and
     /// inventory events
-    pub fn emit_sfx_item(&mut self, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>) {
+    pub fn emit_sfx_item(&mut self, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, vol: Option<f32>) {
         if let Some((event, item)) = trigger_item {
             let sfx_file = match item.files.len() {
                 0 => {
@@ -213,8 +234,7 @@ impl AudioFrontend {
                 },
             };
 
-            // TODO: Should this take `underwater` into consideration?
-            match self.play_sfx(sfx_file, self.listener.pos, None, false) {
+            match self.play_ui_sfx(sfx_file, vol) {
                 Ok(_) => {},
                 Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e),
             }
@@ -282,6 +302,21 @@ impl AudioFrontend {
                 } else {
                     channel.play(sound);
                 }
+            } 
+        }
+        Ok(())
+    }
+
+    pub fn play_ui_sfx(
+        &mut self,
+        sound: &str,
+        vol: Option<f32>,
+    ) -> Result<(), rodio::decoder::DecoderError> {
+        if self.audio_stream.is_some() {
+            let sound = load_ogg(sound).amplify(vol.unwrap_or(1.0));
+
+            if let Some(channel) = self.get_ui_channel() {
+                channel.play(sound);
             }
         }
         Ok(())
@@ -471,6 +506,15 @@ impl AudioFrontend {
         }
     }
 
+    pub fn stop_all_sfx(&mut self) {
+        for channel in self.sfx_channels.iter_mut() {
+            channel.stop()
+        }
+        for channel in self.ui_channels.iter_mut() {
+            channel.stop()
+        }
+    }
+
     // The following is for the disabled device switcher
     //// TODO: figure out how badly this will break things when it is called
     //pub fn set_device(&mut self, name: String) {
diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs
index 6749138dd0..f85a4e9f77 100644
--- a/voxygen/src/audio/sfx/mod.rs
+++ b/voxygen/src/audio/sfx/mod.rs
@@ -82,6 +82,8 @@
 
 mod event_mapper;
 
+use specs::{WorldExt};
+
 use crate::{
     audio::AudioFrontend,
     scene::{Camera, Terrain},
@@ -100,6 +102,7 @@ use common::{
     },
     outcome::Outcome,
     terrain::{BlockKind, TerrainChunk},
+    uid::Uid,
 };
 use common_state::State;
 use event_mapper::SfxEventMapper;
@@ -407,11 +410,14 @@ impl SfxMgr {
         outcome: &Outcome,
         audio: &mut AudioFrontend,
         client: &Client,
+        state: &State,
+        underwater: bool,
     ) {
         if !audio.sfx_enabled() {
             return;
         }
         let triggers = self.triggers.read();
+        let uids = state.ecs().read_storage::<Uid>();
 
         // TODO handle underwater
         match outcome {
@@ -421,12 +427,12 @@ impl SfxMgr {
                     sfx_trigger_item,
                     *pos,
                     Some((power.abs() / 2.5).min(1.5)),
-                    false,
+                    underwater,
                 );
             },
             Outcome::GroundSlam { pos, .. } => {
                 let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundSlam);
-                audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), false);
+                audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
             },
             Outcome::ProjectileShot { pos, body, .. } => {
                 match body {
@@ -437,7 +443,7 @@ impl SfxMgr {
                         | object::Body::ArrowTurret,
                     ) => {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowShot);
-                        audio.emit_sfx(sfx_trigger_item, *pos, None, false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
                     },
                     Body::Object(
                         object::Body::BoltFire
@@ -445,7 +451,7 @@ impl SfxMgr {
                         | object::Body::BoltNature,
                     ) => {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FireShot);
-                        audio.emit_sfx(sfx_trigger_item, *pos, None, false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
                     },
                     _ => {
                         // not mapped to sfx file
@@ -467,37 +473,41 @@ impl SfxMgr {
                 ) => {
                     if target.is_none() {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowMiss);
-                        audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
                     } else if *source == client.uid() {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowHit);
                         audio.emit_sfx(
                             sfx_trigger_item,
                             client.position().unwrap_or(*pos),
                             Some(2.0),
-                            false,
+                            underwater,
                         );
                     } else {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::ArrowHit);
-                        audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, Some(2.0), underwater);
                     }
                 },
                 _ => {},
             },
-            Outcome::SkillPointGain { pos, .. } => {
-                let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SkillPointGain);
-                audio.emit_sfx(sfx_trigger_item, *pos, None, false);
+            Outcome::SkillPointGain { uid, .. } => {
+                if let Some(client_uid) = uids.get(client.entity()) {
+                    if uid == client_uid {
+                        let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SkillPointGain);
+                        audio.emit_sfx_item(sfx_trigger_item, Some(0.4));
+                    }
+                }
             },
             Outcome::Beam { pos, specifier } => match specifier {
                 beam::FrontendSpecifier::LifestealBeam => {
                     if thread_rng().gen_bool(0.5) {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SceptreBeam);
-                        audio.emit_sfx(sfx_trigger_item, *pos, None, false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
                     };
                 },
                 beam::FrontendSpecifier::Flamethrower | beam::FrontendSpecifier::Cultist => {
                     if thread_rng().gen_bool(0.5) {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::FlameThrower);
-                        audio.emit_sfx(sfx_trigger_item, *pos, None, false);
+                        audio.emit_sfx(sfx_trigger_item, *pos, None, underwater);
                     }
                 },
                 beam::FrontendSpecifier::ClayGolem
@@ -511,27 +521,27 @@ impl SfxMgr {
                     sfx_trigger_item,
                     pos.map(|e| e as f32 + 0.5),
                     Some(3.0),
-                    false,
+                    underwater,
                 );
             },
             Outcome::HealthChange { pos, info, .. } => {
                 // Don't emit sound effects from positive damage (healing)
                 if info.amount < Health::HEALTH_EPSILON {
                     let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Damage);
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 }
             },
             Outcome::Death { pos, .. } => {
                 let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Death);
-                audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
             },
             Outcome::Block { pos, parry, .. } => {
                 if *parry {
                     let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Parry);
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 } else {
                     let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Block);
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 }
             },
             Outcome::PoiseChange { pos, state, .. } => match state {
@@ -539,22 +549,22 @@ impl SfxMgr {
                 PoiseState::Interrupted => {
                     let sfx_trigger_item =
                         triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Interrupted));
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 },
                 PoiseState::Stunned => {
                     let sfx_trigger_item =
                         triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Stunned));
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 },
                 PoiseState::Dazed => {
                     let sfx_trigger_item =
                         triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::Dazed));
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 },
                 PoiseState::KnockedDown => {
                     let sfx_trigger_item =
                         triggers.get_key_value(&SfxEvent::PoiseChange(PoiseState::KnockedDown));
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.5), underwater);
                 },
             },
             Outcome::Utterance { pos, kind, body } => {
@@ -562,7 +572,7 @@ impl SfxMgr {
                     let sfx_trigger_item =
                         triggers.get_key_value(&SfxEvent::Utterance(*kind, voice));
                     if let Some(sfx_trigger_item) = sfx_trigger_item {
-                        audio.emit_sfx(Some(sfx_trigger_item), *pos, Some(1.5), false);
+                        audio.emit_sfx(Some(sfx_trigger_item), *pos, Some(1.5), underwater);
                     } else {
                         debug!(
                             "No utterance sound effect exists for ({:?}, {:?})",
@@ -574,12 +584,17 @@ impl SfxMgr {
             Outcome::Glider { pos, wielded } => {
                 if *wielded {
                     let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GliderOpen);
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
                 } else {
                     let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GliderClose);
-                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), false);
+                    audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
                 }
             },
+            // unused for now
+            Outcome::Jump { pos } => {
+                let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Jump);
+                audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater)
+            },
             Outcome::ExpChange { .. }
             | Outcome::ComboChange { .. }
             | Outcome::SummonedCreature { .. } => {},
diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs
index 9b48cffaab..4a995f808b 100644
--- a/voxygen/src/main.rs
+++ b/voxygen/src/main.rs
@@ -223,7 +223,7 @@ fn main() {
     // Setup audio
     let mut audio = match settings.audio.output {
         AudioOutput::Off => AudioFrontend::no_audio(),
-        AudioOutput::Automatic => AudioFrontend::new(settings.audio.num_sfx_channels),
+        AudioOutput::Automatic => AudioFrontend::new(settings.audio.num_sfx_channels, settings.audio.num_ui_channels),
         //    AudioOutput::Device(ref dev) => Some(dev.clone()),
     };
 
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 9f8d446388..ece128be16 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -409,11 +409,18 @@ impl Scene {
         outcome: &Outcome,
         scene_data: &SceneData,
         audio: &mut AudioFrontend,
+        state: &State,
+        cam_pos: Vec3<f32>,
     ) {
         span!(_guard, "handle_outcome", "Scene::handle_outcome");
+        let underwater = state
+                        .terrain()
+                        .get(cam_pos.map(|e| e.floor() as i32))
+                        .map(|b| b.is_liquid())
+                        .unwrap_or(false); 
         self.particle_mgr.handle_outcome(outcome, scene_data);
         self.sfx_mgr
-            .handle_outcome(outcome, audio, scene_data.client);
+            .handle_outcome(outcome, audio, scene_data.client, state, underwater);
 
         match outcome {
             Outcome::Explosion {
diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs
index 5f9c424d39..d155ee50d2 100644
--- a/voxygen/src/scene/particle.rs
+++ b/voxygen/src/scene/particle.rs
@@ -287,7 +287,8 @@ impl ParticleMgr {
             | Outcome::HealthChange { .. }
             | Outcome::PoiseChange { .. }
             | Outcome::Utterance { .. }
-            | Outcome::Glider { .. } => {},
+            | Outcome::Glider { .. }
+            | Outcome::Jump { .. } => {},
         }
     }
 
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index ac764b6669..d976d03756 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -245,7 +245,7 @@ impl SessionState {
                     let sfx_triggers = self.scene.sfx_mgr.triggers.read();
 
                     let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
-                    global_state.audio.emit_sfx_item(sfx_trigger_item);
+                    global_state.audio.emit_sfx_item(sfx_trigger_item, Some(1.0));
 
                     match inv_event {
                         InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
@@ -358,6 +358,7 @@ impl PlayState for SessionState {
             let client = self.client.borrow();
             (client.presence(), client.registered())
         };
+        
         if client_presence.is_some() {
             let camera = self.scene.camera_mut();
 
@@ -1174,6 +1175,7 @@ impl PlayState for SessionState {
                     HudEvent::Logout => {
                         self.client.borrow_mut().logout();
                         global_state.audio.stop_ambient_sounds();
+                        global_state.audio.stop_all_sfx();
                         return PlayStateResult::Pop;
                     },
                     HudEvent::Quit => {
@@ -1575,11 +1577,12 @@ impl PlayState for SessionState {
                         &scene_data,
                         &client,
                     );
+                    
 
                     // Process outcomes from client
                     for outcome in outcomes {
                         self.scene
-                            .handle_outcome(&outcome, &scene_data, &mut global_state.audio);
+                            .handle_outcome(&outcome, &scene_data, &mut global_state.audio, &client.state(), cam_pos);
                         self.hud
                             .handle_outcome(&outcome, scene_data.client, global_state);
                     }
diff --git a/voxygen/src/settings/audio.rs b/voxygen/src/settings/audio.rs
index 7c2603fb4e..2536b914e9 100644
--- a/voxygen/src/settings/audio.rs
+++ b/voxygen/src/settings/audio.rs
@@ -26,6 +26,7 @@ pub struct AudioSettings {
     pub music_volume: f32,
     pub sfx_volume: f32,
     pub num_sfx_channels: usize,
+    pub num_ui_channels: usize,
 
     /// Audio Device that Voxygen will use to play audio.
     pub output: AudioOutput,
@@ -36,9 +37,10 @@ impl Default for AudioSettings {
         Self {
             master_volume: 1.0,
             inactive_master_volume_perc: 0.5,
-            music_volume: 0.4,
+            music_volume: 0.3,
             sfx_volume: 0.6,
             num_sfx_channels: 60,
+            num_ui_channels: 10,
             output: AudioOutput::Automatic,
         }
     }

From 16ca1410bea0126ee0a8d587821f0aca82966427 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Fri, 18 Mar 2022 12:49:28 -0700
Subject: [PATCH 23/71] Remove extraneous sfx code

---
 assets/voxygen/audio/sfx.ron  | 23 +----------------------
 common/src/outcome.rs         |  6 +-----
 voxygen/src/audio/sfx/mod.rs  |  5 -----
 voxygen/src/scene/particle.rs |  3 +--
 4 files changed, 3 insertions(+), 34 deletions(-)

diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron
index 4e89f79b85..8ea7093d23 100644
--- a/assets/voxygen/audio/sfx.ron
+++ b/assets/voxygen/audio/sfx.ron
@@ -239,27 +239,6 @@
             ],
             threshold: 0.8,
         ),
-        //ExperienceGained: (
-        //    files: [
-        //        "voxygen.audio.sfx.character.experience_gained_1",
-        //        "voxygen.audio.sfx.character.experience_gained_2",
-        //        "voxygen.audio.sfx.character.experience_gained_3",
-        //    ],
-        //    threshold: 0.5,
-        //),
-        // unused for now
-        // Jump: (
-        //    files: [
-        //        "voxygen.audio.sfx.utterance.humanmale_hurt1"
-        //    ],
-        //    threshold: 0.25,
-        // ),
-        //Fall: (
-        //    files: [
-        //        // Event not implemented?
-        //    ],
-        //    threshold: 0.25,
-        //),
         Roll: (
             files: [
                 "voxygen.audio.sfx.character.dive_roll_1",
@@ -269,7 +248,7 @@
         ),
         Climb: (
            files: [
-                // TODO: sync with animation
+                // TODO: sync with animation, make actual sfx
                 "voxygen.audio.sfx.footsteps.stepdirt_1",
                 "voxygen.audio.sfx.footsteps.stepdirt_2",
                 "voxygen.audio.sfx.footsteps.stepdirt_3",
diff --git a/common/src/outcome.rs b/common/src/outcome.rs
index 85b5ae95fe..7a7c3cc734 100644
--- a/common/src/outcome.rs
+++ b/common/src/outcome.rs
@@ -93,9 +93,6 @@ pub enum Outcome {
         pos: Vec3<f32>,
         wielded: bool,
     },
-    Jump {
-        pos: Vec3<f32>,
-    }
 }
 
 impl Outcome {
@@ -112,8 +109,7 @@ impl Outcome {
             | Outcome::PoiseChange { pos, .. }
             | Outcome::GroundSlam { pos }
             | Outcome::Utterance { pos, .. }
-            | Outcome::Glider { pos, .. }
-            | Outcome::Jump { pos, .. } => Some(*pos),
+            | Outcome::Glider { pos, .. } => Some(*pos),
             Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
             Outcome::ExpChange { .. } | Outcome::ComboChange { .. } | Outcome::SkillPointGain { .. } => None,
         }
diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs
index f85a4e9f77..6bc5818270 100644
--- a/voxygen/src/audio/sfx/mod.rs
+++ b/voxygen/src/audio/sfx/mod.rs
@@ -590,11 +590,6 @@ impl SfxMgr {
                     audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater);
                 }
             },
-            // unused for now
-            Outcome::Jump { pos } => {
-                let sfx_trigger_item = triggers.get_key_value(&SfxEvent::Jump);
-                audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), underwater)
-            },
             Outcome::ExpChange { .. }
             | Outcome::ComboChange { .. }
             | Outcome::SummonedCreature { .. } => {},
diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs
index d155ee50d2..5f9c424d39 100644
--- a/voxygen/src/scene/particle.rs
+++ b/voxygen/src/scene/particle.rs
@@ -287,8 +287,7 @@ impl ParticleMgr {
             | Outcome::HealthChange { .. }
             | Outcome::PoiseChange { .. }
             | Outcome::Utterance { .. }
-            | Outcome::Glider { .. }
-            | Outcome::Jump { .. } => {},
+            | Outcome::Glider { .. } => {},
         }
     }
 

From aafd13508dfaeff6bc63fec9218b0bbac3e1f0d6 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Sat, 19 Mar 2022 02:14:54 -0700
Subject: [PATCH 24/71] Completely redoing ambient sound again.

---
 common/src/outcome.rs        |   4 +-
 voxygen/src/audio/ambient.rs | 329 ++++++++++++++++-------------------
 voxygen/src/audio/channel.rs |  31 +++-
 voxygen/src/audio/mod.rs     |  72 ++++----
 voxygen/src/audio/sfx/mod.rs |   2 +-
 voxygen/src/main.rs          |   5 +-
 voxygen/src/scene/mod.rs     |  35 ++--
 voxygen/src/session/mod.rs   |  16 +-
 8 files changed, 249 insertions(+), 245 deletions(-)

diff --git a/common/src/outcome.rs b/common/src/outcome.rs
index 7a7c3cc734..69ef8fb3da 100644
--- a/common/src/outcome.rs
+++ b/common/src/outcome.rs
@@ -111,7 +111,9 @@ impl Outcome {
             | Outcome::Utterance { pos, .. }
             | Outcome::Glider { pos, .. } => Some(*pos),
             Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
-            Outcome::ExpChange { .. } | Outcome::ComboChange { .. } | Outcome::SkillPointGain { .. } => None,
+            Outcome::ExpChange { .. }
+            | Outcome::ComboChange { .. }
+            | Outcome::SkillPointGain { .. } => None,
         }
     }
 }
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index d0c89e8c0d..bd3da0e618 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -1,6 +1,9 @@
 //! Handles ambient non-positional sounds
 use crate::{
-    audio::{channel::AmbientChannelTag, AudioFrontend},
+    audio::{
+        channel::{AmbientChannel, AmbientChannelTag},
+        AudioFrontend,
+    },
     scene::Camera,
 };
 use client::Client;
@@ -10,12 +13,13 @@ use common::{
 };
 use common_state::State;
 use serde::Deserialize;
+use strum::IntoEnumIterator;
 use std::time::Instant;
 use tracing::warn;
 use vek::*;
 
 #[derive(Debug, Default, Deserialize)]
-struct AmbientCollection {
+pub struct AmbientCollection {
     tracks: Vec<AmbientItem>,
 }
 
@@ -29,49 +33,11 @@ pub struct AmbientItem {
     tag: AmbientChannelTag,
 }
 
-pub struct AmbientWindMgr {
-    ambience: AssetHandle<AmbientCollection>,
-    began_playing: Instant,
-    next_track_change: f32,
-    volume: f32,
-    tree_multiplier: f32,
+pub struct AmbientMgr {
+    pub ambience: AssetHandle<AmbientCollection>,
 }
 
-pub struct AmbientRainMgr {
-    ambience: AssetHandle<AmbientCollection>,
-    began_playing: Instant,
-    next_track_change: f32,
-    volume: f32,
-    rain_intensity: f32,
-}
-
-impl Default for AmbientWindMgr {
-    fn default() -> Self {
-        Self {
-            ambience: load_ambience_items(),
-            began_playing: Instant::now(),
-            next_track_change: 0.0,
-            volume: 0.0,
-            tree_multiplier: 0.0,
-        }
-    }
-}
-
-impl Default for AmbientRainMgr {
-    fn default() -> Self {
-        Self {
-            ambience: load_ambience_items(),
-            began_playing: Instant::now(),
-            next_track_change: 0.0,
-            volume: 0.0,
-            rain_intensity: 0.0,
-        }
-    }
-}
-
-impl AmbientWindMgr {
-    /// Checks whether the previous track has completed. If so, sends a
-    /// request to play the next (random) track
+impl AmbientMgr {
     pub fn maintain(
         &mut self,
         audio: &mut AudioFrontend,
@@ -79,157 +45,156 @@ impl AmbientWindMgr {
         client: &Client,
         camera: &Camera,
     ) {
-        if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() {
-            let focus_off = camera.get_focus_pos().map(f32::trunc);
-            let cam_pos = camera.dependents().cam_pos + focus_off;
-
-            let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-                (chunk.meta().alt(), chunk.meta().tree_density())
-            } else {
-                (0.0, 0.0)
-            };
-
-            // The following code is specifically for wind, as it is the only
-            // non-positional ambient sound in the game. Others can be added
-            // as seen fit.
-
-            let target_volume = {
-                // Wind volume increases with altitude
-                let alt_multiplier = (cam_pos.z / 1200.0).abs();
-
-                // Tree density factors into wind volume. The more trees,
-                // the lower wind volume. The trees make more of an impact
-                // the closer the camera is to the ground.
-                self.tree_multiplier = ((1.0 - tree_density)
-                    + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
-                .min(1.0);
-
-                let mut volume_multiplier = alt_multiplier * self.tree_multiplier;
-
-                // Checks if the camera is underwater to stop ambient sounds
-                if state
-                    .terrain()
-                    .get((cam_pos).map(|e| e.floor() as i32))
-                    .map(|b| b.is_liquid())
-                    .unwrap_or(false)
-                {
-                    volume_multiplier *= 0.1;
-                }
-                // Is the camera roughly under the terrain?
-                if cam_pos.z < terrain_alt - 10.0 {
-                    volume_multiplier = 0.0;
+        let sfx_volume = audio.get_sfx_volume();
+        // iterate through each tag
+        for tag in AmbientChannelTag::iter() {
+            // iterate through the supposed number of channels - one for each tag
+            for index in 0..AmbientChannelTag::iter().len() {
+                // if index would exceed current number of channels, create a new one with
+                // current tag
+                if index >= audio.ambient_channels.len() {
+                    audio.new_ambient_channel(tag);
                 }
+                // update with sfx volume
+                audio.ambient_channels[index].set_volume(sfx_volume);
+                // if current channel's tag is not the current tag, move on to next channel
+                if audio.ambient_channels[index].get_tag() == tag {
+                    // maintain: get the correct multiplier of whatever the tag of the current
+                    // channel is
+                    let target_volume =
+                        audio.ambient_channels[index].maintain(state, client, camera);
+                    // get multiplier of the current channel
+                    let initial_volume = audio.ambient_channels[index].get_multiplier();
 
-                volume_multiplier.clamped(0.0, 1.0)
-            };
+                    // lerp multiplier of current channel
+                    audio.ambient_channels[index].set_multiplier(Lerp::lerp(
+                        initial_volume,
+                        target_volume,
+                        0.01,
+                    ));
 
-            // Transitions the ambient sounds (more) smoothly
-            if audio.get_ambient_channel(AmbientChannelTag::Wind).is_none() {
-                audio.new_ambient_channel(AmbientChannelTag::Wind);
-            } else {
-                self.volume = audio.get_ambient_volume(AmbientChannelTag::Wind);
-                audio.set_ambient_volume(
-                    AmbientChannelTag::Wind,
-                    Lerp::lerp(self.volume, target_volume, 0.01),
-                );
-            }
+                    // set the duration of the loop to whatever the current value is (0.0 by
+                    // default)
+                    let next_track_change = audio.ambient_channels[index].get_next_track_change();
 
-            if self.began_playing.elapsed().as_secs_f32() > self.next_track_change {
-                let ambience = self.ambience.read();
-                let wind_track = &ambience
-                    .tracks
-                    .iter()
-                    .find(|track| track.tag == AmbientChannelTag::Wind);
-                self.began_playing = Instant::now();
-                if let Some(wind_track) = wind_track {
-                    self.next_track_change = wind_track.length;
-                    audio.play_ambient(AmbientChannelTag::Wind, &wind_track.path, target_volume);
-                }
-            }
-        }
-    }
-}
+                    // if the sound should loop at this point:
+                    if audio.ambient_channels[index]
+                        .get_began_playing()
+                        .elapsed()
+                        .as_secs_f32()
+                        > next_track_change
+                    {
+                        let ambience = self.ambience.read();
+                        let track = ambience.tracks.iter().find(|track| track.tag == tag);
+                        // set the track's start point at this instant
+                        audio.ambient_channels[index].set_began_playing(Instant::now());
+                        if let Some(track) = track {
+                            // set loop duration to the one specified in the ron
+                            audio.ambient_channels[index].set_next_track_change(track.length);
+                            // play the file of the current tag at the current multiplier
+                            let current_multiplier = audio.ambient_channels[index].get_multiplier();
+                            audio.play_ambient(tag, &track.path, current_multiplier);
+                        }
+                    };
 
-impl AmbientRainMgr {
-    /// Checks whether the previous track has completed. If so, sends a
-    /// request to play the next (random) track
-    pub fn maintain(
-        &mut self,
-        audio: &mut AudioFrontend,
-        state: &State,
-        client: &Client,
-        camera: &Camera,
-    ) {
-        if audio.sfx_enabled() && !self.ambience.read().tracks.is_empty() {
-            let focus_off = camera.get_focus_pos().map(f32::trunc);
-            let cam_pos = camera.dependents().cam_pos + focus_off;
-
-            let terrain_alt = if let Some(chunk) = client.current_chunk() {
-                chunk.meta().alt()
-            } else {
-                0.0
-            };
-
-            // multipler at end will have to change depending on how intense rain normally
-            // is
-            self.rain_intensity = client.current_weather().rain * 5.0;
-
-            let mut volume_multiplier = self.rain_intensity;
-
-            // TODO: make rain diminish with distance above terrain
-            let target_volume = {
-                // Checks if the camera is underwater to stop ambient sounds
-                if state
-                    .terrain()
-                    .get((cam_pos).map(|e| e.floor() as i32))
-                    .map(|b| b.is_liquid())
-                    .unwrap_or(false)
-                {
-                    volume_multiplier *= 0.1;
-                }
-                // Is the camera roughly under the terrain?
-                if cam_pos.z < terrain_alt - 10.0 {
-                    volume_multiplier = 0.0;
-                }
-
-                volume_multiplier = volume_multiplier.clamped(0.0, 1.0);
-
-                // possibly remove noise
-                if volume_multiplier < 0.05 {
-                    0.0
+                    // remove channel if not playing
+                    if audio.ambient_channels[index].get_multiplier() == 0.0 {
+                        audio.ambient_channels[index].stop();
+                        audio.ambient_channels.remove(index);
+                    };
+                    // move on to next tag
+                    break;
                 } else {
-                    volume_multiplier
-                }
-            };
-
-            // Transitions the ambient sounds (more) smoothly
-            if audio.get_ambient_channel(AmbientChannelTag::Rain).is_none() {
-                audio.new_ambient_channel(AmbientChannelTag::Rain);
-            } else {
-                self.volume = audio.get_ambient_volume(AmbientChannelTag::Rain);
-                audio.set_ambient_volume(
-                    AmbientChannelTag::Rain,
-                    Lerp::lerp(self.volume, target_volume, 0.01),
-                );
-            }
-
-            if self.began_playing.elapsed().as_secs_f32() > self.next_track_change {
-                let ambience = self.ambience.read();
-                let rain_track = &ambience
-                    .tracks
-                    .iter()
-                    .find(|track| track.tag == AmbientChannelTag::Rain);
-                self.began_playing = Instant::now();
-                if let Some(rain_track) = rain_track {
-                    self.next_track_change = rain_track.length;
-                    audio.play_ambient(AmbientChannelTag::Rain, &rain_track.path, target_volume);
+                    // channel tag and current tag don't match, move on to next channel
+                    continue;
                 }
             }
         }
     }
 }
 
-fn load_ambience_items() -> AssetHandle<AmbientCollection> {
+impl AmbientChannel {
+    pub fn maintain(&mut self, state: &State, client: &Client, camera: &Camera) -> f32 {
+        let tag = self.get_tag();
+
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let mut target_volume: f32 = match tag {
+            // Get target volume of wind
+            AmbientChannelTag::Wind => self.get_wind_volume(client, camera),
+            // get target volume of rain
+            AmbientChannelTag::Rain => self.get_rain_volume(client),
+        };
+
+        // TODO: make rain diminish with distance above terrain
+        target_volume = self.check_camera(state, client, cam_pos, target_volume);
+
+        return target_volume;
+    }
+
+    fn check_camera(
+        &mut self,
+        state: &State,
+        client: &Client,
+        cam_pos: Vec3<f32>,
+        initial_volume: f32,
+    ) -> f32 {
+        let mut volume_multiplier = initial_volume;
+        let terrain_alt = if let Some(chunk) = client.current_chunk() {
+            chunk.meta().alt()
+        } else {
+            0.0
+        };
+        // Checks if the camera is underwater to stop ambient sounds
+        if state
+            .terrain()
+            .get((cam_pos).map(|e| e.floor() as i32))
+            .map(|b| b.is_liquid())
+            .unwrap_or(false)
+        {
+            volume_multiplier *= 0.1;
+        }
+        // Is the camera roughly under the terrain?
+        if cam_pos.z < terrain_alt - 10.0 {
+            volume_multiplier = 0.0;
+        }
+
+        volume_multiplier.clamped(0.0, 1.0)
+    }
+
+    fn get_wind_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+            (chunk.meta().alt(), chunk.meta().tree_density())
+        } else {
+            (0.0, 0.0)
+        };
+
+        // Wind volume increases with altitude
+        let alt_multiplier = (cam_pos.z / 1200.0).abs();
+
+        // Tree density factors into wind volume. The more trees,
+        // the lower wind volume. The trees make more of an impact
+        // the closer the camera is to the ground.
+        let tree_multiplier =
+            ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
+
+        return alt_multiplier * tree_multiplier;
+    }
+
+    fn get_rain_volume(&mut self, client: &Client) -> f32 {
+        // multipler at end will have to change depending on how intense rain normally
+        // is
+        let rain_intensity = client.current_weather().rain * 5.0;
+
+        return rain_intensity;
+    }
+}
+
+pub fn load_ambience_items() -> AssetHandle<AmbientCollection> {
     AmbientCollection::load_or_insert_with("voxygen.audio.ambient", |error| {
         warn!(
             "Error reading ambience config file, ambience will not be available: {:#?}",
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index ab34564933..62ff086c04 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -22,6 +22,8 @@ use crate::audio::{
 };
 use rodio::{OutputStreamHandle, Sample, Sink, Source, SpatialSink};
 use serde::Deserialize;
+use strum::EnumIter;
+use std::time::Instant;
 use tracing::warn;
 use vek::*;
 
@@ -157,7 +159,7 @@ impl MusicChannel {
 
 /// AmbientChannelTags are used for non-positional sfx. Currently the only use
 /// is for wind.
-#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
+#[derive(Debug, PartialEq, Clone, Copy, Deserialize, EnumIter)]
 pub enum AmbientChannelTag {
     Wind,
     Rain,
@@ -168,6 +170,8 @@ pub struct AmbientChannel {
     tag: AmbientChannelTag,
     multiplier: f32,
     sink: Sink,
+    began_playing: Instant,
+    next_track_change: f32,
 }
 
 impl AmbientChannel {
@@ -178,6 +182,8 @@ impl AmbientChannel {
                 tag,
                 multiplier,
                 sink,
+                began_playing: Instant::now(),
+                next_track_change: 0.0,
             },
             Err(_) => {
                 warn!("Failed to create rodio sink. May not play ambient sounds.");
@@ -185,6 +191,8 @@ impl AmbientChannel {
                     tag,
                     multiplier,
                     sink: Sink::new_idle().0,
+                    began_playing: Instant::now(),
+                    next_track_change: 0.0,
                 }
             },
         }
@@ -211,6 +219,20 @@ impl AmbientChannel {
     pub fn get_multiplier(&mut self) -> f32 { self.multiplier }
 
     pub fn get_tag(&self) -> AmbientChannelTag { self.tag }
+
+    pub fn set_tag(&mut self, tag: AmbientChannelTag) { self.tag = tag }
+
+    pub fn get_began_playing(&self) -> Instant { self.began_playing }
+
+    pub fn get_next_track_change(&self) -> f32 { self.next_track_change }
+
+    pub fn set_began_playing(&mut self, began_playing: Instant) {
+        self.began_playing = began_playing
+    }
+
+    pub fn set_next_track_change(&mut self, next_track_change: f32) {
+        self.next_track_change = next_track_change
+    }
 }
 
 /// An SfxChannel uses a positional audio sink, and is designed for short-lived
@@ -264,9 +286,8 @@ impl SfxChannel {
     pub fn update(&mut self, listener: &Listener) {
         const FALLOFF: f32 = 0.13;
 
-        self.sink.set_emitter_position(
-            ((self.pos - listener.pos) * FALLOFF).into_array(),
-        );
+        self.sink
+            .set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array());
         self.sink
             .set_left_ear_position(listener.ear_left_rpos.into_array());
         self.sink
@@ -281,7 +302,7 @@ pub struct UIChannel {
 impl UIChannel {
     pub fn new(stream: &OutputStreamHandle) -> Self {
         Self {
-            sink: Sink::try_new(stream).unwrap()
+            sink: Sink::try_new(stream).unwrap(),
         }
     }
 
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 9656f0c4d7..3b2942c71e 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -7,7 +7,9 @@ pub mod music;
 pub mod sfx;
 pub mod soundcache;
 
-use channel::{AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel};
+use channel::{
+    AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel,
+};
 use fader::Fader;
 use music::MusicTransitionManifest;
 use sfx::{SfxEvent, SfxTriggerItem};
@@ -38,16 +40,16 @@ pub struct AudioFrontend {
     //pub device_list: Vec<String>,
     //pub audio_device: Option<Device>,
     pub stream: Option<rodio::OutputStream>,
-    audio_stream: Option<rodio::OutputStreamHandle>,
+    pub audio_stream: Option<rodio::OutputStreamHandle>,
 
-    music_channels: Vec<MusicChannel>,
-    ambient_channels: Vec<AmbientChannel>,
-    sfx_channels: Vec<SfxChannel>,
-    ui_channels: Vec<UIChannel>,
-    sfx_volume: f32,
-    music_volume: f32,
-    master_volume: f32,
-    listener: Listener,
+    pub music_channels: Vec<MusicChannel>,
+    pub ambient_channels: Vec<AmbientChannel>,
+    pub sfx_channels: Vec<SfxChannel>,
+    pub ui_channels: Vec<UIChannel>,
+    pub sfx_volume: f32,
+    pub music_volume: f32,
+    pub master_volume: f32,
+    pub listener: Listener,
 
     mtm: AssetHandle<MusicTransitionManifest>,
 }
@@ -216,7 +218,11 @@ impl AudioFrontend {
 
     /// Function to play sfx from external places. Useful for UI and
     /// inventory events
-    pub fn emit_sfx_item(&mut self, trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>, vol: Option<f32>) {
+    pub fn emit_sfx_item(
+        &mut self,
+        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
+        vol: Option<f32>,
+    ) {
         if let Some((event, item)) = trigger_item {
             let sfx_file = match item.files.len() {
                 0 => {
@@ -302,7 +308,7 @@ impl AudioFrontend {
                 } else {
                     channel.play(sound);
                 }
-            } 
+            }
         }
         Ok(())
     }
@@ -371,29 +377,29 @@ impl AudioFrontend {
     }
 
     // sets the volume of the channel with the given tag to the given volume
-    fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag, volume_multiplier: f32) {
-        if self.audio_stream.is_some() {
-            let sfx_volume = self.get_sfx_volume();
-            if let Some(channel) = self.get_ambient_channel(channel_tag) {
-                channel.set_multiplier(volume_multiplier);
-                channel.set_volume(sfx_volume);
-            }
-        }
-    }
+    // fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag,
+    // volume_multiplier: f32) {     if self.audio_stream.is_some() {
+    //         let sfx_volume = self.get_sfx_volume();
+    //         if let Some(channel) = self.get_ambient_channel(channel_tag) {
+    //             channel.set_multiplier(volume_multiplier);
+    //             channel.set_volume(sfx_volume);
+    //         }
+    //     }
+    // }
 
     // retrieves volume (pre-sfx-setting) of the channel with a given tag
-    fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 {
-        if self.audio_stream.is_some() {
-            if let Some(channel) = self.get_ambient_channel(channel_tag) {
-                let channel_multiplier = channel.get_multiplier();
-                channel_multiplier
-            } else {
-                0.0
-            }
-        } else {
-            0.0
-        }
-    }
+    // fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 {
+    //     if self.audio_stream.is_some() {
+    //         if let Some(channel) = self.get_ambient_channel(channel_tag) {
+    //             let channel_multiplier = channel.get_multiplier();
+    //             channel_multiplier
+    //         } else {
+    //             0.0
+    //         }
+    //     } else {
+    //         0.0
+    //     }
+    // }
 
     fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag) {
         if self.music_enabled() {
diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs
index 6bc5818270..d33eb68263 100644
--- a/voxygen/src/audio/sfx/mod.rs
+++ b/voxygen/src/audio/sfx/mod.rs
@@ -82,7 +82,7 @@
 
 mod event_mapper;
 
-use specs::{WorldExt};
+use specs::WorldExt;
 
 use crate::{
     audio::AudioFrontend,
diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs
index 4a995f808b..ff811bd9dc 100644
--- a/voxygen/src/main.rs
+++ b/voxygen/src/main.rs
@@ -223,7 +223,10 @@ fn main() {
     // Setup audio
     let mut audio = match settings.audio.output {
         AudioOutput::Off => AudioFrontend::no_audio(),
-        AudioOutput::Automatic => AudioFrontend::new(settings.audio.num_sfx_channels, settings.audio.num_ui_channels),
+        AudioOutput::Automatic => AudioFrontend::new(
+            settings.audio.num_sfx_channels,
+            settings.audio.num_ui_channels,
+        ),
         //    AudioOutput::Device(ref dev) => Some(dev.clone()),
     };
 
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index ece128be16..a27d27ed8d 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -19,12 +19,7 @@ pub use self::{
     trail::TrailMgr,
 };
 use crate::{
-    audio::{
-        ambient::{AmbientRainMgr, AmbientWindMgr},
-        music::MusicMgr,
-        sfx::SfxMgr,
-        AudioFrontend,
-    },
+    audio::{ambient, ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
     render::{
         create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup,
         Light, Model, PointLightMatrix, PostProcessLocals, Renderer, Shadow, ShadowLocals,
@@ -107,8 +102,9 @@ pub struct Scene {
     figure_mgr: FigureMgr,
     pub sfx_mgr: SfxMgr,
     music_mgr: MusicMgr,
-    ambient_wind_mgr: AmbientWindMgr,
-    ambient_rain_mgr: AmbientRainMgr,
+    ambient_mgr: AmbientMgr,
+    // ambient_wind_mgr: AmbientWindMgr,
+    // ambient_rain_mgr: AmbientRainMgr,
 }
 
 pub struct SceneData<'a> {
@@ -320,8 +316,11 @@ impl Scene {
             figure_mgr: FigureMgr::new(renderer),
             sfx_mgr: SfxMgr::default(),
             music_mgr: MusicMgr::default(),
-            ambient_wind_mgr: AmbientWindMgr::default(),
-            ambient_rain_mgr: AmbientRainMgr::default(),
+            ambient_mgr: AmbientMgr {
+                ambience: ambient::load_ambience_items(),
+            },
+            // ambient_wind_mgr: AmbientWindMgr::default(),
+            // ambient_rain_mgr: AmbientRainMgr::default(),
         }
     }
 
@@ -414,10 +413,10 @@ impl Scene {
     ) {
         span!(_guard, "handle_outcome", "Scene::handle_outcome");
         let underwater = state
-                        .terrain()
-                        .get(cam_pos.map(|e| e.floor() as i32))
-                        .map(|b| b.is_liquid())
-                        .unwrap_or(false); 
+            .terrain()
+            .get(cam_pos.map(|e| e.floor() as i32))
+            .map(|b| b.is_liquid())
+            .unwrap_or(false);
         self.particle_mgr.handle_outcome(outcome, scene_data);
         self.sfx_mgr
             .handle_outcome(outcome, audio, scene_data.client, state, underwater);
@@ -1080,10 +1079,12 @@ impl Scene {
             client,
         );
         self.music_mgr.maintain(audio, scene_data.state, client);
-        self.ambient_wind_mgr
-            .maintain(audio, scene_data.state, client, &self.camera);
-        self.ambient_rain_mgr
+        self.ambient_mgr
             .maintain(audio, scene_data.state, client, &self.camera);
+        // self.ambient_wind_mgr
+        //     .maintain(audio, scene_data.state, client, &self.camera);
+        // self.ambient_rain_mgr
+        //     .maintain(audio, scene_data.state, client, &self.camera);
     }
 
     pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index d976d03756..81e06f55c6 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -245,7 +245,9 @@ impl SessionState {
                     let sfx_triggers = self.scene.sfx_mgr.triggers.read();
 
                     let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
-                    global_state.audio.emit_sfx_item(sfx_trigger_item, Some(1.0));
+                    global_state
+                        .audio
+                        .emit_sfx_item(sfx_trigger_item, Some(1.0));
 
                     match inv_event {
                         InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
@@ -358,7 +360,7 @@ impl PlayState for SessionState {
             let client = self.client.borrow();
             (client.presence(), client.registered())
         };
-        
+
         if client_presence.is_some() {
             let camera = self.scene.camera_mut();
 
@@ -1577,12 +1579,16 @@ impl PlayState for SessionState {
                         &scene_data,
                         &client,
                     );
-                    
 
                     // Process outcomes from client
                     for outcome in outcomes {
-                        self.scene
-                            .handle_outcome(&outcome, &scene_data, &mut global_state.audio, &client.state(), cam_pos);
+                        self.scene.handle_outcome(
+                            &outcome,
+                            &scene_data,
+                            &mut global_state.audio,
+                            &client.state(),
+                            cam_pos,
+                        );
                         self.hud
                             .handle_outcome(&outcome, scene_data.client, global_state);
                     }

From c4239e074fa6aa146c4e95cec7e37a717a17153f Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Sat, 19 Mar 2022 02:37:45 -0700
Subject: [PATCH 25/71] Prevent unnecessary running of code

---
 voxygen/src/audio/ambient.rs | 156 ++++++++++++++++++++++-------------
 voxygen/src/scene/mod.rs     |  28 ++++---
 2 files changed, 114 insertions(+), 70 deletions(-)

diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index bd3da0e618..796d285e96 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -48,69 +48,107 @@ impl AmbientMgr {
         let sfx_volume = audio.get_sfx_volume();
         // iterate through each tag
         for tag in AmbientChannelTag::iter() {
-            // iterate through the supposed number of channels - one for each tag
-            for index in 0..AmbientChannelTag::iter().len() {
-                // if index would exceed current number of channels, create a new one with
-                // current tag
-                if index >= audio.ambient_channels.len() {
-                    audio.new_ambient_channel(tag);
-                }
-                // update with sfx volume
-                audio.ambient_channels[index].set_volume(sfx_volume);
-                // if current channel's tag is not the current tag, move on to next channel
-                if audio.ambient_channels[index].get_tag() == tag {
-                    // maintain: get the correct multiplier of whatever the tag of the current
-                    // channel is
-                    let target_volume =
-                        audio.ambient_channels[index].maintain(state, client, camera);
-                    // get multiplier of the current channel
-                    let initial_volume = audio.ambient_channels[index].get_multiplier();
-
-                    // lerp multiplier of current channel
-                    audio.ambient_channels[index].set_multiplier(Lerp::lerp(
-                        initial_volume,
-                        target_volume,
-                        0.01,
-                    ));
-
-                    // set the duration of the loop to whatever the current value is (0.0 by
-                    // default)
-                    let next_track_change = audio.ambient_channels[index].get_next_track_change();
-
-                    // if the sound should loop at this point:
-                    if audio.ambient_channels[index]
-                        .get_began_playing()
-                        .elapsed()
-                        .as_secs_f32()
-                        > next_track_change
-                    {
-                        let ambience = self.ambience.read();
-                        let track = ambience.tracks.iter().find(|track| track.tag == tag);
-                        // set the track's start point at this instant
-                        audio.ambient_channels[index].set_began_playing(Instant::now());
-                        if let Some(track) = track {
-                            // set loop duration to the one specified in the ron
-                            audio.ambient_channels[index].set_next_track_change(track.length);
-                            // play the file of the current tag at the current multiplier
-                            let current_multiplier = audio.ambient_channels[index].get_multiplier();
-                            audio.play_ambient(tag, &track.path, current_multiplier);
-                        }
-                    };
-
-                    // remove channel if not playing
-                    if audio.ambient_channels[index].get_multiplier() == 0.0 {
-                        audio.ambient_channels[index].stop();
-                        audio.ambient_channels.remove(index);
-                    };
-                    // move on to next tag
-                    break;
-                } else {
-                    // channel tag and current tag don't match, move on to next channel
-                    continue;
+            // check if current conditions necessitate the current tag at all
+            let should_create: bool = match tag {
+                AmbientChannelTag::Wind => self.check_wind_necessity(client, camera),
+                AmbientChannelTag::Rain => self.check_rain_necessity(client),
+            };
+            if should_create {
+                // iterate through the supposed number of channels - one for each tag
+                for index in 0..AmbientChannelTag::iter().len() {
+                    // if index would exceed current number of channels, create a new one with
+                    // current tag
+                    if index >= audio.ambient_channels.len() {
+                        audio.new_ambient_channel(tag);
+                    }
+                    // update with sfx volume
+                    audio.ambient_channels[index].set_volume(sfx_volume);
+                    // if current channel's tag is not the current tag, move on to next channel
+                    if audio.ambient_channels[index].get_tag() == tag {
+                        // maintain: get the correct multiplier of whatever the tag of the current
+                        // channel is
+                        let target_volume =
+                            audio.ambient_channels[index].maintain(state, client, camera);
+                        // get multiplier of the current channel
+                        let initial_volume = audio.ambient_channels[index].get_multiplier();
+
+                        // lerp multiplier of current channel
+                        audio.ambient_channels[index].set_multiplier(Lerp::lerp(
+                            initial_volume,
+                            target_volume,
+                            0.01,
+                        ));
+
+                        // set the duration of the loop to whatever the current value is (0.0 by
+                        // default)
+                        let next_track_change =
+                            audio.ambient_channels[index].get_next_track_change();
+
+                        // if the sound should loop at this point:
+                        if audio.ambient_channels[index]
+                            .get_began_playing()
+                            .elapsed()
+                            .as_secs_f32()
+                            > next_track_change
+                        {
+                            let ambience = self.ambience.read();
+                            let track = ambience.tracks.iter().find(|track| track.tag == tag);
+                            // set the track's start point at this instant
+                            audio.ambient_channels[index].set_began_playing(Instant::now());
+                            if let Some(track) = track {
+                                // set loop duration to the one specified in the ron
+                                audio.ambient_channels[index].set_next_track_change(track.length);
+                                // play the file of the current tag at the current multiplier
+                                let current_multiplier =
+                                    audio.ambient_channels[index].get_multiplier();
+                                audio.play_ambient(tag, &track.path, current_multiplier);
+                            }
+                        };
+
+                        // remove channel if not playing
+                        if audio.ambient_channels[index].get_multiplier() == 0.0 {
+                            audio.ambient_channels[index].stop();
+                            audio.ambient_channels.remove(index);
+                        };
+                        // move on to next tag
+                        break;
+                    } else {
+                        // channel tag and current tag don't match, move on to next channel
+                        continue;
+                    }
                 }
+            } else {
+                // no need to run code at all, move on to the next tag
+                continue;
             }
         }
     }
+
+    fn check_wind_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+            (chunk.meta().alt(), chunk.meta().tree_density())
+        } else {
+            (0.0, 0.0)
+        };
+
+        // Wind volume increases with altitude
+        let alt_multiplier = (cam_pos.z / 1200.0).abs();
+
+        // Tree density factors into wind volume. The more trees,
+        // the lower wind volume. The trees make more of an impact
+        // the closer the camera is to the ground.
+        let tree_multiplier =
+            ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
+
+        return alt_multiplier * tree_multiplier > 0.0;
+    }
+
+    fn check_rain_necessity(&mut self, client: &Client) -> bool {
+        client.current_weather().rain * 5.0 > 0.0
+    }
 }
 
 impl AmbientChannel {
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index a27d27ed8d..f22dcfcb88 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1070,17 +1070,23 @@ impl Scene {
         self.figure_mgr.clean(scene_data.tick);
 
         // Maintain audio
-        self.sfx_mgr.maintain(
-            audio,
-            scene_data.state,
-            scene_data.player_entity,
-            &self.camera,
-            &self.terrain,
-            client,
-        );
-        self.music_mgr.maintain(audio, scene_data.state, client);
-        self.ambient_mgr
-            .maintain(audio, scene_data.state, client, &self.camera);
+        if audio.sfx_enabled() {
+            self.sfx_mgr.maintain(
+                audio,
+                scene_data.state,
+                scene_data.player_entity,
+                &self.camera,
+                &self.terrain,
+                client,
+            );
+            self.ambient_mgr
+                .maintain(audio, scene_data.state, client, &self.camera);
+        }
+
+        if audio.music_enabled() {
+            self.music_mgr.maintain(audio, scene_data.state, client);
+        }
+
         // self.ambient_wind_mgr
         //     .maintain(audio, scene_data.state, client, &self.camera);
         // self.ambient_rain_mgr

From b3200ed89f2d0865794b9a39b29c194737e06f3e Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Sun, 20 Mar 2022 00:07:23 -0700
Subject: [PATCH 26/71] More ambience :)

---
 assets/voxygen/audio/ambience/leaves.ogg      |  3 +
 .../audio/{ambient => ambience}/rain.ogg      |  0
 assets/voxygen/audio/ambience/thunder.ogg     |  3 +
 .../audio/{ambient => ambience}/wind.ogg      |  0
 assets/voxygen/audio/ambient.ron              | 19 +++-
 assets/voxygen/audio/sfx/ambient/rain_sfx.ogg |  3 -
 voxygen/src/audio/ambient.rs                  | 92 +++++++++++++++++--
 voxygen/src/audio/channel.rs                  |  2 +
 8 files changed, 109 insertions(+), 13 deletions(-)
 create mode 100644 assets/voxygen/audio/ambience/leaves.ogg
 rename assets/voxygen/audio/{ambient => ambience}/rain.ogg (100%)
 create mode 100644 assets/voxygen/audio/ambience/thunder.ogg
 rename assets/voxygen/audio/{ambient => ambience}/wind.ogg (100%)
 delete mode 100644 assets/voxygen/audio/sfx/ambient/rain_sfx.ogg

diff --git a/assets/voxygen/audio/ambience/leaves.ogg b/assets/voxygen/audio/ambience/leaves.ogg
new file mode 100644
index 0000000000..5b2515be42
--- /dev/null
+++ b/assets/voxygen/audio/ambience/leaves.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6e89ea4467addefe98cd3ec9d9e8895a25900804c71212be657a61d4aa57e5f2
+size 453813
diff --git a/assets/voxygen/audio/ambient/rain.ogg b/assets/voxygen/audio/ambience/rain.ogg
similarity index 100%
rename from assets/voxygen/audio/ambient/rain.ogg
rename to assets/voxygen/audio/ambience/rain.ogg
diff --git a/assets/voxygen/audio/ambience/thunder.ogg b/assets/voxygen/audio/ambience/thunder.ogg
new file mode 100644
index 0000000000..3a214838cb
--- /dev/null
+++ b/assets/voxygen/audio/ambience/thunder.ogg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1c1d9844b1eefb7ae0d3fdeea6407ed1c2da54fc7e4b89f2d57db38e16f17d3d
+size 269156
diff --git a/assets/voxygen/audio/ambient/wind.ogg b/assets/voxygen/audio/ambience/wind.ogg
similarity index 100%
rename from assets/voxygen/audio/ambient/wind.ogg
rename to assets/voxygen/audio/ambience/wind.ogg
diff --git a/assets/voxygen/audio/ambient.ron b/assets/voxygen/audio/ambient.ron
index db088bb86c..4d02185ce8 100644
--- a/assets/voxygen/audio/ambient.ron
+++ b/assets/voxygen/audio/ambient.ron
@@ -1,13 +1,24 @@
 (
     tracks: [
         (
-            path: "voxygen.audio.ambient.wind",
+            path: "voxygen.audio.ambience.wind",
             length: 14.203,
             tag: Wind,
-        ),     (
-            path: "voxygen.audio.ambient.rain",
+        ),     
+        (
+            path: "voxygen.audio.ambience.rain",
             length: 17.0,
             tag: Rain,
-        ),           
+        ), 
+        (
+            path:"voxygen.audio.ambience.thunder",
+            length: 32.0,
+            tag: Thunder,
+        ),
+        (
+            path:"voxygen.audio.ambience.leaves",
+            length: 27.0,
+            tag: Leaves,
+        ),
     ]
 )
diff --git a/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg b/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg
deleted file mode 100644
index 61d4b62a32..0000000000
--- a/assets/voxygen/audio/sfx/ambient/rain_sfx.ogg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:add12cd225a7e4a40a7b251c4586bdb80ec2acdbc8de40f91a5f0e3395179aa2
-size 564035
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 796d285e96..e453ab5f58 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -52,15 +52,29 @@ impl AmbientMgr {
             let should_create: bool = match tag {
                 AmbientChannelTag::Wind => self.check_wind_necessity(client, camera),
                 AmbientChannelTag::Rain => self.check_rain_necessity(client),
+                AmbientChannelTag::Thunder => self.check_thunder_necessity(client),
+                AmbientChannelTag::Leaves => self.check_leaves_necessity(client, camera),
             };
-            if should_create {
+            // if the conditions warrant creating a channel of that tag
+            if should_create && audio.get_ambient_channel(tag).is_none() {
+                println!("No audio channel with this tag: {:?}", tag);
                 // iterate through the supposed number of channels - one for each tag
                 for index in 0..AmbientChannelTag::iter().len() {
+                    println!("Iter on channel index {:?}", index);
                     // if index would exceed current number of channels, create a new one with
                     // current tag
                     if index >= audio.ambient_channels.len() {
+                        println!("Creating audio channel with this tag: {:?}", tag);
                         audio.new_ambient_channel(tag);
+                        break
                     }
+                }
+                // even if the conditions don't warrant the creation of a channel
+                // with that tag, but a channel with that tag remains
+                // nonetheless, run the code
+            } else if audio.get_ambient_channel(tag).is_some() {
+                println!("Channel for {:?} is actually present, performing volume code", tag);
+                for index in 0..AmbientChannelTag::iter().len() {
                     // update with sfx volume
                     audio.ambient_channels[index].set_volume(sfx_volume);
                     // if current channel's tag is not the current tag, move on to next channel
@@ -93,7 +107,7 @@ impl AmbientMgr {
                         {
                             let ambience = self.ambience.read();
                             let track = ambience.tracks.iter().find(|track| track.tag == tag);
-                            // set the track's start point at this instant
+                            // set the channel's start point at this instant
                             audio.ambient_channels[index].set_began_playing(Instant::now());
                             if let Some(track) = track {
                                 // set loop duration to the one specified in the ron
@@ -107,6 +121,7 @@ impl AmbientMgr {
 
                         // remove channel if not playing
                         if audio.ambient_channels[index].get_multiplier() == 0.0 {
+                            println!("Removing channel {:?} with tag {:?}", index, audio.ambient_channels[index].get_tag());
                             audio.ambient_channels[index].stop();
                             audio.ambient_channels.remove(index);
                         };
@@ -147,7 +162,30 @@ impl AmbientMgr {
     }
 
     fn check_rain_necessity(&mut self, client: &Client) -> bool {
-        client.current_weather().rain * 5.0 > 0.0
+        client.current_weather().rain * 500.0 > 0.0
+    }
+
+    fn check_thunder_necessity(&mut self, client: &Client) -> bool {
+        client.current_weather().rain * 500.0 > 0.7
+    }
+
+    fn check_leaves_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+            (chunk.meta().alt(), chunk.meta().tree_density())
+        } else {
+            (0.0, 0.0)
+        };
+
+        // Tree density factors into wind volume. The more trees,
+        // the lower wind volume. The trees make more of an impact
+        // the closer the camera is to the ground.
+        let tree_multiplier =
+            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+
+        return tree_multiplier > 0.05;
     }
 }
 
@@ -161,8 +199,12 @@ impl AmbientChannel {
         let mut target_volume: f32 = match tag {
             // Get target volume of wind
             AmbientChannelTag::Wind => self.get_wind_volume(client, camera),
-            // get target volume of rain
+            // Get target volume of rain
             AmbientChannelTag::Rain => self.get_rain_volume(client),
+            // Get target volume of thunder
+            AmbientChannelTag::Thunder => self.get_thunder_volume(client),
+            // Get target volume of leaves
+            AmbientChannelTag::Leaves => self.get_leaves_volume(client, camera),
         };
 
         // TODO: make rain diminish with distance above terrain
@@ -204,6 +246,8 @@ impl AmbientChannel {
     fn get_wind_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
         let focus_off = camera.get_focus_pos().map(f32::trunc);
         let cam_pos = camera.dependents().cam_pos + focus_off;
+        // Float from around -30.0 to 30.0
+        let client_wind_speed_sq = client.current_weather().wind.magnitude_squared();
 
         let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
             (chunk.meta().alt(), chunk.meta().tree_density())
@@ -220,16 +264,52 @@ impl AmbientChannel {
         let tree_multiplier =
             ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
 
-        return alt_multiplier * tree_multiplier;
+        // Lastly, we of course have to take into account actual wind speed from weathersim
+        let wind_speed_multiplier = (client_wind_speed_sq / 30.0_f32.powi(2)).min(1.0);
+
+        return alt_multiplier * tree_multiplier * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
     }
 
     fn get_rain_volume(&mut self, client: &Client) -> f32 {
         // multipler at end will have to change depending on how intense rain normally
         // is
-        let rain_intensity = client.current_weather().rain * 5.0;
+        let rain_intensity = client.current_weather().rain * 500.0;
 
         return rain_intensity;
     }
+
+    fn get_thunder_volume(&mut self, client: &Client) -> f32 {
+        let thunder_intensity = client.current_weather().rain * 500.0;
+
+        if thunder_intensity < 0.7 {
+            0.0
+        } else {
+            thunder_intensity
+        }
+    }
+
+    fn get_leaves_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+            (chunk.meta().alt(), chunk.meta().tree_density())
+        } else {
+            (0.0, 0.0)
+        };
+
+        // Tree density factors into wind volume. The more trees,
+        // the lower wind volume. The trees make more of an impact
+        // the closer the camera is to the ground.
+        let tree_multiplier =
+            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+
+        if tree_multiplier > 0.05 {
+            tree_multiplier
+        } else {
+            0.0
+        }
+    }
 }
 
 pub fn load_ambience_items() -> AssetHandle<AmbientCollection> {
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index 62ff086c04..4a6963c9c5 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -163,6 +163,8 @@ impl MusicChannel {
 pub enum AmbientChannelTag {
     Wind,
     Rain,
+    Thunder,
+    Leaves,
 }
 /// A AmbientChannel uses a non-positional audio sink designed to play sounds
 /// which are always heard at the camera's position.

From 79cac935c8043192d4121cb56143340c82f4f8a7 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 15 Mar 2022 19:04:21 +0100
Subject: [PATCH 27/71] Added rain occlusion

---
 assets/voxygen/shaders/clouds-frag.glsl       |  65 +++--
 assets/voxygen/shaders/fluid-frag/shiny.glsl  |   2 +-
 .../shaders/include/rain_occlusion.glsl       |  36 +++
 assets/voxygen/shaders/include/sky.glsl       |   4 +-
 .../shaders/rain-occlusion-directed-vert.glsl |  61 +++++
 .../shaders/rain-occlusion-figure-vert.glsl   |  82 +++++++
 assets/voxygen/shaders/sprite-vert.glsl       |   2 +-
 assets/voxygen/shaders/terrain-frag.glsl      |   6 +-
 client/src/lib.rs                             |  12 +-
 server/src/events/interaction.rs              |   4 +-
 voxygen/src/audio/ambient.rs                  |  36 ++-
 voxygen/src/audio/channel.rs                  |   2 +-
 voxygen/src/menu/main/scene.rs                |   6 +-
 voxygen/src/render/mod.rs                     |   7 +-
 voxygen/src/render/pipelines/clouds.rs        |   6 +-
 voxygen/src/render/pipelines/mod.rs           |  48 ++++
 .../src/render/pipelines/rain_occlusion.rs    | 213 +++++++++++++++++
 voxygen/src/render/pipelines/sprite.rs        |   6 +-
 voxygen/src/render/renderer.rs                | 109 +++++++--
 voxygen/src/render/renderer/binding.rs        |  12 +
 voxygen/src/render/renderer/drawer.rs         |  77 +++++-
 .../src/render/renderer/pipeline_creation.rs  |  79 +++++-
 .../src/render/renderer/rain_occlusion_map.rs | 226 ++++++++++++++++++
 voxygen/src/render/renderer/shaders.rs        |   3 +
 voxygen/src/scene/mod.rs                      |  28 ++-
 voxygen/src/scene/simple.rs                   |   6 +-
 26 files changed, 1046 insertions(+), 92 deletions(-)
 create mode 100644 assets/voxygen/shaders/include/rain_occlusion.glsl
 create mode 100644 assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
 create mode 100644 assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
 create mode 100644 voxygen/src/render/pipelines/rain_occlusion.rs
 create mode 100644 voxygen/src/render/renderer/rain_occlusion_map.rs

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index f7ae9feaef..e6d96cf472 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -28,19 +28,19 @@
 // This *MUST* come after `cloud.glsl`: it contains a function that depends on `cloud.glsl` when clouds are enabled
 #include <point_glow.glsl>
 
-layout(set = 1, binding = 0)
+layout(set = 2, binding = 0)
 uniform texture2D t_src_color;
-layout(set = 1, binding = 1)
+layout(set = 2, binding = 1)
 uniform sampler s_src_color;
 
-layout(set = 1, binding = 2)
+layout(set = 2, binding = 2)
 uniform texture2D t_src_depth;
-layout(set = 1, binding = 3)
+layout(set = 2, binding = 3)
 uniform sampler s_src_depth;
 
 layout(location = 0) in vec2 uv;
 
-layout (std140, set = 1, binding = 4)
+layout (std140, set = 2, binding = 4)
 uniform u_locals {
     mat4 proj_mat_inv;
     mat4 view_mat_inv;
@@ -112,40 +112,37 @@ void main() {
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-        float rain_density = rain_density_at(cam_wpos.xy) * 10.0;
-        if (rain_density > 0) {
-            float rain_dist = 150.0;
-            for (int i = 0; i < 7; i ++) {
-                rain_dist *= 0.3;
+        float rain_dist = 150.0;
+        for (int i = 0; i < 7; i ++) {
+            rain_dist *= 0.3;
 
-                vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
-                float dist_to_rain = length(rpos);
-
-                if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
+            float dist_to_rain = length(rpos);
+            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
                     continue;
                 }
 
-                if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-                    break;
-                }
-
-                vec2 drop_density = vec2(30, 1);
-                vec2 drop_size = vec2(0.0008, 0.05);
-
-                vec2 rain_pos = (view_pos * rain_dist);
-                rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
-
-                vec2 cell = floor(rain_pos * drop_density) / drop_density;
-                if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
-                    continue;
-                }
-                vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
-
-                float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
-                float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
-                float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
-                color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
+            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+                break;
             }
+            float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+
+            vec2 drop_density = vec2(30, 1);
+            vec2 drop_size = vec2(0.0008, 0.05);
+
+            vec2 rain_pos = (view_pos * rain_dist);
+            rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
+
+            vec2 cell = floor(rain_pos * drop_density) / drop_density;
+            if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
+                continue;
+            }
+            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+            float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
+            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
+            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
         }
     #endif
 
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index 39538ba87b..f91f5fc7d7 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -150,7 +150,7 @@ void main() {
     );
 
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 50.0;
+        float rain_density = rain_density_at(f_pos.xy + focus_off.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
         if (rain_density > 0 && surf_norm.z > 0.5) {
             vec3 drop_density = vec3(2, 2, 2);
             vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
new file mode 100644
index 0000000000..1a7a3fe4e0
--- /dev/null
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -0,0 +1,36 @@
+
+#ifndef RAIN_OCCLUSION_GLSL
+#define RAIN_OCCLUSION_GLSL
+
+// Use with sampler2DShadow
+layout(set = 1, binding = 4)
+uniform texture2D t_directed_occlusion_maps;
+layout(set = 1, binding = 5)
+uniform samplerShadow s_directed_occlusion_maps;
+
+layout (std140, set = 0, binding = 14)
+uniform u_rain_occlusion {
+    mat4 occlusionMatrices;
+    mat4 occlusion_texture_mat;
+};
+
+float rain_occlusion_at(in vec3 fragPos)
+{
+    float bias = 0.000;
+    float diskRadius = 0.01;
+    const vec3 sampleOffsetDirections[20] = vec3[]
+    (
+       vec3( 1,  1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1,  1,  1),
+       vec3( 1,  1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1,  1, -1),
+       vec3( 1,  1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1,  1,  0),
+       vec3( 1,  0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1,  0, -1),
+       vec3( 0,  1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0,  1, -1)
+    );
+
+    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos, 1.0);
+
+    float visibility = textureProj(sampler2DShadow(t_directed_occlusion_maps, s_directed_occlusion_maps), rain_pos);
+
+    return visibility;
+}
+#endif
\ No newline at end of file
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 580d6f94db..9b1fd7f04e 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -5,6 +5,7 @@
 #include <srgb.glsl>
 #include <shadows.glsl>
 #include <globals.glsl>
+#include <rain_occlusion.glsl>
 
 // Information about an approximately directional light, like the sun or moon.
 struct DirectionalLight {
@@ -121,8 +122,9 @@ float cloud_tendency_at(vec2 pos) {
 }
 
 const float RAIN_CLOUD = 0.05;
+
 float rain_density_at(vec2 pos) {
-    return sample_weather(pos).g;
+    return 1.0; //sample_weather(pos).g;
     //return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
 }
 
diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
new file mode 100644
index 0000000000..bb7b30906d
--- /dev/null
+++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
@@ -0,0 +1,61 @@
+#version 420 core
+// #extension ARB_texture_storage : enable
+
+#include <constants.glsl>
+
+#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
+
+#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
+
+#if (FLUID_MODE == FLUID_MODE_CHEAP)
+#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
+#elif (FLUID_MODE == FLUID_MODE_SHINY)
+#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE
+#endif
+
+#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
+
+#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
+
+#define HAS_SHADOW_MAPS
+
+// Currently, we only need globals for focus_off.
+#include <globals.glsl>
+// For shadow locals.
+// #include <shadows.glsl>
+
+layout (std140, set = 0, binding = 14)
+uniform u_rain_occlusion {
+    mat4 rainOcclusionMatrices;
+    mat4 texture_mat;
+};
+
+/* Accurate packed shadow maps for many lights at once!
+ *
+ * Ideally, we would just write to a bitmask...
+ *
+ * */
+
+layout(location = 0) in uint v_pos_norm;
+// in uint v_col_light;
+// in vec4 v_pos;
+// layout(location = 1) in uint v_atlas_pos;
+
+// Light projection matrices.
+layout (std140, set = 1,  binding = 0)
+uniform u_locals {
+    vec3 model_offs;
+    float load_time;
+    ivec4 atlas_offs;
+};
+
+// out vec4 shadowMapCoord;
+
+const float EXTRA_NEG_Z = 32768.0;
+
+void main() {
+    vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z);
+    vec3 f_pos = f_chunk_pos + (model_offs - focus_off.xyz);
+    
+    gl_Position = rainOcclusionMatrices * vec4(f_pos, 1.0);
+}
diff --git a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
new file mode 100644
index 0000000000..ae9ecb90a7
--- /dev/null
+++ b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
@@ -0,0 +1,82 @@
+#version 420 core
+// #extension ARB_texture_storage : enable
+
+#define FIGURE_SHADER
+
+#include <constants.glsl>
+
+#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
+
+#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
+
+#if (FLUID_MODE == FLUID_MODE_CHEAP)
+#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
+#elif (FLUID_MODE == FLUID_MODE_SHINY)
+#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE
+#endif
+
+#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
+
+#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
+
+#define HAS_SHADOW_MAPS
+
+// Currently, we only need globals for focus_off.
+#include <globals.glsl>
+// For shadow locals.
+// #include <shadows.glsl>
+
+layout (std140, set = 0, binding = 14)
+uniform u_rain_occlusion {
+    mat4 rainOcclusionMatrices;
+    mat4 texture_mat;
+};
+
+/* Accurate packed shadow maps for many lights at once!
+ *
+ * Ideally, we would just write to a bitmask...
+ *
+ * */
+
+layout(location = 0) in uint v_pos_norm;
+layout(location = 1) in uint v_atlas_pos;
+// in uint v_col_light;
+// in vec4 v_pos;
+
+layout (std140, set = 1, binding = 0)
+uniform u_locals {
+    mat4 model_mat;
+    vec4 highlight_col;
+    vec4 model_light;
+    vec4 model_glow;
+    ivec4 atlas_offs;
+    vec3 model_pos;
+    // bit 0 - is player
+    // bit 1-31 - unused
+    int flags;
+};
+
+struct BoneData {
+    mat4 bone_mat;
+    mat4 normals_mat;
+};
+
+layout (std140, set = 1, binding = 1)
+uniform u_bones {
+    // Warning: might not actually be 16 elements long. Don't index out of bounds!
+    BoneData bones[16];
+};
+
+// out vec4 shadowMapCoord;
+
+void main() {
+    uint bone_idx = (v_pos_norm >> 27) & 0xFu;
+    vec3 pos = (vec3((uvec3(v_pos_norm) >> uvec3(0, 9, 18)) & uvec3(0x1FFu)) - 256.0) / 2.0;
+
+    vec3 f_pos = (
+        bones[bone_idx].bone_mat *
+        vec4(pos, 1.0)
+    ).xyz + (model_pos - focus_off.xyz/* + vec3(0.0, 0.0, 0.0001)*/);
+
+    gl_Position = rainOcclusionMatrices * vec4(f_pos, 1.0);
+}
diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl
index f36fd6a79b..f5d25606f2 100644
--- a/assets/voxygen/shaders/sprite-vert.glsl
+++ b/assets/voxygen/shaders/sprite-vert.glsl
@@ -30,7 +30,7 @@ layout(location = 7) in float inst_glow;
 layout(location = 8) in float model_wind_sway; // NOTE: this only varies per model
 layout(location = 9) in float model_z_scale; // NOTE: this only varies per model
 
-layout(set = 0, binding = 14) restrict readonly buffer sprite_verts {
+layout(set = 0, binding = 15) restrict readonly buffer sprite_verts {
     uvec2 verts[];
 };
 
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index df76c504b7..d5291b208e 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -232,9 +232,11 @@ void main() {
     vec3 k_s = vec3(R_s);
 
     #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(cam_pos.xy + focus_off.xy) * 50.0;
+        vec3 pos = f_pos + focus_off.xyz;
+        float rain_density = rain_density_at(pos.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
+        // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
+        // return;
         if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
-            vec3 pos = f_pos + focus_off.xyz;
             vec3 drop_density = vec3(2, 2, 2);
             vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
             drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
diff --git a/client/src/lib.rs b/client/src/lib.rs
index 95229f5570..dec60c0e45 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -166,11 +166,13 @@ impl WeatherLerp {
     fn update(&mut self) {
         let old = &self.old.0;
         let new = &self.new.0;
-        if old.size() != new.size() {
-            if self.current.size() != new.size() {
-                self.current = new.clone();
-            }
-        } else {
+        if new.size() == Vec2::zero() {
+            return;
+        }
+        if self.current.size() != new.size() {
+            self.current = new.clone();
+        }
+        if old.size() == new.size() {
             // Assume updates are regular
             let t = (self.new.1.elapsed().as_secs_f32()
                 / self.new.1.duration_since(self.old.1).as_secs_f32())
diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs
index 7f5f1e8ed3..e60e359d45 100644
--- a/server/src/events/interaction.rs
+++ b/server/src/events/interaction.rs
@@ -186,7 +186,9 @@ pub fn handle_mine_block(
                     ) {
                         let skill_group = SkillGroupKind::Weapon(tool);
                         let outcome_bus = state.ecs().read_resource::<EventBus<Outcome>>();
-                        if let Some(level_outcome) = skillset.add_experience(skill_group, exp_reward) {
+                        if let Some(level_outcome) =
+                            skillset.add_experience(skill_group, exp_reward)
+                        {
                             outcome_bus.emit_now(Outcome::SkillPointGain {
                                 uid,
                                 skill_tree: skill_group,
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index e453ab5f58..6be97876dc 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -13,8 +13,8 @@ use common::{
 };
 use common_state::State;
 use serde::Deserialize;
-use strum::IntoEnumIterator;
 use std::time::Instant;
+use strum::IntoEnumIterator;
 use tracing::warn;
 use vek::*;
 
@@ -66,14 +66,17 @@ impl AmbientMgr {
                     if index >= audio.ambient_channels.len() {
                         println!("Creating audio channel with this tag: {:?}", tag);
                         audio.new_ambient_channel(tag);
-                        break
+                        break;
                     }
                 }
-                // even if the conditions don't warrant the creation of a channel
-                // with that tag, but a channel with that tag remains
-                // nonetheless, run the code
+                // even if the conditions don't warrant the creation of a
+                // channel with that tag, but a channel with
+                // that tag remains nonetheless, run the code
             } else if audio.get_ambient_channel(tag).is_some() {
-                println!("Channel for {:?} is actually present, performing volume code", tag);
+                println!(
+                    "Channel for {:?} is actually present, performing volume code",
+                    tag
+                );
                 for index in 0..AmbientChannelTag::iter().len() {
                     // update with sfx volume
                     audio.ambient_channels[index].set_volume(sfx_volume);
@@ -121,7 +124,11 @@ impl AmbientMgr {
 
                         // remove channel if not playing
                         if audio.ambient_channels[index].get_multiplier() == 0.0 {
-                            println!("Removing channel {:?} with tag {:?}", index, audio.ambient_channels[index].get_tag());
+                            println!(
+                                "Removing channel {:?} with tag {:?}",
+                                index,
+                                audio.ambient_channels[index].get_tag()
+                            );
                             audio.ambient_channels[index].stop();
                             audio.ambient_channels.remove(index);
                         };
@@ -182,8 +189,8 @@ impl AmbientMgr {
         // Tree density factors into wind volume. The more trees,
         // the lower wind volume. The trees make more of an impact
         // the closer the camera is to the ground.
-        let tree_multiplier =
-            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+        let tree_multiplier = 1.0
+            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
 
         return tree_multiplier > 0.05;
     }
@@ -264,10 +271,13 @@ impl AmbientChannel {
         let tree_multiplier =
             ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
 
-        // Lastly, we of course have to take into account actual wind speed from weathersim
+        // Lastly, we of course have to take into account actual wind speed from
+        // weathersim
         let wind_speed_multiplier = (client_wind_speed_sq / 30.0_f32.powi(2)).min(1.0);
 
-        return alt_multiplier * tree_multiplier * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
+        return alt_multiplier
+            * tree_multiplier
+            * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
     }
 
     fn get_rain_volume(&mut self, client: &Client) -> f32 {
@@ -301,8 +311,8 @@ impl AmbientChannel {
         // Tree density factors into wind volume. The more trees,
         // the lower wind volume. The trees make more of an impact
         // the closer the camera is to the ground.
-        let tree_multiplier =
-            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+        let tree_multiplier = 1.0
+            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
 
         if tree_multiplier > 0.05 {
             tree_multiplier
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index 4a6963c9c5..bc5f4ccd4d 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -22,8 +22,8 @@ use crate::audio::{
 };
 use rodio::{OutputStreamHandle, Sample, Sink, Source, SpatialSink};
 use serde::Deserialize;
-use strum::EnumIter;
 use std::time::Instant;
+use strum::EnumIter;
 use tracing::warn;
 use vek::*;
 
diff --git a/voxygen/src/menu/main/scene.rs b/voxygen/src/menu/main/scene.rs
index 77f5c709d0..68d25b8f47 100644
--- a/voxygen/src/menu/main/scene.rs
+++ b/voxygen/src/menu/main/scene.rs
@@ -1,6 +1,6 @@
 use crate::render::{
-    GlobalModel, Globals, GlobalsBindGroup, Light, LodData, PointLightMatrix, Renderer, Shadow,
-    ShadowLocals,
+    GlobalModel, Globals, GlobalsBindGroup, Light, LodData, PointLightMatrix, RainOcclusionLocals,
+    Renderer, Shadow, ShadowLocals,
 };
 
 pub struct Scene {
@@ -14,6 +14,8 @@ impl Scene {
             lights: renderer.create_consts(&[Light::default(); 32]),
             shadows: renderer.create_consts(&[Shadow::default(); 32]),
             shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
+            rain_occlusion_mats: renderer
+                .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
             point_light_matrices: Box::new([PointLightMatrix::default(); 126]),
         };
 
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index 923af4c49f..cdfb34d2d6 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -30,6 +30,7 @@ pub use self::{
         lod_terrain::{LodData, Vertex as LodTerrainVertex},
         particle::{Instance as ParticleInstance, Vertex as ParticleVertex},
         postprocess::Locals as PostProcessLocals,
+        rain_occlusion::Locals as RainOcclusionLocals,
         shadow::{Locals as ShadowLocals, PointLightMatrix},
         skybox::{create_mesh as create_skybox_mesh, Vertex as SkyboxVertex},
         sprite::{
@@ -122,6 +123,10 @@ pub enum CloudMode {
     High,
 }
 
+impl CloudMode {
+    pub fn is_enabled(&self) -> bool { *self != CloudMode::None }
+}
+
 impl Default for CloudMode {
     fn default() -> Self { CloudMode::High }
 }
@@ -398,7 +403,7 @@ impl RenderMode {
 #[derive(PartialEq, Clone, Debug)]
 pub struct PipelineModes {
     aa: AaMode,
-    cloud: CloudMode,
+    pub cloud: CloudMode,
     fluid: FluidMode,
     lighting: LightingMode,
     pub shadow: ShadowMode,
diff --git a/voxygen/src/render/pipelines/clouds.rs b/voxygen/src/render/pipelines/clouds.rs
index 95e03f65dc..f0531bdf85 100644
--- a/voxygen/src/render/pipelines/clouds.rs
+++ b/voxygen/src/render/pipelines/clouds.rs
@@ -153,7 +153,11 @@ impl CloudsPipeline {
             device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                 label: Some("Clouds pipeline layout"),
                 push_constant_ranges: &[],
-                bind_group_layouts: &[&global_layout.globals, &layout.layout],
+                bind_group_layouts: &[
+                    &global_layout.globals,
+                    &global_layout.shadow_textures,
+                    &layout.layout,
+                ],
             });
 
         let samples = match aa_mode {
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index 879d22857a..7f3c7cb194 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -8,6 +8,7 @@ pub mod lod_object;
 pub mod lod_terrain;
 pub mod particle;
 pub mod postprocess;
+pub mod rain_occlusion;
 pub mod shadow;
 pub mod skybox;
 pub mod sprite;
@@ -254,6 +255,7 @@ pub struct GlobalModel {
     pub lights: Consts<Light>,
     pub shadows: Consts<Shadow>,
     pub shadow_mats: shadow::BoundLocals,
+    pub rain_occlusion_mats: rain_occlusion::BoundLocals,
     pub point_light_matrices: Box<[shadow::PointLightMatrix; 126]>,
 }
 
@@ -425,6 +427,18 @@ impl GlobalsLayouts {
                 },
                 count: None,
             },
+            // rain occlusion
+            wgpu::BindGroupLayoutEntry {
+                binding: 14,
+                visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                // TODO: is this relevant?
+                ty: wgpu::BindingType::Buffer {
+                    ty: wgpu::BufferBindingType::Uniform,
+                    has_dynamic_offset: false,
+                    min_binding_size: None,
+                },
+                count: None,
+            },
         ]
     }
 
@@ -503,6 +517,26 @@ impl GlobalsLayouts {
                     },
                     count: None,
                 },
+                // Rain occlusion maps
+                wgpu::BindGroupLayoutEntry {
+                    binding: 4,
+                    visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                    ty: wgpu::BindingType::Texture {
+                        sample_type: wgpu::TextureSampleType::Depth,
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                        multisampled: false,
+                    },
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 5,
+                    visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                    ty: wgpu::BindingType::Sampler {
+                        filtering: true,
+                        comparison: true,
+                    },
+                    count: None,
+                },
             ],
         });
 
@@ -584,6 +618,11 @@ impl GlobalsLayouts {
                 binding: 13,
                 resource: wgpu::BindingResource::Sampler(&lod_data.weather.sampler),
             },
+            // rain occlusion
+            wgpu::BindGroupEntry {
+                binding: 14,
+                resource: global_model.rain_occlusion_mats.buf().as_entire_binding(),
+            },
         ]
     }
 
@@ -608,6 +647,7 @@ impl GlobalsLayouts {
         device: &wgpu::Device,
         point_shadow_map: &Texture,
         directed_shadow_map: &Texture,
+        rain_occlusion_map: &Texture,
     ) -> ShadowTexturesBindGroup {
         let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
             label: None,
@@ -629,6 +669,14 @@ impl GlobalsLayouts {
                     binding: 3,
                     resource: wgpu::BindingResource::Sampler(&directed_shadow_map.sampler),
                 },
+                wgpu::BindGroupEntry {
+                    binding: 4,
+                    resource: wgpu::BindingResource::TextureView(&rain_occlusion_map.view),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 5,
+                    resource: wgpu::BindingResource::Sampler(&rain_occlusion_map.sampler),
+                },
             ],
         });
 
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
new file mode 100644
index 0000000000..aeafdf24d5
--- /dev/null
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -0,0 +1,213 @@
+use super::super::{
+    AaMode, Bound, Consts, FigureLayout, GlobalsLayouts, TerrainLayout, TerrainVertex,
+};
+use bytemuck::{Pod, Zeroable};
+use vek::*;
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Zeroable, Pod)]
+pub struct Locals {
+    shadow_matrices: [[f32; 4]; 4],
+    texture_mats: [[f32; 4]; 4],
+}
+
+impl Locals {
+    pub fn new(shadow_mat: Mat4<f32>, texture_mat: Mat4<f32>) -> Self {
+        Self {
+            shadow_matrices: shadow_mat.into_col_arrays(),
+            texture_mats: texture_mat.into_col_arrays(),
+        }
+    }
+
+    pub fn default() -> Self { Self::new(Mat4::identity(), Mat4::identity()) }
+}
+
+pub type BoundLocals = Bound<Consts<Locals>>;
+
+pub struct RainOcclusionLayout {
+    pub locals: wgpu::BindGroupLayout,
+}
+
+impl RainOcclusionLayout {
+    pub fn new(device: &wgpu::Device) -> Self {
+        Self {
+            locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+                label: None,
+                entries: &[wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
+                    ty: wgpu::BindingType::Buffer {
+                        ty: wgpu::BufferBindingType::Uniform,
+                        has_dynamic_offset: false,
+                        min_binding_size: None,
+                    },
+                    count: None,
+                }],
+            }),
+        }
+    }
+
+    pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts<Locals>) -> BoundLocals {
+        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: None,
+            layout: &self.locals,
+            entries: &[wgpu::BindGroupEntry {
+                binding: 0,
+                resource: locals.buf().as_entire_binding(),
+            }],
+        });
+
+        BoundLocals {
+            bind_group,
+            with: locals,
+        }
+    }
+}
+
+pub struct RainOcclusionFigurePipeline {
+    pub pipeline: wgpu::RenderPipeline,
+}
+
+impl RainOcclusionFigurePipeline {
+    pub fn new(
+        device: &wgpu::Device,
+        vs_module: &wgpu::ShaderModule,
+        global_layout: &GlobalsLayouts,
+        figure_layout: &FigureLayout,
+        aa_mode: AaMode,
+    ) -> Self {
+        common_base::span!(_guard, "new");
+
+        let render_pipeline_layout =
+            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+                label: Some("Directed figure shadow pipeline layout"),
+                push_constant_ranges: &[],
+                bind_group_layouts: &[&global_layout.globals, &figure_layout.locals],
+            });
+
+        let samples = match aa_mode {
+            AaMode::None | AaMode::Fxaa => 1,
+            AaMode::MsaaX4 => 4,
+            AaMode::MsaaX8 => 8,
+            AaMode::MsaaX16 => 16,
+        };
+
+        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+            label: Some("Directed shadow figure pipeline"),
+            layout: Some(&render_pipeline_layout),
+            vertex: wgpu::VertexState {
+                module: vs_module,
+                entry_point: "main",
+                buffers: &[TerrainVertex::desc()],
+            },
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleList,
+                strip_index_format: None,
+                front_face: wgpu::FrontFace::Ccw,
+                cull_mode: None,
+                clamp_depth: true,
+                polygon_mode: wgpu::PolygonMode::Fill,
+                conservative: false,
+            },
+            depth_stencil: Some(wgpu::DepthStencilState {
+                format: wgpu::TextureFormat::Depth24Plus,
+                depth_write_enabled: true,
+                depth_compare: wgpu::CompareFunction::Less,
+                stencil: wgpu::StencilState {
+                    front: wgpu::StencilFaceState::IGNORE,
+                    back: wgpu::StencilFaceState::IGNORE,
+                    read_mask: !0,
+                    write_mask: !0,
+                },
+                bias: wgpu::DepthBiasState {
+                    constant: 0,
+                    slope_scale: 0.0,
+                    clamp: 0.0,
+                },
+            }),
+            multisample: wgpu::MultisampleState {
+                count: samples,
+                mask: !0,
+                alpha_to_coverage_enabled: false,
+            },
+            fragment: None,
+        });
+
+        Self {
+            pipeline: render_pipeline,
+        }
+    }
+}
+
+pub struct RainOcclusionPipeline {
+    pub pipeline: wgpu::RenderPipeline,
+}
+
+impl RainOcclusionPipeline {
+    pub fn new(
+        device: &wgpu::Device,
+        vs_module: &wgpu::ShaderModule,
+        global_layout: &GlobalsLayouts,
+        terrain_layout: &TerrainLayout,
+        aa_mode: AaMode,
+    ) -> Self {
+        let render_pipeline_layout =
+            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+                label: Some("Rain occlusion pipeline layout"),
+                push_constant_ranges: &[],
+                bind_group_layouts: &[&global_layout.globals, &terrain_layout.locals],
+            });
+
+        let samples = match aa_mode {
+            AaMode::None | AaMode::Fxaa => 1,
+            AaMode::MsaaX4 => 4,
+            AaMode::MsaaX8 => 8,
+            AaMode::MsaaX16 => 16,
+        };
+
+        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+            label: Some("Rain occlusion pipeline"),
+            layout: Some(&render_pipeline_layout),
+            vertex: wgpu::VertexState {
+                module: vs_module,
+                entry_point: "main",
+                buffers: &[TerrainVertex::desc()],
+            },
+            primitive: wgpu::PrimitiveState {
+                topology: wgpu::PrimitiveTopology::TriangleList,
+                strip_index_format: None,
+                front_face: wgpu::FrontFace::Ccw,
+                cull_mode: Some(wgpu::Face::Front),
+                clamp_depth: true,
+                polygon_mode: wgpu::PolygonMode::Fill,
+                conservative: false,
+            },
+            depth_stencil: Some(wgpu::DepthStencilState {
+                format: wgpu::TextureFormat::Depth24Plus,
+                depth_write_enabled: true,
+                depth_compare: wgpu::CompareFunction::Less,
+                stencil: wgpu::StencilState {
+                    front: wgpu::StencilFaceState::IGNORE,
+                    back: wgpu::StencilFaceState::IGNORE,
+                    read_mask: !0,
+                    write_mask: !0,
+                },
+                bias: wgpu::DepthBiasState {
+                    constant: 0,
+                    slope_scale: 0.0,
+                    clamp: 0.0,
+                },
+            }),
+            multisample: wgpu::MultisampleState {
+                count: samples,
+                mask: !0,
+                alpha_to_coverage_enabled: false,
+            },
+            fragment: None,
+        });
+
+        Self {
+            pipeline: render_pipeline,
+        }
+    }
+}
diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs
index 504d481f6a..e7b81f0d88 100644
--- a/voxygen/src/render/pipelines/sprite.rs
+++ b/voxygen/src/render/pipelines/sprite.rs
@@ -176,11 +176,11 @@ pub struct SpriteLayout {
 impl SpriteLayout {
     pub fn new(device: &wgpu::Device) -> Self {
         let mut entries = GlobalsLayouts::base_globals_layout();
-        debug_assert_eq!(14, entries.len()); // To remember to adjust the bindings below
+        debug_assert_eq!(15, entries.len()); // To remember to adjust the bindings below
         entries.extend_from_slice(&[
             // sprite_verts
             wgpu::BindGroupLayoutEntry {
-                binding: 14,
+                binding: 15,
                 visibility: wgpu::ShaderStage::VERTEX,
                 ty: wgpu::BindingType::Buffer {
                     ty: wgpu::BufferBindingType::Storage { read_only: true },
@@ -214,7 +214,7 @@ impl SpriteLayout {
         entries.extend_from_slice(&[
             // sprite_verts
             wgpu::BindGroupEntry {
-                binding: 14,
+                binding: 15,
                 resource: sprite_verts.0.buf.as_entire_binding(),
             },
         ]);
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index 4f8a17baf2..e210217844 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -3,6 +3,7 @@ pub(super) mod drawer;
 // Consts and bind groups for post-process and clouds
 mod locals;
 mod pipeline_creation;
+mod rain_occlusion_map;
 mod screenshot;
 mod shaders;
 mod shadow_map;
@@ -14,6 +15,8 @@ use pipeline_creation::{
 use shaders::Shaders;
 use shadow_map::{ShadowMap, ShadowMapRenderer};
 
+use self::{pipeline_creation::RainOcclusionPipelines, rain_occlusion_map::RainOcclusionMap};
+
 use super::{
     buffer::Buffer,
     consts::Consts,
@@ -21,8 +24,8 @@ use super::{
     mesh::Mesh,
     model::{DynamicModel, Model},
     pipelines::{
-        blit, bloom, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui,
-        GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
+        blit, bloom, clouds, debug, figure, postprocess, rain_occlusion, shadow, sprite, terrain,
+        ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
     },
     texture::Texture,
     AaMode, AddressMode, FilterMode, OtherModes, PipelineModes, RenderError, RenderMode,
@@ -54,6 +57,7 @@ struct ImmutableLayouts {
     debug: debug::DebugLayout,
     figure: figure::FigureLayout,
     shadow: shadow::ShadowLayout,
+    rain_occlusion: rain_occlusion::RainOcclusionLayout,
     sprite: sprite::SpriteLayout,
     terrain: terrain::TerrainLayout,
     clouds: clouds::CloudsLayout,
@@ -90,6 +94,7 @@ struct Views {
 
 /// Shadow rendering textures, layouts, pipelines, and bind groups
 struct Shadow {
+    rain_map: RainOcclusionMap,
     map: ShadowMap,
     bind: ShadowTexturesBindGroup,
 }
@@ -104,6 +109,7 @@ enum State {
     Interface {
         pipelines: InterfacePipelines,
         shadow_views: Option<(Texture, Texture)>,
+        rain_occlusion_view: Option<Texture>,
         // In progress creation of the remaining pipelines in the background
         creating: PipelineCreation<IngameAndShadowPipelines>,
     },
@@ -117,6 +123,7 @@ enum State {
                     (
                         Pipelines,
                         ShadowPipelines,
+                        RainOcclusionPipelines,
                         Arc<postprocess::PostProcessLayout>,
                     ),
                     RenderError,
@@ -359,6 +366,16 @@ impl Renderer {
         })
         .ok();
 
+        let rain_occlusion_view = RainOcclusionMap::create_view(
+            &device,
+            (dims.width, dims.height),
+            &ShadowMapMode::try_from(pipeline_modes.shadow).unwrap_or_default(),
+        )
+        .map_err(|err| {
+            warn!("Could not create rain occlusion map views: {:?}", err);
+        })
+        .ok();
+
         let shaders = Shaders::load_expect("");
         let shaders_watcher = shaders.reload_watcher();
 
@@ -368,6 +385,7 @@ impl Renderer {
             let debug = debug::DebugLayout::new(&device);
             let figure = figure::FigureLayout::new(&device);
             let shadow = shadow::ShadowLayout::new(&device);
+            let rain_occlusion = rain_occlusion::RainOcclusionLayout::new(&device);
             let sprite = sprite::SpriteLayout::new(&device);
             let terrain = terrain::TerrainLayout::new(&device);
             let clouds = clouds::CloudsLayout::new(&device);
@@ -385,6 +403,7 @@ impl Renderer {
                 debug,
                 figure,
                 shadow,
+                rain_occlusion,
                 sprite,
                 terrain,
                 clouds,
@@ -417,6 +436,7 @@ impl Renderer {
         let state = State::Interface {
             pipelines: interface_pipelines,
             shadow_views,
+            rain_occlusion_view,
             creating,
         };
 
@@ -680,34 +700,49 @@ impl Renderer {
 
             // Get mutable reference to shadow views out of the current state
             let shadow_views = match &mut self.state {
-                State::Interface { shadow_views, .. } => {
-                    shadow_views.as_mut().map(|s| (&mut s.0, &mut s.1))
-                },
+                State::Interface {
+                    shadow_views,
+                    rain_occlusion_view,
+                    ..
+                } => shadow_views
+                    .as_mut()
+                    .map(|s| (&mut s.0, &mut s.1))
+                    .zip(rain_occlusion_view.as_mut()),
                 State::Complete {
                     shadow:
                         Shadow {
                             map: ShadowMap::Enabled(shadow_map),
+                            rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
                             ..
                         },
                     ..
-                } => Some((&mut shadow_map.point_depth, &mut shadow_map.directed_depth)),
+                } => Some((
+                    (&mut shadow_map.point_depth, &mut shadow_map.directed_depth),
+                    &mut rain_occlusion_map.depth,
+                )),
                 State::Complete { .. } => None,
                 State::Nothing => None, // Should never hit this
             };
 
-            if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) =
+            if let (Some(((point_depth, directed_depth), rain_depth)), ShadowMode::Map(mode)) =
                 (shadow_views, self.pipeline_modes.shadow)
             {
-                match ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode) {
-                    Ok((new_point_depth, new_directed_depth)) => {
+                match (
+                    ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode),
+                    RainOcclusionMap::create_view(&self.device, (dims.x, dims.y), &mode),
+                ) {
+                    (Ok((new_point_depth, new_directed_depth)), Ok(new_rain_depth)) => {
                         *point_depth = new_point_depth;
                         *directed_depth = new_directed_depth;
+                        *rain_depth = new_rain_depth;
+
                         // Recreate the shadow bind group if needed
                         if let State::Complete {
                             shadow:
                                 Shadow {
                                     bind,
                                     map: ShadowMap::Enabled(shadow_map),
+                                    rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
                                     ..
                                 },
                             ..
@@ -717,11 +752,17 @@ impl Renderer {
                                 &self.device,
                                 &shadow_map.point_depth,
                                 &shadow_map.directed_depth,
+                                &rain_occlusion_map.depth,
                             );
                         }
                     },
-                    Err(err) => {
-                        warn!("Could not create shadow map views: {:?}", err);
+                    (shadow, rain) => {
+                        if let Err(err) = shadow {
+                            warn!("Could not create shadow map views: {:?}", err);
+                        }
+                        if let Err(err) = rain {
+                            warn!("Could not create rain occlusion map view: {:?}", err);
+                        }
                     },
                 }
             }
@@ -951,12 +992,17 @@ impl Renderer {
         self.state = if let State::Interface {
             pipelines: interface,
             shadow_views,
+            rain_occlusion_view,
             creating,
         } = state
         {
             match creating.try_complete() {
                 Ok(pipelines) => {
-                    let IngameAndShadowPipelines { ingame, shadow } = pipelines;
+                    let IngameAndShadowPipelines {
+                        ingame,
+                        shadow,
+                        rain_occlusion,
+                    } = pipelines;
 
                     let pipelines = Pipelines::consolidate(interface, ingame);
 
@@ -969,14 +1015,26 @@ impl Renderer {
                         shadow_views,
                     );
 
+                    let rain_occlusion_map = RainOcclusionMap::new(
+                        &self.device,
+                        &self.queue,
+                        rain_occlusion.terrain,
+                        rain_occlusion.figure,
+                        rain_occlusion_view,
+                    );
+
                     let shadow_bind = {
                         let (point, directed) = shadow_map.textures();
-                        self.layouts
-                            .global
-                            .bind_shadow_textures(&self.device, point, directed)
+                        self.layouts.global.bind_shadow_textures(
+                            &self.device,
+                            point,
+                            directed,
+                            rain_occlusion_map.texture(),
+                        )
                     };
 
                     let shadow = Shadow {
+                        rain_map: rain_occlusion_map,
                         map: shadow_map,
                         bind: shadow_bind,
                     };
@@ -991,6 +1049,7 @@ impl Renderer {
                 Err(creating) => State::Interface {
                     pipelines: interface,
                     shadow_views,
+                    rain_occlusion_view,
                     creating,
                 },
             }
@@ -1002,7 +1061,12 @@ impl Renderer {
         } = state
         {
             match pipeline_creation.try_complete() {
-                Ok(Ok((pipelines, shadow_pipelines, postprocess_layout))) => {
+                Ok(Ok((
+                    pipelines,
+                    shadow_pipelines,
+                    rain_occlusion_pipelines,
+                    postprocess_layout,
+                ))) => {
                     if let (
                         Some(point_pipeline),
                         Some(terrain_directed_pipeline),
@@ -1019,6 +1083,19 @@ impl Renderer {
                         shadow_map.figure_directed_pipeline = figure_directed_pipeline;
                     }
 
+                    if let (
+                        Some(terrain_directed_pipeline),
+                        Some(figure_directed_pipeline),
+                        RainOcclusionMap::Enabled(rain_occlusion_map),
+                    ) = (
+                        rain_occlusion_pipelines.terrain,
+                        rain_occlusion_pipelines.figure,
+                        &mut shadow.rain_map,
+                    ) {
+                        rain_occlusion_map.terrain_pipeline = terrain_directed_pipeline;
+                        rain_occlusion_map.figure_pipeline = figure_directed_pipeline;
+                    }
+
                     self.pipeline_modes = new_pipeline_modes;
                     self.layouts.postprocess = postprocess_layout;
                     // TODO: we have the potential to skip recreating bindings / render targets on
diff --git a/voxygen/src/render/renderer/binding.rs b/voxygen/src/render/renderer/binding.rs
index f42808c0a8..6deffb0f28 100644
--- a/voxygen/src/render/renderer/binding.rs
+++ b/voxygen/src/render/renderer/binding.rs
@@ -1,3 +1,5 @@
+use crate::render::pipelines::rain_occlusion;
+
 use super::{
     super::{
         pipelines::{
@@ -74,6 +76,16 @@ impl Renderer {
         self.layouts.shadow.bind_locals(&self.device, locals)
     }
 
+    pub fn create_rain_occlusion_bound_locals(
+        &mut self,
+        locals: &[rain_occlusion::Locals],
+    ) -> rain_occlusion::BoundLocals {
+        let locals = self.create_consts(locals);
+        self.layouts
+            .rain_occlusion
+            .bind_locals(&self.device, locals)
+    }
+
     pub fn figure_bind_col_light(&self, col_light: Texture) -> ColLights<figure::Locals> {
         self.layouts.global.bind_col_light(&self.device, col_light)
     }
diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs
index 4d0912c25c..7fdedd371d 100644
--- a/voxygen/src/render/renderer/drawer.rs
+++ b/voxygen/src/render/renderer/drawer.rs
@@ -9,6 +9,7 @@ use super::{
             ShadowTexturesBindGroup,
         },
     },
+    rain_occlusion_map::{RainOcclusionMap, RainOcclusionMapRenderer},
     Renderer, ShadowMap, ShadowMapRenderer,
 };
 use core::{num::NonZeroU32, ops::Range};
@@ -135,6 +136,46 @@ impl<'frame> Drawer<'frame> {
     /// Get the pipeline modes.
     pub fn pipeline_modes(&self) -> &super::super::PipelineModes { self.borrow.pipeline_modes }
 
+    /// Returns None if the shadow renderer is not enabled at some level or the
+    /// pipelines are not available yet
+    pub fn rain_occlusion_pass(&mut self) -> Option<RainOcclusionPassDrawer> {
+        if !self.borrow.pipeline_modes.cloud.is_enabled() {
+            return None;
+        }
+
+        if let RainOcclusionMap::Enabled(ref rain_occlusion_renderer) = self.borrow.shadow?.rain_map
+        {
+            let encoder = self.encoder.as_mut().unwrap();
+            let device = self.borrow.device;
+            let mut render_pass = encoder.scoped_render_pass(
+                "rain_occlusion_pass",
+                device,
+                &wgpu::RenderPassDescriptor {
+                    label: Some("rain occlusion pass"),
+                    color_attachments: &[],
+                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                        view: &rain_occlusion_renderer.depth.view,
+                        depth_ops: Some(wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(1.0),
+                            store: true,
+                        }),
+                        stencil_ops: None,
+                    }),
+                },
+            );
+
+            render_pass.set_bind_group(0, &self.globals.bind_group, &[]);
+
+            Some(RainOcclusionPassDrawer {
+                render_pass,
+                borrow: &self.borrow,
+                rain_occlusion_renderer,
+            })
+        } else {
+            None
+        }
+    }
+
     /// Returns None if the shadow renderer is not enabled at some level or the
     /// pipelines are not available yet
     pub fn shadow_pass(&mut self) -> Option<ShadowPassDrawer> {
@@ -216,6 +257,8 @@ impl<'frame> Drawer<'frame> {
     /// Returns None if the clouds pipeline is not available
     pub fn second_pass(&mut self) -> Option<SecondPassDrawer> {
         let pipelines = &self.borrow.pipelines.all()?;
+        let shadow = self.borrow.shadow?;
+
         let encoder = self.encoder.as_mut().unwrap();
         let device = self.borrow.device;
         let mut render_pass =
@@ -237,6 +280,7 @@ impl<'frame> Drawer<'frame> {
             });
 
         render_pass.set_bind_group(0, &self.globals.bind_group, &[]);
+        render_pass.set_bind_group(1, &shadow.bind.bind_group, &[]);
 
         Some(SecondPassDrawer {
             render_pass,
@@ -639,6 +683,37 @@ impl<'pass> ShadowPassDrawer<'pass> {
     }
 }
 
+#[must_use]
+pub struct RainOcclusionPassDrawer<'pass> {
+    render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>,
+    borrow: &'pass RendererBorrow<'pass>,
+    rain_occlusion_renderer: &'pass RainOcclusionMapRenderer,
+}
+
+impl<'pass> RainOcclusionPassDrawer<'pass> {
+    pub fn draw_figure_shadows(&mut self) -> FigureShadowDrawer<'_, 'pass> {
+        let mut render_pass = self
+            .render_pass
+            .scope("direcred_figure_rain_occlusion", self.borrow.device);
+
+        render_pass.set_pipeline(&self.rain_occlusion_renderer.figure_pipeline.pipeline);
+        set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
+
+        FigureShadowDrawer { render_pass }
+    }
+
+    pub fn draw_terrain_shadows(&mut self) -> TerrainShadowDrawer<'_, 'pass> {
+        let mut render_pass = self
+            .render_pass
+            .scope("direcred_terrain_rain_occlusion", self.borrow.device);
+
+        render_pass.set_pipeline(&self.rain_occlusion_renderer.terrain_pipeline.pipeline);
+        set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
+
+        TerrainShadowDrawer { render_pass }
+    }
+}
+
 #[must_use]
 pub struct FigureShadowDrawer<'pass_ref, 'pass: 'pass_ref> {
     render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
@@ -970,7 +1045,7 @@ impl<'pass> SecondPassDrawer<'pass> {
         self.render_pass
             .set_pipeline(&self.clouds_pipeline.pipeline);
         self.render_pass
-            .set_bind_group(1, &self.borrow.locals.clouds_bind.bind_group, &[]);
+            .set_bind_group(2, &self.borrow.locals.clouds_bind.bind_group, &[]);
         self.render_pass.draw(0..3, 0..1);
     }
 
diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs
index 01664803fa..ac29f6e087 100644
--- a/voxygen/src/render/renderer/pipeline_creation.rs
+++ b/voxygen/src/render/renderer/pipeline_creation.rs
@@ -1,3 +1,5 @@
+use crate::render::pipelines::rain_occlusion;
+
 use super::{
     super::{
         pipelines::{
@@ -60,9 +62,16 @@ pub struct ShadowPipelines {
     pub figure: Option<shadow::ShadowFigurePipeline>,
 }
 
+pub struct RainOcclusionPipelines {
+    pub terrain: Option<rain_occlusion::RainOcclusionPipeline>,
+    pub figure: Option<rain_occlusion::RainOcclusionFigurePipeline>,
+}
+
+// TODO: Find a better name for this?
 pub struct IngameAndShadowPipelines {
     pub ingame: IngamePipelines,
     pub shadow: ShadowPipelines,
+    pub rain_occlusion: RainOcclusionPipelines,
 }
 
 /// Pipelines neccesary to display the UI and take screenshots
@@ -131,6 +140,8 @@ struct ShaderModules {
     point_light_shadows_vert: wgpu::ShaderModule,
     light_shadows_directed_vert: wgpu::ShaderModule,
     light_shadows_figure_vert: wgpu::ShaderModule,
+    rain_occlusion_directed_vert: wgpu::ShaderModule,
+    rain_occlusion_figure_vert: wgpu::ShaderModule,
 }
 
 impl ShaderModules {
@@ -151,6 +162,7 @@ impl ShaderModules {
         let random = shaders.get("include.random").unwrap();
         let lod = shaders.get("include.lod").unwrap();
         let shadows = shaders.get("include.shadows").unwrap();
+        let rain_occlusion = shaders.get("include.rain_occlusion").unwrap();
         let point_glow = shaders.get("include.point_glow").unwrap();
 
         // We dynamically add extra configuration settings to the constants file.
@@ -252,6 +264,7 @@ impl ShaderModules {
                     "constants.glsl" => constants.clone(),
                     "globals.glsl" => globals.0.to_owned(),
                     "shadows.glsl" => shadows.0.to_owned(),
+                    "rain_occlusion.glsl" => rain_occlusion.0.to_owned(),
                     "sky.glsl" => sky.0.to_owned(),
                     "light.glsl" => light.0.to_owned(),
                     "srgb.glsl" => srgb.0.to_owned(),
@@ -332,6 +345,14 @@ impl ShaderModules {
                 "light-shadows-figure-vert",
                 ShaderKind::Vertex,
             )?,
+            rain_occlusion_directed_vert: create_shader(
+                "rain-occlusion-directed-vert",
+                ShaderKind::Vertex,
+            )?,
+            rain_occlusion_figure_vert: create_shader(
+                "rain-occlusion-figure-vert",
+                ShaderKind::Vertex,
+            )?,
         })
     }
 }
@@ -422,7 +443,7 @@ fn create_ingame_and_shadow_pipelines(
     needs: PipelineNeeds,
     pool: &rayon::ThreadPool,
     // TODO: Reduce the boilerplate in this file
-    tasks: [Task; 16],
+    tasks: [Task; 18],
 ) -> IngameAndShadowPipelines {
     prof_span!(_guard, "create_ingame_and_shadow_pipelines");
 
@@ -454,6 +475,8 @@ fn create_ingame_and_shadow_pipelines(
         point_shadow_task,
         terrain_directed_shadow_task,
         figure_directed_shadow_task,
+        terrain_directed_rain_occlusion_task,
+        figure_directed_rain_occlusion_task,
     ] = tasks;
 
     // TODO: pass in format of target color buffer
@@ -739,6 +762,36 @@ fn create_ingame_and_shadow_pipelines(
             "figure directed shadow pipeline creation",
         )
     };
+    // Pipeline for rendering directional light terrain rain occlusion maps.
+    let create_terrain_directed_rain_occlusion = || {
+        terrain_directed_rain_occlusion_task.run(
+            || {
+                rain_occlusion::RainOcclusionPipeline::new(
+                    device,
+                    &shaders.rain_occlusion_directed_vert,
+                    &layouts.global,
+                    &layouts.terrain,
+                    pipeline_modes.aa,
+                )
+            },
+            "terrain directed rain occlusion pipeline creation",
+        )
+    };
+    // Pipeline for rendering directional light figure rain occlusion maps.
+    let create_figure_directed_rain_occlusion = || {
+        figure_directed_rain_occlusion_task.run(
+            || {
+                rain_occlusion::RainOcclusionFigurePipeline::new(
+                    device,
+                    &shaders.rain_occlusion_figure_vert,
+                    &layouts.global,
+                    &layouts.figure,
+                    pipeline_modes.aa,
+                )
+            },
+            "figure directed rain occlusion pipeline creation",
+        )
+    };
 
     let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure));
     let j2 = || pool.join(create_terrain, || pool.join(create_fluid, create_bloom));
@@ -755,7 +808,14 @@ fn create_ingame_and_shadow_pipelines(
             create_figure_directed_shadow,
         )
     };
-    let j7 = create_lod_object;
+    let j7 = || {
+        pool.join(create_lod_object, || {
+            pool.join(
+                create_terrain_directed_rain_occlusion,
+                create_figure_directed_rain_occlusion,
+            )
+        })
+    };
 
     // Ignore this
     let (
@@ -765,7 +825,7 @@ fn create_ingame_and_shadow_pipelines(
         ),
         (
             ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)),
-            lod_object,
+            (lod_object, (terrain_directed_rain_occlusion, figure_directed_rain_occlusion)),
         ),
     ) = pool.join(
         || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
@@ -795,6 +855,10 @@ fn create_ingame_and_shadow_pipelines(
             directed: Some(terrain_directed_shadow),
             figure: Some(figure_directed_shadow),
         },
+        rain_occlusion: RainOcclusionPipelines {
+            terrain: Some(terrain_directed_rain_occlusion),
+            figure: Some(figure_directed_rain_occlusion),
+        },
     }
 }
 
@@ -887,6 +951,7 @@ pub(super) fn recreate_pipelines(
         (
             Pipelines,
             ShadowPipelines,
+            RainOcclusionPipelines,
             Arc<postprocess::PostProcessLayout>,
         ),
         RenderError,
@@ -952,14 +1017,18 @@ pub(super) fn recreate_pipelines(
         let interface = create_interface_pipelines(needs, pool, interface_tasks);
 
         // Create the rest of the pipelines
-        let IngameAndShadowPipelines { ingame, shadow } =
-            create_ingame_and_shadow_pipelines(needs, pool, ingame_and_shadow_tasks);
+        let IngameAndShadowPipelines {
+            ingame,
+            shadow,
+            rain_occlusion,
+        } = create_ingame_and_shadow_pipelines(needs, pool, ingame_and_shadow_tasks);
 
         // Send them
         result_send
             .send(Ok((
                 Pipelines::consolidate(interface, ingame),
                 shadow,
+                rain_occlusion,
                 layouts.postprocess,
             )))
             .expect("Channel disconnected");
diff --git a/voxygen/src/render/renderer/rain_occlusion_map.rs b/voxygen/src/render/renderer/rain_occlusion_map.rs
new file mode 100644
index 0000000000..45ca1bb933
--- /dev/null
+++ b/voxygen/src/render/renderer/rain_occlusion_map.rs
@@ -0,0 +1,226 @@
+use crate::render::pipelines::rain_occlusion;
+
+use super::{
+    super::{texture::Texture, RenderError, ShadowMapMode},
+    Renderer,
+};
+use vek::*;
+
+/// A type that holds shadow map data.  Since shadow mapping may not be
+/// supported on all platforms, we try to keep it separate.
+pub struct RainOcclusionMapRenderer {
+    pub depth: Texture,
+
+    pub terrain_pipeline: rain_occlusion::RainOcclusionPipeline,
+    pub figure_pipeline: rain_occlusion::RainOcclusionFigurePipeline,
+    pub layout: rain_occlusion::RainOcclusionLayout,
+}
+
+pub enum RainOcclusionMap {
+    Enabled(RainOcclusionMapRenderer),
+    /// Dummy texture
+    Disabled(Texture),
+}
+
+impl RainOcclusionMap {
+    pub fn new(
+        device: &wgpu::Device,
+        queue: &wgpu::Queue,
+        directed: Option<rain_occlusion::RainOcclusionPipeline>,
+        figure: Option<rain_occlusion::RainOcclusionFigurePipeline>,
+        view: Option<Texture>,
+    ) -> Self {
+        if let (Some(terrain_pipeline), Some(figure_pipeline), Some(depth)) =
+            (directed, figure, view)
+        {
+            let layout = rain_occlusion::RainOcclusionLayout::new(device);
+
+            Self::Enabled(RainOcclusionMapRenderer {
+                depth,
+                terrain_pipeline,
+                figure_pipeline,
+                layout,
+            })
+        } else {
+            Self::Disabled(Self::create_dummy_tex(device, queue))
+        }
+    }
+
+    fn create_dummy_tex(device: &wgpu::Device, queue: &wgpu::Queue) -> Texture {
+        let tex = {
+            let tex = wgpu::TextureDescriptor {
+                label: None,
+                size: wgpu::Extent3d {
+                    width: 4,
+                    height: 4,
+                    depth_or_array_layers: 1,
+                },
+                mip_level_count: 1,
+                sample_count: 1,
+                dimension: wgpu::TextureDimension::D2,
+                format: wgpu::TextureFormat::Depth24Plus,
+                usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
+            };
+
+            let view = wgpu::TextureViewDescriptor {
+                label: None,
+                format: Some(wgpu::TextureFormat::Depth24Plus),
+                dimension: Some(wgpu::TextureViewDimension::D2),
+                aspect: wgpu::TextureAspect::DepthOnly,
+                base_mip_level: 0,
+                mip_level_count: None,
+                base_array_layer: 0,
+                array_layer_count: None,
+            };
+
+            let sampler_info = wgpu::SamplerDescriptor {
+                label: None,
+                address_mode_u: wgpu::AddressMode::ClampToEdge,
+                address_mode_v: wgpu::AddressMode::ClampToEdge,
+                address_mode_w: wgpu::AddressMode::ClampToEdge,
+                mag_filter: wgpu::FilterMode::Linear,
+                min_filter: wgpu::FilterMode::Linear,
+                mipmap_filter: wgpu::FilterMode::Nearest,
+                compare: Some(wgpu::CompareFunction::LessEqual),
+                ..Default::default()
+            };
+
+            Texture::new_raw(device, &tex, &view, &sampler_info)
+        };
+
+        // Clear to 1.0
+        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
+            label: Some("Dummy rain occlusion tex clearing encoder"),
+        });
+
+        encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+            label: Some("Clear dummy rain occlusion texture"),
+            color_attachments: &[],
+            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+                view: &tex.view,
+                depth_ops: Some(wgpu::Operations {
+                    load: wgpu::LoadOp::Clear(1.0),
+                    store: true,
+                }),
+                stencil_ops: None,
+            }),
+        });
+
+        queue.submit(std::iter::once(encoder.finish()));
+
+        tex
+    }
+
+    /// Create texture and view for rain ocllusion maps.
+    /// Returns (point, directed)
+    pub(super) fn create_view(
+        device: &wgpu::Device,
+        size: (u32, u32),
+        mode: &ShadowMapMode,
+    ) -> Result<Texture, RenderError> {
+        // (Attempt to) apply resolution factor to rain occlusion map resolution.
+        let resolution_factor = mode.resolution.clamped(0.25, 4.0);
+
+        let max_texture_size = Renderer::max_texture_size_raw(device);
+        // Limit to max texture size, rather than erroring.
+        let size = Vec2::new(size.0, size.1).map(|e| {
+            let size = e as f32 * resolution_factor;
+            // NOTE: We know 0 <= e since we clamped the resolution factor to be between
+            // 0.25 and 4.0.
+            if size <= max_texture_size as f32 {
+                size as u32
+            } else {
+                max_texture_size
+            }
+        });
+
+        let levels = 1;
+        // Limit to max texture size rather than erroring.
+        let two_size = size.map(|e| {
+            u32::checked_next_power_of_two(e)
+                .filter(|&e| e <= max_texture_size)
+                .unwrap_or(max_texture_size)
+        });
+        let min_size = size.reduce_min();
+        let max_size = size.reduce_max();
+        let _min_two_size = two_size.reduce_min();
+        let _max_two_size = two_size.reduce_max();
+        // For rotated shadow maps, the maximum size of a pixel along any axis is the
+        // size of a diagonal along that axis.
+        let diag_size = size.map(f64::from).magnitude();
+        let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size;
+        let (diag_size, _diag_cross_size) =
+            if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) {
+                // NOTE: diag_cross_size must be non-negative, since it is the ratio of a
+                // non-negative and a positive number (if max_size were zero,
+                // diag_size would be 0 too).  And it must be <= diag_size,
+                // since min_size <= max_size.  Therefore, if diag_size fits in a
+                // u16, so does diag_cross_size.
+                (diag_size as u32, diag_cross_size as u32)
+            } else {
+                // Limit to max texture resolution rather than error.
+                (max_texture_size as u32, max_texture_size as u32)
+            };
+        let diag_two_size = u32::checked_next_power_of_two(diag_size)
+            .filter(|&e| e <= max_texture_size)
+            // Limit to max texture resolution rather than error.
+            .unwrap_or(max_texture_size)
+            // Make sure we don't try to create a zero sized texture (divided by 4 below)
+            .max(4);
+
+        let rain_occlusion_tex = wgpu::TextureDescriptor {
+            label: None,
+            size: wgpu::Extent3d {
+                width: diag_two_size,
+                height: diag_two_size,
+                depth_or_array_layers: 1,
+            },
+            mip_level_count: levels,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: wgpu::TextureFormat::Depth24Plus,
+            usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::RENDER_ATTACHMENT,
+        };
+
+        let rain_occlusion_view = wgpu::TextureViewDescriptor {
+            label: None,
+            format: Some(wgpu::TextureFormat::Depth24Plus),
+            dimension: Some(wgpu::TextureViewDimension::D2),
+            aspect: wgpu::TextureAspect::DepthOnly,
+            base_mip_level: 0,
+            mip_level_count: None,
+            base_array_layer: 0,
+            array_layer_count: None,
+        };
+
+        let sampler_info = wgpu::SamplerDescriptor {
+            label: None,
+            address_mode_u: wgpu::AddressMode::ClampToEdge,
+            address_mode_v: wgpu::AddressMode::ClampToEdge,
+            address_mode_w: wgpu::AddressMode::ClampToEdge,
+            mag_filter: wgpu::FilterMode::Linear,
+            min_filter: wgpu::FilterMode::Linear,
+            mipmap_filter: wgpu::FilterMode::Nearest,
+            compare: Some(wgpu::CompareFunction::LessEqual),
+            ..Default::default()
+        };
+
+        let rain_occlusion_tex = Texture::new_raw(
+            device,
+            &rain_occlusion_tex,
+            &rain_occlusion_view,
+            &sampler_info,
+        );
+
+        Ok(rain_occlusion_tex)
+    }
+
+    pub fn texture(&self) -> &Texture {
+        match self {
+            Self::Enabled(renderer) => &renderer.depth,
+            Self::Disabled(dummy) => dummy,
+        }
+    }
+
+    pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) }
+}
diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs
index 8ee49843d8..6bb1b31d54 100644
--- a/voxygen/src/render/renderer/shaders.rs
+++ b/voxygen/src/render/renderer/shaders.rs
@@ -34,6 +34,7 @@ impl assets::Compound for Shaders {
             "include.random",
             "include.lod",
             "include.shadows",
+            "include.rain_occlusion",
             "include.point_glow",
             "antialias.none",
             "antialias.fxaa",
@@ -45,6 +46,8 @@ impl assets::Compound for Shaders {
             "figure-vert",
             "light-shadows-figure-vert",
             "light-shadows-directed-vert",
+            "rain-occlusion-figure-vert",
+            "rain-occlusion-directed-vert",
             "point-light-shadows-vert",
             "skybox-vert",
             "skybox-frag",
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index f22dcfcb88..1e00717ecf 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -22,8 +22,8 @@ use crate::{
     audio::{ambient, ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
     render::{
         create_skybox_mesh, CloudsLocals, Consts, Drawer, GlobalModel, Globals, GlobalsBindGroup,
-        Light, Model, PointLightMatrix, PostProcessLocals, Renderer, Shadow, ShadowLocals,
-        SkyboxVertex,
+        Light, Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals, Renderer, Shadow,
+        ShadowLocals, SkyboxVertex,
     },
     settings::Settings,
     window::{AnalogGameInput, Event},
@@ -282,6 +282,8 @@ impl Scene {
             lights: renderer.create_consts(&[Light::default(); MAX_LIGHT_COUNT]),
             shadows: renderer.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]),
             shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
+            rain_occlusion_mats: renderer
+                .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
             point_light_matrices: Box::new([PointLightMatrix::default(); MAX_LIGHT_COUNT * 6 + 6]),
         };
 
@@ -1010,6 +1012,13 @@ impl Scene {
                 );
 
                 renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
+
+                let rain_occlusion_locals = RainOcclusionLocals::new(
+                    directed_proj_mat * shadow_all_mat,
+                    directed_texture_proj_mat * shadow_all_mat,
+                );
+                renderer
+                    .update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
             }
             directed_shadow_mats.push(light_view_mat);
             // This leaves us with five dummy slots, which we push as defaults.
@@ -1139,6 +1148,21 @@ impl Scene {
                     self.terrain.chunks_for_point_shadows(focus_pos),
                 )
             }
+            // Render rain occlusion texture
+            {
+                prof_span!("rain occlusion");
+                if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
+                    self.terrain
+                        .render_shadows(&mut occlusion_pass.draw_terrain_shadows(), focus_pos);
+
+                    self.figure_mgr.render_shadows(
+                        &mut occlusion_pass.draw_figure_shadows(),
+                        state,
+                        tick,
+                        camera_data,
+                    );
+                }
+            }
         }
 
         prof_span!(guard, "main pass");
diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs
index cf7be2cb9f..3bf60e5ed7 100644
--- a/voxygen/src/scene/simple.rs
+++ b/voxygen/src/scene/simple.rs
@@ -2,8 +2,8 @@ use crate::{
     mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_terrain},
     render::{
         create_skybox_mesh, BoneMeshes, Consts, FigureModel, FirstPassDrawer, GlobalModel, Globals,
-        GlobalsBindGroup, Light, LodData, Mesh, Model, PointLightMatrix, Renderer, Shadow,
-        ShadowLocals, SkyboxVertex, TerrainVertex,
+        GlobalsBindGroup, Light, LodData, Mesh, Model, PointLightMatrix, RainOcclusionLocals,
+        Renderer, Shadow, ShadowLocals, SkyboxVertex, TerrainVertex,
     },
     scene::{
         camera::{self, Camera, CameraMode},
@@ -113,6 +113,8 @@ impl Scene {
             lights: renderer.create_consts(&[Light::default(); 20]),
             shadows: renderer.create_consts(&[Shadow::default(); 24]),
             shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
+            rain_occlusion_mats: renderer
+                .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
             point_light_matrices: Box::new([PointLightMatrix::default(); 126]),
         };
         let lod = LodData::dummy(renderer);

From bfb4769315d82b39e80d270f357e543ef19fe2d4 Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Wed, 16 Mar 2022 12:04:02 +0000
Subject: [PATCH 28/71] Random depth for rain drops for better occlusion

---
 assets/voxygen/shaders/clouds-frag.glsl       | 29 ++++++++++++-------
 .../shaders/include/rain_occlusion.glsl       |  6 ++--
 2 files changed, 21 insertions(+), 14 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index e6d96cf472..2f2ac74331 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -114,19 +114,9 @@ void main() {
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
         float rain_dist = 150.0;
         for (int i = 0; i < 7; i ++) {
+            float old_rain_dist = rain_dist;
             rain_dist *= 0.3;
 
-            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * rain_dist;
-            float dist_to_rain = length(rpos);
-            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
-                    continue;
-                }
-
-            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-                break;
-            }
-            float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
-
             vec2 drop_density = vec2(30, 1);
             vec2 drop_size = vec2(0.0008, 0.05);
 
@@ -134,6 +124,23 @@ void main() {
             rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
 
             vec2 cell = floor(rain_pos * drop_density) / drop_density;
+
+            float drop_depth = mix(
+                old_rain_dist,
+                rain_dist,
+                fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
+            );
+            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
+            float dist_to_rain = length(rpos);
+            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+                continue;
+            }
+
+            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+                break;
+            }
+            float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+
             if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                 continue;
             }
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index 1a7a3fe4e0..b85b4c44cc 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -16,7 +16,7 @@ uniform u_rain_occlusion {
 
 float rain_occlusion_at(in vec3 fragPos)
 {
-    float bias = 0.000;
+    float bias = 0.5;
     float diskRadius = 0.01;
     const vec3 sampleOffsetDirections[20] = vec3[]
     (
@@ -27,10 +27,10 @@ float rain_occlusion_at(in vec3 fragPos)
        vec3( 0,  1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0,  1, -1)
     );
 
-    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos, 1.0);
+    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos - vec3(0, 0, bias), 1.0);
 
     float visibility = textureProj(sampler2DShadow(t_directed_occlusion_maps, s_directed_occlusion_maps), rain_pos);
 
     return visibility;
 }
-#endif
\ No newline at end of file
+#endif

From 295b89d446f48bc0a01d04b7a9bd3358f8a659ce Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Wed, 16 Mar 2022 12:22:30 +0000
Subject: [PATCH 29/71] Better bias

---
 assets/voxygen/shaders/clouds-frag.glsl            | 2 +-
 assets/voxygen/shaders/include/rain_occlusion.glsl | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 2f2ac74331..57d3befef7 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -112,7 +112,7 @@ void main() {
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-        float rain_dist = 150.0;
+        float rain_dist = 250.0;
         for (int i = 0; i < 7; i ++) {
             float old_rain_dist = rain_dist;
             rain_dist *= 0.3;
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index b85b4c44cc..2c8509d948 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -16,7 +16,7 @@ uniform u_rain_occlusion {
 
 float rain_occlusion_at(in vec3 fragPos)
 {
-    float bias = 0.5;
+    float bias = -0.2;
     float diskRadius = 0.01;
     const vec3 sampleOffsetDirections[20] = vec3[]
     (
@@ -27,7 +27,7 @@ float rain_occlusion_at(in vec3 fragPos)
        vec3( 0,  1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0,  1, -1)
     );
 
-    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos - vec3(0, 0, bias), 1.0);
+    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos, 1.0) - vec4(0, 0, bias, 0);
 
     float visibility = textureProj(sampler2DShadow(t_directed_occlusion_maps, s_directed_occlusion_maps), rain_pos);
 

From 234ed5afb24e76ed4fcf048bebaebf6e00c0877b Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Thu, 17 Mar 2022 10:28:14 +0100
Subject: [PATCH 30/71] Rain occlusion projection matrix

---
 assets/voxygen/shaders/clouds-frag.glsl       |   2 +-
 .../shaders/include/rain_occlusion.glsl       |   9 -
 assets/voxygen/shaders/include/sky.glsl       |   2 +-
 voxygen/src/hud/mod.rs                        |   1 -
 voxygen/src/scene/mod.rs                      | 575 +++++++++---------
 voxygen/src/session/mod.rs                    |   1 -
 6 files changed, 297 insertions(+), 293 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 57d3befef7..93a5696882 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -102,8 +102,8 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         vec3 old_color = color.rgb;
 
+        // If this value is changed also change it in voxygen/src/scene/mod.rs
         float fall_rate = 70.0;
-
         dir.xy += wind_vel * dir.z / fall_rate;
         dir = normalize(dir);
 
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index 2c8509d948..a00d2e549a 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -17,15 +17,6 @@ uniform u_rain_occlusion {
 float rain_occlusion_at(in vec3 fragPos)
 {
     float bias = -0.2;
-    float diskRadius = 0.01;
-    const vec3 sampleOffsetDirections[20] = vec3[]
-    (
-       vec3( 1,  1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1,  1,  1),
-       vec3( 1,  1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1,  1, -1),
-       vec3( 1,  1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1,  1,  0),
-       vec3( 1,  0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1,  0, -1),
-       vec3( 0,  1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0,  1, -1)
-    );
 
     vec4 rain_pos = occlusion_texture_mat * vec4(fragPos, 1.0) - vec4(0, 0, bias, 0);
 
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 9b1fd7f04e..5069377b14 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -124,7 +124,7 @@ float cloud_tendency_at(vec2 pos) {
 const float RAIN_CLOUD = 0.05;
 
 float rain_density_at(vec2 pos) {
-    return 1.0; //sample_weather(pos).g;
+    return sample_weather(pos).g;
     //return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
 }
 
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index e0d04f3353..f335c5a29d 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -494,7 +494,6 @@ pub struct DebugInfo {
     pub num_figures_visible: u32,
     pub num_particles: u32,
     pub num_particles_visible: u32,
-    pub weather: Weather,
 }
 
 pub struct HudInfo {
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 1e00717ecf..b0ec725bda 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -722,13 +722,300 @@ impl Scene {
             Some(&self.terrain),
         );
 
+        let fov = self.camera.get_effective_fov();
+        let aspect_ratio = self.camera.get_aspect_ratio();
+        let view_dir = ((focus_pos.map(f32::fract)) - cam_pos).normalized();
+
+        // We need to compute these offset matrices to transform world space coordinates
+        // to the translated ones we use when multiplying by the light space
+        // matrix; this helps avoid precision loss during the
+        // multiplication.
+        let look_at = math::Vec3::from(cam_pos);
+        let new_dir = math::Vec3::from(view_dir);
+        let new_dir = new_dir.normalized();
+        let up: math::Vec3<f32> = math::Vec3::unit_y();
+
+        // Optimal warping for directed lights:
+        //
+        // n_opt = 1 / sin y (z_n + √(z_n + (f - n) sin y))
+        //
+        // where n is near plane, f is far plane, y is the tilt angle between view and
+        // light direction, and n_opt is the optimal near plane.
+        // We also want a way to transform and scale this matrix (* 0.5 + 0.5) in order
+        // to transform it correctly into texture coordinates, as well as
+        // OpenGL coordinates.  Note that the matrix for directional light
+        // is *already* linear in the depth buffer.
+        //
+        // Also, observe that we flip the texture sampling matrix in order to account
+        // for the fact that DirectX renders top-down.
+        let texture_mat = Mat4::<f32>::scaling_3d::<Vec3<f32>>(Vec3::new(0.5, -0.5, 1.0))
+            * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
+
+        let directed_mats = |d_view_mat: math::Mat4<f32>,
+                             d_dir: math::Vec3<f32>|
+         -> (Mat4<f32>, Mat4<f32>) {
+            // NOTE: Light view space, right-handed.
+            let v_p_orig = math::Vec3::from(d_view_mat * math::Vec4::from_direction(new_dir));
+            let mut v_p = v_p_orig.normalized();
+            let cos_gamma = new_dir.map(f64::from).dot(d_dir.map(f64::from));
+            let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt();
+            let gamma = sin_gamma.asin();
+            let view_mat = math::Mat4::from_col_array(view_mat.into_col_array());
+            // coordinates are transformed from world space (right-handed) to view space
+            // (right-handed).
+            let bounds1 = math::fit_psr(
+                view_mat.map_cols(math::Vec4::from),
+                visible_light_volume.iter().copied(),
+                math::Vec4::homogenized,
+            );
+            let n_e = f64::from(-bounds1.max.z);
+            let factor = compute_warping_parameter_perspective(
+                gamma,
+                n_e,
+                f64::from(fov),
+                f64::from(aspect_ratio),
+            );
+
+            v_p.z = 0.0;
+            v_p.normalize();
+            let l_r: math::Mat4<f32> = if factor > EPSILON_UPSILON {
+                // NOTE: Our coordinates are now in left-handed space, but v_p isn't; however,
+                // v_p has no z component, so we don't have to adjust it for left-handed
+                // spaces.
+                math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p)
+            } else {
+                math::Mat4::identity()
+            };
+            // Convert from right-handed to left-handed coordinates.
+            let directed_proj_mat = math::Mat4::new(
+                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
+            );
+
+            let light_all_mat = l_r * directed_proj_mat * d_view_mat;
+            // coordinates are transformed from world space (right-handed) to rotated light
+            // space (left-handed).
+            let bounds0 = math::fit_psr(
+                light_all_mat,
+                visible_light_volume.iter().copied(),
+                math::Vec4::homogenized,
+            );
+            // Vague idea: project z_n from the camera view to the light view (where it's
+            // tilted by γ).
+            //
+            // NOTE: To transform a normal by M, we multiply by the transpose of the inverse
+            // of M. For the cases below, we are transforming by an
+            // already-inverted matrix, so the transpose of its inverse is
+            // just the transpose of the original matrix.
+            let (z_0, z_1) = {
+                let f_e = f64::from(-bounds1.min.z).max(n_e);
+                // view space, right-handed coordinates.
+                let p_z = bounds1.max.z;
+                // rotated light space, left-handed coordinates.
+                let p_y = bounds0.min.y;
+                let p_x = bounds0.center().x;
+                // moves from view-space (right-handed) to world space (right-handed)
+                let view_inv = view_mat.inverted();
+                // moves from rotated light space (left-handed) to world space (right-handed).
+                let light_all_inv = light_all_mat.inverted();
+
+                // moves from view-space (right-handed) to world-space (right-handed).
+                let view_point = view_inv
+                    * math::Vec4::from_point(
+                        -math::Vec3::unit_z() * p_z, /* + math::Vec4::unit_w() */
+                    );
+                let view_plane = view_mat.transposed() * -math::Vec4::unit_z();
+
+                // moves from rotated light space (left-handed) to world space (right-handed).
+                let light_point = light_all_inv
+                    * math::Vec4::from_point(
+                        math::Vec3::unit_y() * p_y, /* + math::Vec4::unit_w() */
+                    );
+                let light_plane = light_all_mat.transposed() * math::Vec4::unit_y();
+
+                // moves from rotated light space (left-handed) to world space (right-handed).
+                let shadow_point = light_all_inv
+                    * math::Vec4::from_point(
+                        math::Vec3::unit_x() * p_x, /* + math::Vec4::unit_w() */
+                    );
+                let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x();
+
+                // Find the point at the intersection of the three planes; note that since the
+                // equations are already in right-handed world space, we don't need to negate
+                // the z coordinates.
+                let solve_p0 = math::Mat4::new(
+                    view_plane.x,
+                    view_plane.y,
+                    view_plane.z,
+                    0.0,
+                    light_plane.x,
+                    light_plane.y,
+                    light_plane.z,
+                    0.0,
+                    shadow_plane.x,
+                    shadow_plane.y,
+                    shadow_plane.z,
+                    0.0,
+                    0.0,
+                    0.0,
+                    0.0,
+                    1.0,
+                );
+
+                // in world-space (right-handed).
+                let plane_dist = math::Vec4::new(
+                    view_plane.dot(view_point),
+                    light_plane.dot(light_point),
+                    shadow_plane.dot(shadow_point),
+                    1.0,
+                );
+                let p0_world = solve_p0.inverted() * plane_dist;
+                // in rotated light-space (left-handed).
+                let p0 = light_all_mat * p0_world;
+                let mut p1 = p0;
+                // in rotated light-space (left-handed).
+                p1.y = bounds0.max.y;
+
+                // transforms from rotated light-space (left-handed) to view space
+                // (right-handed).
+                let view_from_light_mat = view_mat * light_all_inv;
+                // z0 and z1 are in view space (right-handed).
+                let z0 = view_from_light_mat * p0;
+                let z1 = view_from_light_mat * p1;
+
+                // Extract the homogenized forward component (right-handed).
+                //
+                // NOTE: I don't think the w component should be anything but 1 here, but
+                // better safe than sorry.
+                (
+                    f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
+                    f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
+                )
+            };
+
+            // all of this is in rotated light-space (left-handed).
+            let mut light_focus_pos: math::Vec3<f32> = math::Vec3::zero();
+            light_focus_pos.x = bounds0.center().x;
+            light_focus_pos.y = bounds0.min.y;
+            light_focus_pos.z = bounds0.center().z;
+
+            let d = f64::from(bounds0.max.y - bounds0.min.y).abs();
+
+            let w_l_y = d;
+
+            // NOTE: See section 5.1.2.2 of Lloyd's thesis.
+            // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to
+            // worry about the handedness of their ratio.
+            let alpha = z_1 / z_0;
+            let alpha_sqrt = alpha.sqrt();
+            let directed_near_normal = if factor < 0.0 {
+                // Standard shadow map to LiSPSM
+                (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0))
+            } else {
+                // LiSPSM to PSM
+                ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip()
+            };
+
+            // Equation 5.14 - 5.16
+            let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs();
+            let directed_near = y_(0.0) as f32;
+            let directed_far = y_(1.0) as f32;
+            light_focus_pos.y = if factor > EPSILON_UPSILON {
+                light_focus_pos.y - directed_near
+            } else {
+                light_focus_pos.y
+            };
+            // Left-handed translation.
+            let w_v: math::Mat4<f32> = math::Mat4::translation_3d(-math::Vec3::new(
+                light_focus_pos.x,
+                light_focus_pos.y,
+                light_focus_pos.z,
+            ));
+            let shadow_view_mat: math::Mat4<f32> = w_v * light_all_mat;
+            let w_p: math::Mat4<f32> = {
+                if factor > EPSILON_UPSILON {
+                    // Projection for y
+                    let near = directed_near;
+                    let far = directed_far;
+                    let left = -1.0;
+                    let right = 1.0;
+                    let bottom = -1.0;
+                    let top = 1.0;
+                    let s_x = 2.0 * near / (right - left);
+                    let o_x = (right + left) / (right - left);
+                    let s_z = 2.0 * near / (top - bottom);
+                    let o_z = (top + bottom) / (top - bottom);
+
+                    let s_y = (far + near) / (far - near);
+                    let o_y = -2.0 * far * near / (far - near);
+
+                    math::Mat4::new(
+                        s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, 1.0, 0.0,
+                        0.0,
+                    )
+                } else {
+                    math::Mat4::identity()
+                }
+            };
+
+            let shadow_all_mat: math::Mat4<f32> = w_p * shadow_view_mat;
+            // coordinates are transformed from world space (right-handed)
+            // to post-warp light space (left-handed), then homogenized.
+            let math::Aabb::<f32> {
+                min:
+                    math::Vec3 {
+                        x: xmin,
+                        y: ymin,
+                        z: zmin,
+                    },
+                max:
+                    math::Vec3 {
+                        x: xmax,
+                        y: ymax,
+                        z: zmax,
+                    },
+            } = math::fit_psr(
+                shadow_all_mat,
+                visible_light_volume.iter().copied(),
+                math::Vec4::homogenized,
+            );
+            let s_x = 2.0 / (xmax - xmin);
+            let s_y = 2.0 / (ymax - ymin);
+            let s_z = 1.0 / (zmax - zmin);
+            let o_x = -(xmax + xmin) / (xmax - xmin);
+            let o_y = -(ymax + ymin) / (ymax - ymin);
+            let o_z = -zmin / (zmax - zmin);
+            let directed_proj_mat = Mat4::new(
+                s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, 1.0,
+            );
+
+            let shadow_all_mat: Mat4<f32> = Mat4::from_col_arrays(shadow_all_mat.into_col_arrays());
+
+            let directed_texture_proj_mat = texture_mat * directed_proj_mat;
+            (
+                directed_proj_mat * shadow_all_mat,
+                directed_texture_proj_mat * shadow_all_mat,
+            )
+        };
+
+        let weather = client.current_weather_wpos(focus_off.xy() + cam_pos.xy());
+        if true || weather.rain > 0.001
+        // TODO: check if rain map mode is on
+        {
+            // If this value is changed also change it in cloud-frag.glsl
+            const FALL_RATE: f32 = 70.0;
+            let rain_dir =
+                math::Vec3::from(-Vec3::unit_z() + weather.wind / FALL_RATE).normalized();
+            let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_dir, up);
+
+            let (shadow_mat, texture_mat) = directed_mats(rain_view_mat, rain_dir);
+
+            let rain_occlusion_locals = RainOcclusionLocals::new(shadow_mat, texture_mat);
+            renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
+        }
+
         let sun_dir = scene_data.get_sun_dir();
         let is_daylight = sun_dir.z < 0.0;
         if renderer.pipeline_modes().shadow.is_map() && (is_daylight || !lights.is_empty()) {
-            let fov = self.camera.get_effective_fov();
-            let aspect_ratio = self.camera.get_aspect_ratio();
-
-            let view_dir = ((focus_pos.map(f32::fract)) - cam_pos).normalized();
             let (point_shadow_res, _directed_shadow_res) = renderer.get_shadow_resolution();
             // NOTE: The aspect ratio is currently always 1 for our cube maps, since they
             // are equal on all sides.
@@ -737,289 +1024,17 @@ impl Scene {
             // and moon.
             let directed_light_dir = math::Vec3::from(sun_dir);
 
-            // Optimal warping for directed lights:
-            //
-            // n_opt = 1 / sin y (z_n + √(z_n + (f - n) sin y))
-            //
-            // where n is near plane, f is far plane, y is the tilt angle between view and
-            // light direction, and n_opt is the optimal near plane.
-            // We also want a way to transform and scale this matrix (* 0.5 + 0.5) in order
-            // to transform it correctly into texture coordinates, as well as
-            // OpenGL coordinates.  Note that the matrix for directional light
-            // is *already* linear in the depth buffer.
-            //
-            // Also, observe that we flip the texture sampling matrix in order to account
-            // for the fact that DirectX renders top-down.
-            let texture_mat = Mat4::<f32>::scaling_3d::<Vec3<f32>>(Vec3::new(0.5, -0.5, 1.0))
-                * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
-            // We need to compute these offset matrices to transform world space coordinates
-            // to the translated ones we use when multiplying by the light space
-            // matrix; this helps avoid precision loss during the
-            // multiplication.
-            let look_at = math::Vec3::from(cam_pos);
             // We upload view matrices as well, to assist in linearizing vertex positions.
             // (only for directional lights, so far).
             let mut directed_shadow_mats = Vec::with_capacity(6);
-            let new_dir = math::Vec3::from(view_dir);
-            let new_dir = new_dir.normalized();
-            let up: math::Vec3<f32> = math::Vec3::unit_y();
+
             let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up);
-            {
-                // NOTE: Light view space, right-handed.
-                let v_p_orig =
-                    math::Vec3::from(light_view_mat * math::Vec4::from_direction(new_dir));
-                let mut v_p = v_p_orig.normalized();
-                let cos_gamma = new_dir
-                    .map(f64::from)
-                    .dot(directed_light_dir.map(f64::from));
-                let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt();
-                let gamma = sin_gamma.asin();
-                let view_mat = math::Mat4::from_col_array(view_mat.into_col_array());
-                // coordinates are transformed from world space (right-handed) to view space
-                // (right-handed).
-                let bounds1 = math::fit_psr(
-                    view_mat.map_cols(math::Vec4::from),
-                    visible_light_volume.iter().copied(),
-                    math::Vec4::homogenized,
-                );
-                let n_e = f64::from(-bounds1.max.z);
-                let factor = compute_warping_parameter_perspective(
-                    gamma,
-                    n_e,
-                    f64::from(fov),
-                    f64::from(aspect_ratio),
-                );
+            let (shadow_mat, texture_mat) = directed_mats(light_view_mat, directed_light_dir);
 
-                v_p.z = 0.0;
-                v_p.normalize();
-                let l_r: math::Mat4<f32> = if factor > EPSILON_UPSILON {
-                    // NOTE: Our coordinates are now in left-handed space, but v_p isn't; however,
-                    // v_p has no z component, so we don't have to adjust it for left-handed
-                    // spaces.
-                    math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p)
-                } else {
-                    math::Mat4::identity()
-                };
-                // Convert from right-handed to left-handed coordinates.
-                let directed_proj_mat = math::Mat4::new(
-                    1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
-                );
+            let shadow_locals = ShadowLocals::new(shadow_mat, texture_mat);
 
-                let light_all_mat = l_r * directed_proj_mat * light_view_mat;
-                // coordinates are transformed from world space (right-handed) to rotated light
-                // space (left-handed).
-                let bounds0 = math::fit_psr(
-                    light_all_mat,
-                    visible_light_volume.iter().copied(),
-                    math::Vec4::homogenized,
-                );
-                // Vague idea: project z_n from the camera view to the light view (where it's
-                // tilted by γ).
-                //
-                // NOTE: To transform a normal by M, we multiply by the transpose of the inverse
-                // of M. For the cases below, we are transforming by an
-                // already-inverted matrix, so the transpose of its inverse is
-                // just the transpose of the original matrix.
-                let (z_0, z_1) = {
-                    let f_e = f64::from(-bounds1.min.z).max(n_e);
-                    // view space, right-handed coordinates.
-                    let p_z = bounds1.max.z;
-                    // rotated light space, left-handed coordinates.
-                    let p_y = bounds0.min.y;
-                    let p_x = bounds0.center().x;
-                    // moves from view-space (right-handed) to world space (right-handed)
-                    let view_inv = view_mat.inverted();
-                    // moves from rotated light space (left-handed) to world space (right-handed).
-                    let light_all_inv = light_all_mat.inverted();
+            renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
 
-                    // moves from view-space (right-handed) to world-space (right-handed).
-                    let view_point = view_inv
-                        * math::Vec4::from_point(
-                            -math::Vec3::unit_z() * p_z, /* + math::Vec4::unit_w() */
-                        );
-                    let view_plane = view_mat.transposed() * -math::Vec4::unit_z();
-
-                    // moves from rotated light space (left-handed) to world space (right-handed).
-                    let light_point = light_all_inv
-                        * math::Vec4::from_point(
-                            math::Vec3::unit_y() * p_y, /* + math::Vec4::unit_w() */
-                        );
-                    let light_plane = light_all_mat.transposed() * math::Vec4::unit_y();
-
-                    // moves from rotated light space (left-handed) to world space (right-handed).
-                    let shadow_point = light_all_inv
-                        * math::Vec4::from_point(
-                            math::Vec3::unit_x() * p_x, /* + math::Vec4::unit_w() */
-                        );
-                    let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x();
-
-                    // Find the point at the intersection of the three planes; note that since the
-                    // equations are already in right-handed world space, we don't need to negate
-                    // the z coordinates.
-                    let solve_p0 = math::Mat4::new(
-                        view_plane.x,
-                        view_plane.y,
-                        view_plane.z,
-                        0.0,
-                        light_plane.x,
-                        light_plane.y,
-                        light_plane.z,
-                        0.0,
-                        shadow_plane.x,
-                        shadow_plane.y,
-                        shadow_plane.z,
-                        0.0,
-                        0.0,
-                        0.0,
-                        0.0,
-                        1.0,
-                    );
-
-                    // in world-space (right-handed).
-                    let plane_dist = math::Vec4::new(
-                        view_plane.dot(view_point),
-                        light_plane.dot(light_point),
-                        shadow_plane.dot(shadow_point),
-                        1.0,
-                    );
-                    let p0_world = solve_p0.inverted() * plane_dist;
-                    // in rotated light-space (left-handed).
-                    let p0 = light_all_mat * p0_world;
-                    let mut p1 = p0;
-                    // in rotated light-space (left-handed).
-                    p1.y = bounds0.max.y;
-
-                    // transforms from rotated light-space (left-handed) to view space
-                    // (right-handed).
-                    let view_from_light_mat = view_mat * light_all_inv;
-                    // z0 and z1 are in view space (right-handed).
-                    let z0 = view_from_light_mat * p0;
-                    let z1 = view_from_light_mat * p1;
-
-                    // Extract the homogenized forward component (right-handed).
-                    //
-                    // NOTE: I don't think the w component should be anything but 1 here, but
-                    // better safe than sorry.
-                    (
-                        f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
-                        f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
-                    )
-                };
-
-                // all of this is in rotated light-space (left-handed).
-                let mut light_focus_pos: math::Vec3<f32> = math::Vec3::zero();
-                light_focus_pos.x = bounds0.center().x;
-                light_focus_pos.y = bounds0.min.y;
-                light_focus_pos.z = bounds0.center().z;
-
-                let d = f64::from(bounds0.max.y - bounds0.min.y).abs();
-
-                let w_l_y = d;
-
-                // NOTE: See section 5.1.2.2 of Lloyd's thesis.
-                // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to
-                // worry about the handedness of their ratio.
-                let alpha = z_1 / z_0;
-                let alpha_sqrt = alpha.sqrt();
-                let directed_near_normal = if factor < 0.0 {
-                    // Standard shadow map to LiSPSM
-                    (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0))
-                } else {
-                    // LiSPSM to PSM
-                    ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip()
-                };
-
-                // Equation 5.14 - 5.16
-                let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs();
-                let directed_near = y_(0.0) as f32;
-                let directed_far = y_(1.0) as f32;
-                light_focus_pos.y = if factor > EPSILON_UPSILON {
-                    light_focus_pos.y - directed_near
-                } else {
-                    light_focus_pos.y
-                };
-                // Left-handed translation.
-                let w_v: math::Mat4<f32> = math::Mat4::translation_3d(-math::Vec3::new(
-                    light_focus_pos.x,
-                    light_focus_pos.y,
-                    light_focus_pos.z,
-                ));
-                let shadow_view_mat: math::Mat4<f32> = w_v * light_all_mat;
-                let w_p: math::Mat4<f32> = {
-                    if factor > EPSILON_UPSILON {
-                        // Projection for y
-                        let near = directed_near;
-                        let far = directed_far;
-                        let left = -1.0;
-                        let right = 1.0;
-                        let bottom = -1.0;
-                        let top = 1.0;
-                        let s_x = 2.0 * near / (right - left);
-                        let o_x = (right + left) / (right - left);
-                        let s_z = 2.0 * near / (top - bottom);
-                        let o_z = (top + bottom) / (top - bottom);
-
-                        let s_y = (far + near) / (far - near);
-                        let o_y = -2.0 * far * near / (far - near);
-
-                        math::Mat4::new(
-                            s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, 1.0,
-                            0.0, 0.0,
-                        )
-                    } else {
-                        math::Mat4::identity()
-                    }
-                };
-
-                let shadow_all_mat: math::Mat4<f32> = w_p * shadow_view_mat;
-                // coordinates are transformed from world space (right-handed)
-                // to post-warp light space (left-handed), then homogenized.
-                let math::Aabb::<f32> {
-                    min:
-                        math::Vec3 {
-                            x: xmin,
-                            y: ymin,
-                            z: zmin,
-                        },
-                    max:
-                        math::Vec3 {
-                            x: xmax,
-                            y: ymax,
-                            z: zmax,
-                        },
-                } = math::fit_psr(
-                    shadow_all_mat,
-                    visible_light_volume.iter().copied(),
-                    math::Vec4::homogenized,
-                );
-                let s_x = 2.0 / (xmax - xmin);
-                let s_y = 2.0 / (ymax - ymin);
-                let s_z = 1.0 / (zmax - zmin);
-                let o_x = -(xmax + xmin) / (xmax - xmin);
-                let o_y = -(ymax + ymin) / (ymax - ymin);
-                let o_z = -zmin / (zmax - zmin);
-                let directed_proj_mat = Mat4::new(
-                    s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, 1.0,
-                );
-
-                let shadow_all_mat: Mat4<f32> =
-                    Mat4::from_col_arrays(shadow_all_mat.into_col_arrays());
-
-                let directed_texture_proj_mat = texture_mat * directed_proj_mat;
-                let shadow_locals = ShadowLocals::new(
-                    directed_proj_mat * shadow_all_mat,
-                    directed_texture_proj_mat * shadow_all_mat,
-                );
-
-                renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
-
-                let rain_occlusion_locals = RainOcclusionLocals::new(
-                    directed_proj_mat * shadow_all_mat,
-                    directed_texture_proj_mat * shadow_all_mat,
-                );
-                renderer
-                    .update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
-            }
             directed_shadow_mats.push(light_view_mat);
             // This leaves us with five dummy slots, which we push as defaults.
             directed_shadow_mats
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 81e06f55c6..f4087ef206 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -1104,7 +1104,6 @@ impl PlayState for SessionState {
                     num_particles: self.scene.particle_mgr().particle_count() as u32,
                     num_particles_visible: self.scene.particle_mgr().particle_count_visible()
                         as u32,
-                    weather: client.current_weather(),
                 }
             });
 

From 088d1cfe9decaefc61c519c2579123d2a8eddd73 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Tue, 22 Mar 2022 21:42:51 -0700
Subject: [PATCH 31/71] Remove debug lines, re-mix some sfx, add crafting sfx,
 separate inv event sfx into spatial and nonspatial

---
 assets/voxygen/audio/ambience/leaves.ogg      |  4 ++--
 assets/voxygen/audio/sfx.ron                  | 20 ++++++++++------
 assets/voxygen/audio/sfx/ambient/bees_1.ogg   |  4 ++--
 .../voxygen/audio/sfx/ambient/birdcall_1.ogg  |  4 ++--
 .../voxygen/audio/sfx/ambient/birdcall_2.ogg  |  4 ++--
 .../audio/sfx/ambient/frog_croak_1.ogg        |  4 ++--
 .../audio/sfx/character/dive_roll_1.ogg       |  4 ++--
 .../audio/sfx/character/dive_roll_2.ogg       |  4 ++--
 assets/voxygen/audio/sfx/crafting/hammer.ogg  |  4 ++--
 .../audio/sfx/footsteps/snow_step_1.ogg       |  4 ++--
 .../audio/sfx/footsteps/snow_step_2.ogg       |  4 ++--
 .../audio/sfx/footsteps/snow_step_3.ogg       |  4 ++--
 voxygen/src/audio/ambient.rs                  | 24 +++++--------------
 voxygen/src/audio/sfx/mod.rs                  |  2 ++
 voxygen/src/session/mod.rs                    | 22 ++++++++++++++---
 15 files changed, 62 insertions(+), 50 deletions(-)

diff --git a/assets/voxygen/audio/ambience/leaves.ogg b/assets/voxygen/audio/ambience/leaves.ogg
index 5b2515be42..ee07a5c77a 100644
--- a/assets/voxygen/audio/ambience/leaves.ogg
+++ b/assets/voxygen/audio/ambience/leaves.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:6e89ea4467addefe98cd3ec9d9e8895a25900804c71212be657a61d4aa57e5f2
-size 453813
+oid sha256:04fc161500a4ea9bdbc280c1f88df95baeb06d77c2ee3beb5feddcec25274ff5
+size 449134
diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron
index 8ea7093d23..ecff170504 100644
--- a/assets/voxygen/audio/sfx.ron
+++ b/assets/voxygen/audio/sfx.ron
@@ -155,7 +155,7 @@
                 "voxygen.audio.sfx.footsteps.stepgrass_5",
                 "voxygen.audio.sfx.footsteps.stepgrass_6",
             ],
-            threshold: 1.6,
+            threshold: 1.8,
         ),
         QuadRun(Grass): (
             files: [
@@ -166,7 +166,7 @@
                 "voxygen.audio.sfx.footsteps.stepgrass_5",
                 "voxygen.audio.sfx.footsteps.stepgrass_6",
             ],
-            threshold: 0.8,
+            threshold: 0.9,
         ),
         // For when sand 1) exists and 2) has unique sounds
         // Run(Sand): (
@@ -195,7 +195,7 @@
                 "voxygen.audio.sfx.footsteps.snow_step_2",
                 "voxygen.audio.sfx.footsteps.snow_step_3",
             ],
-            threshold: 1.6,
+            threshold: 1.8,
         ),
         QuadRun(Snow): (
             files: [
@@ -203,7 +203,7 @@
                 "voxygen.audio.sfx.footsteps.snow_step_2",
                 "voxygen.audio.sfx.footsteps.snow_step_3",
             ],
-            threshold: 0.8,
+            threshold: 0.9,
         ),
         Run(Rock): (
             files: [
@@ -220,7 +220,7 @@
                 "voxygen.audio.sfx.footsteps.stone_step_11",
                 "voxygen.audio.sfx.footsteps.stone_step_12",
             ],
-            threshold: 1.6,
+            threshold: 1.8,
         ),
         QuadRun(Rock): (
             files: [
@@ -237,14 +237,14 @@
                 "voxygen.audio.sfx.footsteps.stone_step_11",
                 "voxygen.audio.sfx.footsteps.stone_step_12",
             ],
-            threshold: 0.8,
+            threshold: 0.9,
         ),
         Roll: (
             files: [
                 "voxygen.audio.sfx.character.dive_roll_1",
                 "voxygen.audio.sfx.character.dive_roll_2",
             ],
-            threshold: 0.25,
+            threshold: 0.3,
         ),
         Climb: (
            files: [
@@ -620,6 +620,12 @@
             ],
             threshold: 0.3,
         ),
+        Inventory(Craft): (
+            files: [
+                "voxygen.audio.sfx.crafting.hammer",
+            ],
+            threshold: 0.05,
+        ),
 
         //
         // Consumables
diff --git a/assets/voxygen/audio/sfx/ambient/bees_1.ogg b/assets/voxygen/audio/sfx/ambient/bees_1.ogg
index 57738d6ca8..00511a8725 100644
--- a/assets/voxygen/audio/sfx/ambient/bees_1.ogg
+++ b/assets/voxygen/audio/sfx/ambient/bees_1.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:1eee9c850c3fba96e607284abac60c52ec7703a31fc42e0f97c367df1a12d168
-size 174456
+oid sha256:51e3b36a0ddded060a009ae2c0e0f282d1bcc3826019573de5d68a10847f2335
+size 137186
diff --git a/assets/voxygen/audio/sfx/ambient/birdcall_1.ogg b/assets/voxygen/audio/sfx/ambient/birdcall_1.ogg
index 2d8abedaf2..06ceea2840 100644
--- a/assets/voxygen/audio/sfx/ambient/birdcall_1.ogg
+++ b/assets/voxygen/audio/sfx/ambient/birdcall_1.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:673504d4e6e1ccf272782b8159baedabf7fde73d7c006cccaf0beaedc5d314fa
-size 84946
+oid sha256:b8e7061dca633f53c30cc91d18b266d46c53504926ad3168ace1c673de444cef
+size 66488
diff --git a/assets/voxygen/audio/sfx/ambient/birdcall_2.ogg b/assets/voxygen/audio/sfx/ambient/birdcall_2.ogg
index a9a5a7f47b..e1e6cbfb50 100644
--- a/assets/voxygen/audio/sfx/ambient/birdcall_2.ogg
+++ b/assets/voxygen/audio/sfx/ambient/birdcall_2.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cedcdff91a632c439467420293b2f22f18399268f7ec08b76f81c34fdddaab29
-size 137561
+oid sha256:9a305641f3eaa45b8fadd4160e6a677bfd9bf0a5950c79e62bbc0d4bb72b4fa6
+size 107298
diff --git a/assets/voxygen/audio/sfx/ambient/frog_croak_1.ogg b/assets/voxygen/audio/sfx/ambient/frog_croak_1.ogg
index e6fb9f3650..cdb7fee5d8 100644
--- a/assets/voxygen/audio/sfx/ambient/frog_croak_1.ogg
+++ b/assets/voxygen/audio/sfx/ambient/frog_croak_1.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:cfc457273f6a969f064ee8a149ea2b1ca5f2cd80c4747bb4a65a2b1b6cb7c439
-size 9212
+oid sha256:1f77f03cfdde09602f6cfe676c7e9a12ae42feff41fc548d4ac7ac880312ca75
+size 7409
diff --git a/assets/voxygen/audio/sfx/character/dive_roll_1.ogg b/assets/voxygen/audio/sfx/character/dive_roll_1.ogg
index ccf1cec114..6295dcdb16 100644
--- a/assets/voxygen/audio/sfx/character/dive_roll_1.ogg
+++ b/assets/voxygen/audio/sfx/character/dive_roll_1.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:3a3f7f26a385ede32dc79744078967caf3449b8623785729c1f186f78d9399d3
-size 16489
+oid sha256:3b0a80cfe9688a00b5690ffc79746fe6b47de1cadf053b8af509ed6e28ba82ee
+size 8989
diff --git a/assets/voxygen/audio/sfx/character/dive_roll_2.ogg b/assets/voxygen/audio/sfx/character/dive_roll_2.ogg
index 3b05cabb0e..356487fb28 100644
--- a/assets/voxygen/audio/sfx/character/dive_roll_2.ogg
+++ b/assets/voxygen/audio/sfx/character/dive_roll_2.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:7638f3a2053e3cadc7551bbe105eb1fb9afb934e4d71d551064e5d740c411df2
-size 20611
+oid sha256:b3107b18d513c69bb794bad320b36e02f8dcf80b2f4eec1704de553861a38cce
+size 9105
diff --git a/assets/voxygen/audio/sfx/crafting/hammer.ogg b/assets/voxygen/audio/sfx/crafting/hammer.ogg
index 231793d411..0000a6454b 100644
--- a/assets/voxygen/audio/sfx/crafting/hammer.ogg
+++ b/assets/voxygen/audio/sfx/crafting/hammer.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:ad18acbbf98dcb1f6b0dcbb37c5bb97331d1eec6f0ea3e1b68c6aacf3a629afb
-size 11280
+oid sha256:4f3936067b4e070911420ff038f6784177c1682c0f0d4da29ddf6b734a3b7a14
+size 8707
diff --git a/assets/voxygen/audio/sfx/footsteps/snow_step_1.ogg b/assets/voxygen/audio/sfx/footsteps/snow_step_1.ogg
index 3eb0d491ac..625097be82 100644
--- a/assets/voxygen/audio/sfx/footsteps/snow_step_1.ogg
+++ b/assets/voxygen/audio/sfx/footsteps/snow_step_1.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:c6aeabd16949b87a43d194d08f6aedd173103ce8535cd930029f280d6b00c841
-size 12849
+oid sha256:7586e83188dbb014be78922eec4c3418d59924516bde0c67a8b3031a6740b320
+size 13332
diff --git a/assets/voxygen/audio/sfx/footsteps/snow_step_2.ogg b/assets/voxygen/audio/sfx/footsteps/snow_step_2.ogg
index ab72e4cb88..85474aaaf6 100644
--- a/assets/voxygen/audio/sfx/footsteps/snow_step_2.ogg
+++ b/assets/voxygen/audio/sfx/footsteps/snow_step_2.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:9810dca5425173b196a794f46ed2f8a0e707c636290ef6cc84ca7686761cd924
-size 12297
+oid sha256:33ef3179c67c9c2f27f9b509fcfe21ad8586d9f0eb4b9a29e3046daf0940448c
+size 12950
diff --git a/assets/voxygen/audio/sfx/footsteps/snow_step_3.ogg b/assets/voxygen/audio/sfx/footsteps/snow_step_3.ogg
index 9e63e16562..cd7182040e 100644
--- a/assets/voxygen/audio/sfx/footsteps/snow_step_3.ogg
+++ b/assets/voxygen/audio/sfx/footsteps/snow_step_3.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:07d11684646b9942e6fceb342a6b26e66b704a9072c2ca127dd2f896ff196fd3
-size 10593
+oid sha256:26e6212bb82f1a7f5795944a55ba71e317c8bd0124bdec99ed11d466612b4da4
+size 11012
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 6be97876dc..3cdfaa5bfd 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -57,14 +57,11 @@ impl AmbientMgr {
             };
             // if the conditions warrant creating a channel of that tag
             if should_create && audio.get_ambient_channel(tag).is_none() {
-                println!("No audio channel with this tag: {:?}", tag);
                 // iterate through the supposed number of channels - one for each tag
                 for index in 0..AmbientChannelTag::iter().len() {
-                    println!("Iter on channel index {:?}", index);
                     // if index would exceed current number of channels, create a new one with
                     // current tag
                     if index >= audio.ambient_channels.len() {
-                        println!("Creating audio channel with this tag: {:?}", tag);
                         audio.new_ambient_channel(tag);
                         break;
                     }
@@ -73,10 +70,6 @@ impl AmbientMgr {
                 // channel with that tag, but a channel with
                 // that tag remains nonetheless, run the code
             } else if audio.get_ambient_channel(tag).is_some() {
-                println!(
-                    "Channel for {:?} is actually present, performing volume code",
-                    tag
-                );
                 for index in 0..AmbientChannelTag::iter().len() {
                     // update with sfx volume
                     audio.ambient_channels[index].set_volume(sfx_volume);
@@ -124,11 +117,6 @@ impl AmbientMgr {
 
                         // remove channel if not playing
                         if audio.ambient_channels[index].get_multiplier() == 0.0 {
-                            println!(
-                                "Removing channel {:?} with tag {:?}",
-                                index,
-                                audio.ambient_channels[index].get_tag()
-                            );
                             audio.ambient_channels[index].stop();
                             audio.ambient_channels.remove(index);
                         };
@@ -189,10 +177,10 @@ impl AmbientMgr {
         // Tree density factors into wind volume. The more trees,
         // the lower wind volume. The trees make more of an impact
         // the closer the camera is to the ground.
-        let tree_multiplier = 1.0
-            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+        let tree_multiplier =
+            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2)).min(1.0));
 
-        return tree_multiplier > 0.05;
+        return tree_multiplier > 0.1;
     }
 }
 
@@ -311,10 +299,10 @@ impl AmbientChannel {
         // Tree density factors into wind volume. The more trees,
         // the lower wind volume. The trees make more of an impact
         // the closer the camera is to the ground.
-        let tree_multiplier = 1.0
-            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0));
+        let tree_multiplier =
+            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2)).min(1.0));
 
-        if tree_multiplier > 0.05 {
+        if tree_multiplier > 0.1 {
             tree_multiplier
         } else {
             0.0
diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs
index d33eb68263..86378f46bf 100644
--- a/voxygen/src/audio/sfx/mod.rs
+++ b/voxygen/src/audio/sfx/mod.rs
@@ -296,6 +296,7 @@ pub enum SfxInventoryEvent {
     Dropped,
     Given,
     Swapped,
+    Craft,
 }
 
 // TODO Move to a separate event mapper?
@@ -328,6 +329,7 @@ impl From<&InventoryUpdateEvent> for SfxEvent {
             InventoryUpdateEvent::Dropped => SfxEvent::Inventory(SfxInventoryEvent::Dropped),
             InventoryUpdateEvent::Given => SfxEvent::Inventory(SfxInventoryEvent::Given),
             InventoryUpdateEvent::Swapped => SfxEvent::Inventory(SfxInventoryEvent::Swapped),
+            InventoryUpdateEvent::Craft => SfxEvent::Inventory(SfxInventoryEvent::Craft),
             _ => SfxEvent::Inventory(SfxInventoryEvent::Swapped),
         }
     }
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index f4087ef206..ec440ca71a 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -245,9 +245,25 @@ impl SessionState {
                     let sfx_triggers = self.scene.sfx_mgr.triggers.read();
 
                     let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
-                    global_state
-                        .audio
-                        .emit_sfx_item(sfx_trigger_item, Some(1.0));
+
+                    match inv_event {
+                        InventoryUpdateEvent::Dropped 
+                        | InventoryUpdateEvent::Swapped
+                        | InventoryUpdateEvent::Given
+                        | InventoryUpdateEvent::Collected(_)
+                        | InventoryUpdateEvent::EntityCollectFailed(_)
+                        | InventoryUpdateEvent::BlockCollectFailed(_)
+                        | InventoryUpdateEvent::Craft => {
+                            global_state
+                                .audio
+                                .emit_sfx_item(sfx_trigger_item, Some(1.0));
+
+                        }
+                        _ => global_state
+                                .audio
+                                .emit_sfx(sfx_trigger_item, client.position().unwrap_or_default(), Some(1.0), false)
+                    }
+
 
                     match inv_event {
                         InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {

From 365f45397b1fdb04bce309b6ff19ae54d11d8daa Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Tue, 22 Mar 2022 22:39:06 -0700
Subject: [PATCH 32/71] At this point all sfx should be able to detect
 underwater

---
 voxygen/src/session/mod.rs | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index ec440ca71a..791e7989c2 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -175,6 +175,21 @@ impl SessionState {
         self.scene
             .maintain_debug_hitboxes(&client, &global_state.settings, &mut self.hitboxes);
 
+        // All this camera code is just to determine if it's underwater for the sfx filter
+        let camera = self.scene.camera_mut();
+        camera.compute_dependents(&*client.state().terrain());
+        let camera::Dependents {
+            cam_pos, ..
+        } = self.scene.camera().dependents();
+        let focus_pos = self.scene.camera().get_focus_pos();
+        let focus_off = focus_pos.map(|e| e.trunc());
+        let cam_pos = cam_pos + focus_off;
+        let underwater = client.state()
+            .terrain()
+            .get(cam_pos.map(|e| e.floor() as i32))
+            .map(|b| b.is_liquid())
+            .unwrap_or(false);
+
         #[cfg(not(target_os = "macos"))]
         {
             // Update mumble positional audio
@@ -261,7 +276,7 @@ impl SessionState {
                         }
                         _ => global_state
                                 .audio
-                                .emit_sfx(sfx_trigger_item, client.position().unwrap_or_default(), Some(1.0), false)
+                                .emit_sfx(sfx_trigger_item, client.position().unwrap_or_default(), Some(1.0), underwater)
                     }
 
 

From 83ee54001e8b6194955c0af4710709073012922a Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Tue, 22 Mar 2022 23:50:38 -0700
Subject: [PATCH 33/71] Ambience slider; fix to utterances

---
 assets/voxygen/i18n/en/hud/settings.ron  |  1 +
 server/src/events/entity_manipulation.rs |  2 +-
 voxygen/src/audio/ambient.rs             | 30 +++++++++---------
 voxygen/src/audio/mod.rs                 | 20 +++++++++---
 voxygen/src/hud/settings_window/sound.rs | 39 +++++++++++++++++++++++-
 voxygen/src/main.rs                      |  1 +
 voxygen/src/scene/mod.rs                 |  8 ++---
 voxygen/src/session/mod.rs               | 25 +++++++--------
 voxygen/src/session/settings_change.rs   |  6 ++++
 voxygen/src/settings/audio.rs            |  2 ++
 10 files changed, 95 insertions(+), 39 deletions(-)

diff --git a/assets/voxygen/i18n/en/hud/settings.ron b/assets/voxygen/i18n/en/hud/settings.ron
index 631bff9d65..daad5949d4 100644
--- a/assets/voxygen/i18n/en/hud/settings.ron
+++ b/assets/voxygen/i18n/en/hud/settings.ron
@@ -116,6 +116,7 @@
         "hud.settings.inactive_master_volume_perc": "Inactive Window Volume",
         "hud.settings.music_volume": "Music Volume",
         "hud.settings.sound_effect_volume": "Sound Effects Volume",
+        "hud.settings.ambience_volume": "Ambience Volume",
         "hud.settings.audio_device": "Audio Device",
         "hud.settings.reset_sound": "Reset to Defaults",
 
diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs
index 2bd1e04a90..cdb9ec07e9 100644
--- a/server/src/events/entity_manipulation.rs
+++ b/server/src/events/entity_manipulation.rs
@@ -91,7 +91,7 @@ pub fn handle_health_change(server: &Server, entity: EcsEntity, change: HealthCh
     // This if statement filters out anything under 5 damage, for DOT ticks
     // TODO: Find a better way to separate direct damage from DOT here
     let damage = -change.amount;
-    if damage > -5.0 {
+    if damage > 5.0 {
         if let Some(agent) = ecs.write_storage::<Agent>().get_mut(entity) {
             agent.inbox.push_front(AgentEvent::Hurt);
         }
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 3cdfaa5bfd..8ea7bd3c2e 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -45,7 +45,7 @@ impl AmbientMgr {
         client: &Client,
         camera: &Camera,
     ) {
-        let sfx_volume = audio.get_sfx_volume();
+        let ambience_volume = audio.get_ambience_volume();
         // iterate through each tag
         for tag in AmbientChannelTag::iter() {
             // check if current conditions necessitate the current tag at all
@@ -72,7 +72,7 @@ impl AmbientMgr {
             } else if audio.get_ambient_channel(tag).is_some() {
                 for index in 0..AmbientChannelTag::iter().len() {
                     // update with sfx volume
-                    audio.ambient_channels[index].set_volume(sfx_volume);
+                    audio.ambient_channels[index].set_volume(ambience_volume);
                     // if current channel's tag is not the current tag, move on to next channel
                     if audio.ambient_channels[index].get_tag() == tag {
                         // maintain: get the correct multiplier of whatever the tag of the current
@@ -157,7 +157,7 @@ impl AmbientMgr {
     }
 
     fn check_rain_necessity(&mut self, client: &Client) -> bool {
-        client.current_weather().rain * 500.0 > 0.0
+        client.current_weather().rain > 0.001
     }
 
     fn check_thunder_necessity(&mut self, client: &Client) -> bool {
@@ -173,12 +173,9 @@ impl AmbientMgr {
         } else {
             (0.0, 0.0)
         };
-
-        // Tree density factors into wind volume. The more trees,
-        // the lower wind volume. The trees make more of an impact
-        // the closer the camera is to the ground.
-        let tree_multiplier =
-            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2)).min(1.0));
+        let tree_multiplier = 1.0
+            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
+                .min(1.0));
 
         return tree_multiplier > 0.1;
     }
@@ -202,7 +199,6 @@ impl AmbientChannel {
             AmbientChannelTag::Leaves => self.get_leaves_volume(client, camera),
         };
 
-        // TODO: make rain diminish with distance above terrain
         target_volume = self.check_camera(state, client, cam_pos, target_volume);
 
         return target_volume;
@@ -271,9 +267,10 @@ impl AmbientChannel {
     fn get_rain_volume(&mut self, client: &Client) -> f32 {
         // multipler at end will have to change depending on how intense rain normally
         // is
+        // TODO: make rain diminish with distance above terrain
         let rain_intensity = client.current_weather().rain * 500.0;
 
-        return rain_intensity;
+        return rain_intensity.min(0.9);
     }
 
     fn get_thunder_volume(&mut self, client: &Client) -> f32 {
@@ -296,11 +293,12 @@ impl AmbientChannel {
             (0.0, 0.0)
         };
 
-        // Tree density factors into wind volume. The more trees,
-        // the lower wind volume. The trees make more of an impact
-        // the closer the camera is to the ground.
-        let tree_multiplier =
-            1.0 - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2)).min(1.0));
+        // Tree density factors into leaves volume. The more trees,
+        // the higher volume. The trees make more of an impact
+        // the closer the camera is to the ground
+        let tree_multiplier = 1.0
+            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
+                .min(1.0));
 
         if tree_multiplier > 0.1 {
             tree_multiplier
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 3b2942c71e..78091460af 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -47,6 +47,7 @@ pub struct AudioFrontend {
     pub sfx_channels: Vec<SfxChannel>,
     pub ui_channels: Vec<UIChannel>,
     pub sfx_volume: f32,
+    pub ambience_volume: f32,
     pub music_volume: f32,
     pub master_volume: f32,
     pub listener: Listener,
@@ -101,6 +102,7 @@ impl AudioFrontend {
             ui_channels,
             ambient_channels: Vec::new(),
             sfx_volume: 1.0,
+            ambience_volume: 1.0,
             music_volume: 1.0,
             master_volume: 1.0,
             listener: Listener::default(),
@@ -131,6 +133,7 @@ impl AudioFrontend {
             ui_channels: Vec::new(),
             ambient_channels: Vec::new(),
             sfx_volume: 1.0,
+            ambience_volume: 1.0,
             music_volume: 1.0,
             master_volume: 1.0,
             listener: Listener::default(),
@@ -462,11 +465,16 @@ impl AudioFrontend {
     // this retrieves the current setting for sfx volume
     pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume * self.master_volume }
 
+    // this retrieves the current setting for ambience volume
+    pub fn get_ambience_volume(&self) -> f32 { self.ambience_volume * self.master_volume }
+
     // this retrieves the current setting for music volume
     pub fn get_music_volume(&self) -> f32 { self.music_volume * self.master_volume }
 
     pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
 
+    pub fn ambience_enabled(&self) -> bool { self.get_ambience_volume() > 0.0 }
+
     pub fn music_enabled(&self) -> bool { self.get_music_volume() > 0.0 }
 
     pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
@@ -475,6 +483,14 @@ impl AudioFrontend {
         self.update_sfx_volumes();
     }
 
+    pub fn set_ambience_volume(&mut self, ambience_volume: f32) {
+        self.ambience_volume = ambience_volume;
+
+        for channel in self.ambient_channels.iter_mut() {
+            channel.set_volume(ambience_volume)
+        }
+    }
+
     pub fn set_music_volume(&mut self, music_volume: f32) {
         self.music_volume = music_volume;
 
@@ -500,10 +516,6 @@ impl AudioFrontend {
         for channel in self.sfx_channels.iter_mut() {
             channel.set_volume(sfx_volume);
         }
-
-        for channel in self.ambient_channels.iter_mut() {
-            channel.set_volume(sfx_volume);
-        }
     }
 
     pub fn stop_ambient_sounds(&mut self) {
diff --git a/voxygen/src/hud/settings_window/sound.rs b/voxygen/src/hud/settings_window/sound.rs
index 96fda99e6f..7d3e1a9503 100644
--- a/voxygen/src/hud/settings_window/sound.rs
+++ b/voxygen/src/hud/settings_window/sound.rs
@@ -31,6 +31,9 @@ widget_ids! {
         sfx_volume_text,
         sfx_volume_slider,
         sfx_volume_number,
+        ambience_volume_text,
+        ambience_volume_slider,
+        ambience_volume_number,
         //audio_device_list,
         //audio_device_text,
         reset_sound_button,
@@ -246,6 +249,40 @@ impl<'a> Widget for Sound<'a> {
         .font_id(self.fonts.cyri.conrod_id)
         .color(TEXT_COLOR)
         .set(state.ids.sfx_volume_number, ui);
+        // Ambience Volume
+        Text::new(self.localized_strings.get("hud.settings.ambience_volume"))
+            .down_from(state.ids.sfx_volume_slider, 10.0)
+            .font_size(self.fonts.cyri.scale(14))
+            .font_id(self.fonts.cyri.conrod_id)
+            .color(TEXT_COLOR)
+            .set(state.ids.ambience_volume_text, ui);
+        // Ambience Volume Slider
+        if let Some(new_val) = ImageSlider::continuous(
+            self.global_state.settings.audio.ambience_volume,
+            0.0,
+            1.0,
+            self.imgs.slider_indicator,
+            self.imgs.slider,
+        )
+        .w_h(104.0, 22.0)
+        .down_from(state.ids.ambience_volume_text, 10.0)
+        .track_breadth(12.0)
+        .slider_length(10.0)
+        .pad_track((5.0, 5.0))
+        .set(state.ids.ambience_volume_slider, ui)
+        {
+            events.push(AdjustAmbienceVolume(new_val));
+        }
+        // Ambience Volume Number
+        Text::new(&format!(
+            "{:2.0}%",
+            self.global_state.settings.audio.ambience_volume * 100.0
+        ))
+        .right_from(state.ids.ambience_volume_slider, 8.0)
+        .font_size(self.fonts.cyri.scale(14))
+        .font_id(self.fonts.cyri.conrod_id)
+        .color(TEXT_COLOR)
+        .set(state.ids.ambience_volume_number, ui);
 
         // Audio Device Selector
         // --------------------------------------------
@@ -279,7 +316,7 @@ impl<'a> Widget for Sound<'a> {
             .w_h(RESET_BUTTONS_WIDTH, RESET_BUTTONS_HEIGHT)
             .hover_image(self.imgs.button_hover)
             .press_image(self.imgs.button_press)
-            .down_from(state.ids.sfx_volume_slider, 12.0)
+            .down_from(state.ids.ambience_volume_slider, 12.0)
             .label(self.localized_strings.get("hud.settings.reset_sound"))
             .label_font_size(self.fonts.cyri.scale(14))
             .label_color(TEXT_COLOR)
diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs
index ff811bd9dc..86c43540aa 100644
--- a/voxygen/src/main.rs
+++ b/voxygen/src/main.rs
@@ -233,6 +233,7 @@ fn main() {
     audio.set_master_volume(settings.audio.master_volume);
     audio.set_music_volume(settings.audio.music_volume);
     audio.set_sfx_volume(settings.audio.sfx_volume);
+    audio.set_ambience_volume(settings.audio.ambience_volume);
 
     // Load the profile.
     let profile = Profile::load(&config_dir);
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index b0ec725bda..4b57e01642 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1103,6 +1103,9 @@ impl Scene {
                 &self.terrain,
                 client,
             );
+        }
+
+        if audio.ambience_enabled() {
             self.ambient_mgr
                 .maintain(audio, scene_data.state, client, &self.camera);
         }
@@ -1110,11 +1113,6 @@ impl Scene {
         if audio.music_enabled() {
             self.music_mgr.maintain(audio, scene_data.state, client);
         }
-
-        // self.ambient_wind_mgr
-        //     .maintain(audio, scene_data.state, client, &self.camera);
-        // self.ambient_rain_mgr
-        //     .maintain(audio, scene_data.state, client, &self.camera);
     }
 
     pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 791e7989c2..73f7eae955 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -175,16 +175,16 @@ impl SessionState {
         self.scene
             .maintain_debug_hitboxes(&client, &global_state.settings, &mut self.hitboxes);
 
-        // All this camera code is just to determine if it's underwater for the sfx filter
+        // All this camera code is just to determine if it's underwater for the sfx
+        // filter
         let camera = self.scene.camera_mut();
         camera.compute_dependents(&*client.state().terrain());
-        let camera::Dependents {
-            cam_pos, ..
-        } = self.scene.camera().dependents();
+        let camera::Dependents { cam_pos, .. } = self.scene.camera().dependents();
         let focus_pos = self.scene.camera().get_focus_pos();
         let focus_off = focus_pos.map(|e| e.trunc());
         let cam_pos = cam_pos + focus_off;
-        let underwater = client.state()
+        let underwater = client
+            .state()
             .terrain()
             .get(cam_pos.map(|e| e.floor() as i32))
             .map(|b| b.is_liquid())
@@ -262,7 +262,7 @@ impl SessionState {
                     let sfx_trigger_item = sfx_triggers.get_key_value(&SfxEvent::from(&inv_event));
 
                     match inv_event {
-                        InventoryUpdateEvent::Dropped 
+                        InventoryUpdateEvent::Dropped
                         | InventoryUpdateEvent::Swapped
                         | InventoryUpdateEvent::Given
                         | InventoryUpdateEvent::Collected(_)
@@ -272,14 +272,15 @@ impl SessionState {
                             global_state
                                 .audio
                                 .emit_sfx_item(sfx_trigger_item, Some(1.0));
-
-                        }
-                        _ => global_state
-                                .audio
-                                .emit_sfx(sfx_trigger_item, client.position().unwrap_or_default(), Some(1.0), underwater)
+                        },
+                        _ => global_state.audio.emit_sfx(
+                            sfx_trigger_item,
+                            client.position().unwrap_or_default(),
+                            Some(1.0),
+                            underwater,
+                        ),
                     }
 
-
                     match inv_event {
                         InventoryUpdateEvent::BlockCollectFailed { pos, reason } => {
                             self.hud.add_failed_block_pickup(
diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs
index b56e3f81c6..825933532a 100644
--- a/voxygen/src/session/settings_change.rs
+++ b/voxygen/src/session/settings_change.rs
@@ -22,6 +22,7 @@ pub enum Audio {
     AdjustInactiveMasterVolume(f32),
     AdjustMusicVolume(f32),
     AdjustSfxVolume(f32),
+    AdjustAmbienceVolume(f32),
     //ChangeAudioDevice(String),
     ResetAudioSettings,
 }
@@ -202,6 +203,11 @@ impl SettingsChange {
 
                         settings.audio.sfx_volume = sfx_volume;
                     },
+                    Audio::AdjustAmbienceVolume(ambience_volume) => {
+                        global_state.audio.set_ambience_volume(ambience_volume);
+
+                        settings.audio.ambience_volume = ambience_volume;
+                    },
                     //Audio::ChangeAudioDevice(name) => {
                     //    global_state.audio.set_device(name.clone());
 
diff --git a/voxygen/src/settings/audio.rs b/voxygen/src/settings/audio.rs
index 2536b914e9..36f2230c17 100644
--- a/voxygen/src/settings/audio.rs
+++ b/voxygen/src/settings/audio.rs
@@ -25,6 +25,7 @@ pub struct AudioSettings {
     pub inactive_master_volume_perc: f32,
     pub music_volume: f32,
     pub sfx_volume: f32,
+    pub ambience_volume: f32,
     pub num_sfx_channels: usize,
     pub num_ui_channels: usize,
 
@@ -39,6 +40,7 @@ impl Default for AudioSettings {
             inactive_master_volume_perc: 0.5,
             music_volume: 0.3,
             sfx_volume: 0.6,
+            ambience_volume: 0.6,
             num_sfx_channels: 60,
             num_ui_channels: 10,
             output: AudioOutput::Automatic,

From 54f958acc7b886aa9745fb9c82f542d1836605a7 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Mon, 28 Mar 2022 15:22:57 +0200
Subject: [PATCH 34/71] more correct occlusion

---
 assets/voxygen/shaders/clouds-frag.glsl |  4 +-
 client/src/lib.rs                       | 79 ++++++-----------------
 common/net/src/msg/server.rs            |  4 +-
 common/src/weather.rs                   | 73 ++++++++++++++++++++-
 common/state/src/state.rs               | 14 +++-
 server/src/weather/mod.rs               |  9 +--
 server/src/weather/sim.rs               | 20 ++----
 server/src/weather/sync.rs              | 13 ++--
 server/src/weather/tick.rs              | 10 ++-
 voxygen/src/audio/ambient.rs            | 10 +--
 voxygen/src/audio/music.rs              |  2 +-
 voxygen/src/hud/mod.rs                  |  2 +-
 voxygen/src/scene/lod.rs                |  2 +-
 voxygen/src/scene/mod.rs                | 40 ++++++------
 voxygen/src/scene/terrain.rs            | 85 ++++++++++++++++++++++++-
 15 files changed, 246 insertions(+), 121 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 93a5696882..41f9f46e61 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -102,7 +102,7 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         vec3 old_color = color.rgb;
 
-        // If this value is changed also change it in voxygen/src/scene/mod.rs
+        // If this value is changed also change it in common/src/weather.rs
         float fall_rate = 70.0;
         dir.xy += wind_vel * dir.z / fall_rate;
         dir = normalize(dir);
@@ -118,7 +118,6 @@ void main() {
             rain_dist *= 0.3;
 
             vec2 drop_density = vec2(30, 1);
-            vec2 drop_size = vec2(0.0008, 0.05);
 
             vec2 rain_pos = (view_pos * rain_dist);
             rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
@@ -140,6 +139,7 @@ void main() {
                 break;
             }
             float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+            vec2 drop_size = vec2(0.0008, 0.05);
 
             if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                 continue;
diff --git a/client/src/lib.rs b/client/src/lib.rs
index dec60c0e45..d09e09f493 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -48,7 +48,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
     uid::{Uid, UidAllocator},
     vol::RectVolSize,
-    weather::{self, Weather},
+    weather::{self, Weather, WeatherGrid},
 };
 #[cfg(feature = "tracy")] use common_base::plot;
 use common_base::{prof_span, span};
@@ -153,24 +153,23 @@ pub struct SiteInfoRich {
 }
 
 struct WeatherLerp {
-    old: (Grid<Weather>, Instant),
-    new: (Grid<Weather>, Instant),
-    current: Grid<Weather>,
+    old: (WeatherGrid, Instant),
+    new: (WeatherGrid, Instant),
 }
 
 impl WeatherLerp {
-    fn weather_update(&mut self, weather: Grid<Weather>) {
+    fn weather_update(&mut self, weather: WeatherGrid) {
         self.old = mem::replace(&mut self.new, (weather, Instant::now()));
     }
 
-    fn update(&mut self) {
+    fn update(&mut self, to_update: &mut WeatherGrid) {
         let old = &self.old.0;
         let new = &self.new.0;
         if new.size() == Vec2::zero() {
             return;
         }
-        if self.current.size() != new.size() {
-            self.current = new.clone();
+        if to_update.size() != new.size() {
+            *to_update = new.clone();
         }
         if old.size() == new.size() {
             // Assume updates are regular
@@ -178,9 +177,12 @@ impl WeatherLerp {
                 / self.new.1.duration_since(self.old.1).as_secs_f32())
             .clamp(0.0, 1.0);
 
-            old.iter().zip(new.iter()).for_each(|((p, old), (_, new))| {
-                self.current[p] = Weather::lerp(old, new, t);
-            });
+            to_update
+                .iter_mut()
+                .zip(old.iter().zip(new.iter()))
+                .for_each(|((_, current), ((_, old), (_, new)))| {
+                    *current = Weather::lerp(old, new, t);
+                });
         }
     }
 }
@@ -188,15 +190,8 @@ impl WeatherLerp {
 impl Default for WeatherLerp {
     fn default() -> Self {
         Self {
-            old: (
-                Grid::new(Vec2::new(0, 0), Weather::default()),
-                Instant::now(),
-            ),
-            new: (
-                Grid::new(Vec2::new(0, 0), Weather::default()),
-                Instant::now(),
-            ),
-            current: Grid::new(Vec2::new(0, 0), Weather::default()),
+            old: (WeatherGrid::new(Vec2::broadcast(0)), Instant::now()),
+            new: (WeatherGrid::new(Vec2::broadcast(0)), Instant::now()),
         }
     }
 }
@@ -1465,47 +1460,12 @@ impl Client {
             .map(|v| v.0)
     }
 
-    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather.current }
-
-    pub fn current_weather(&self) -> Weather {
+    pub fn weather_at_player(&self) -> Weather {
         self.position()
-            .map(|wpos| self.current_weather_wpos(wpos.xy()))
+            .map(|wpos| self.state.weather_at(wpos.xy()))
             .unwrap_or_default()
     }
 
-    pub fn current_weather_wpos(&self, wpos: Vec2<f32>) -> Weather {
-        let cell_pos = wpos / ((TerrainChunkSize::RECT_SIZE * weather::CHUNKS_PER_CELL).as_());
-        let rpos = cell_pos.map(|e| e.fract());
-        let cell_pos = cell_pos.map(|e| e.floor());
-
-        let wpos = cell_pos.as_::<i32>();
-        Weather::lerp(
-            &Weather::lerp(
-                self.weather
-                    .current
-                    .get(wpos)
-                    .unwrap_or(&Weather::default()),
-                self.weather
-                    .current
-                    .get(wpos + Vec2::unit_x())
-                    .unwrap_or(&Weather::default()),
-                rpos.x,
-            ),
-            &Weather::lerp(
-                self.weather
-                    .current
-                    .get(wpos + Vec2::unit_x())
-                    .unwrap_or(&Weather::default()),
-                self.weather
-                    .current
-                    .get(wpos + Vec2::one())
-                    .unwrap_or(&Weather::default()),
-                rpos.x,
-            ),
-            rpos.y,
-        )
-    }
-
     pub fn current_chunk(&self) -> Option<Arc<TerrainChunk>> {
         let chunk_pos = Vec2::from(self.position()?)
             .map2(TerrainChunkSize::RECT_SIZE, |e: f32, sz| {
@@ -1724,6 +1684,9 @@ impl Client {
             self.invite = None;
         }
 
+        // TODO: put this somewhere else? Otherwise update comments here.
+        self.weather.update(&mut self.state.weather_grid_mut());
+
         // Lerp towards the target time of day - this ensures a smooth transition for
         // large jumps in TimeOfDay such as when using /time
         if let Some(target_tod) = self.target_time_of_day {
@@ -1751,8 +1714,6 @@ impl Client {
 
         // 5) Terrain
         self.tick_terrain()?;
-        // TODO: put this somewhere else?
-        self.weather.update();
 
         // Send a ping to the server once every second
         if self.state.get_time() - self.last_server_ping > 1. {
diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs
index 1515648414..8c119a7820 100644
--- a/common/net/src/msg/server.rs
+++ b/common/net/src/msg/server.rs
@@ -15,7 +15,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeId, TradeResult},
     uid::Uid,
     uuid::Uuid,
-    weather::Weather,
+    weather::{Weather, WeatherGrid},
 };
 use hashbrown::HashMap;
 use serde::{Deserialize, Serialize};
@@ -198,7 +198,7 @@ pub enum ServerGeneral {
     /// Economic information about sites
     SiteEconomy(EconomyInfo),
     MapMarker(comp::MapMarkerUpdate),
-    WeatherUpdate(common::grid::Grid<Weather>),
+    WeatherUpdate(WeatherGrid),
 }
 
 impl ServerGeneral {
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 750fc0c68a..9a20d79d92 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -1,10 +1,11 @@
 use std::fmt;
 
 use serde::{Deserialize, Serialize};
-use vek::{Lerp, Vec2};
+use vek::{Lerp, Vec2, Vec3};
 
-pub const CHUNKS_PER_CELL: u32 = 16;
-// Weather::default is Clear, 0 degrees C and no wind
+use crate::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize};
+
+/// Weather::default is Clear, 0 degrees C and no wind
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
 pub struct Weather {
     /// Clouds currently in the area between 0 and 1
@@ -39,6 +40,13 @@ impl Weather {
             wind: Vec2::<f32>::lerp(from.wind, to.wind, t),
         }
     }
+
+    // Get the rain direction for this weather
+    pub fn rain_dir(&self) -> Vec3<f32> {
+        // If this value is changed also change it in cloud-frag.glsl
+        const FALL_RATE: f32 = 70.0;
+        (-Vec3::unit_z() + self.wind / FALL_RATE).normalized()
+    }
 }
 
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
@@ -59,3 +67,62 @@ impl fmt::Display for WeatherKind {
         }
     }
 }
+
+pub const CHUNKS_PER_CELL: u32 = 16;
+
+pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
+
+/// How often the weather is updated, in seconds
+pub const WEATHER_DT: f32 = 5.0;
+
+// pub const MAX_WIND_SPEED: f32 = CELL_SIZE as f32 / WEATHER_DT;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct WeatherGrid {
+    weather: Grid<Weather>,
+}
+
+impl WeatherGrid {
+    pub fn new(size: Vec2<u32>) -> Self {
+        Self {
+            weather: Grid::new(size.as_(), Weather::default()),
+        }
+    }
+
+    pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &Weather)> { self.weather.iter() }
+
+    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut Weather)> {
+        self.weather.iter_mut()
+    }
+
+    pub fn size(&self) -> Vec2<u32> { self.weather.size().as_() }
+
+    /// Get the weather at a given world position by doing bilinear
+    /// interpolation between four cells.
+    pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather {
+        let cell_pos = wpos / CELL_SIZE as f32;
+        let rpos = cell_pos.map(|e| e.fract());
+        let cell_pos = cell_pos.map(|e| e.floor());
+
+        let wpos = cell_pos.as_::<i32>();
+        Weather::lerp(
+            &Weather::lerp(
+                self.weather.get(wpos).unwrap_or(&Weather::default()),
+                self.weather
+                    .get(wpos + Vec2::unit_x())
+                    .unwrap_or(&Weather::default()),
+                rpos.x,
+            ),
+            &Weather::lerp(
+                self.weather
+                    .get(wpos + Vec2::unit_x())
+                    .unwrap_or(&Weather::default()),
+                self.weather
+                    .get(wpos + Vec2::one())
+                    .unwrap_or(&Weather::default()),
+                rpos.x,
+            ),
+            rpos.y,
+        )
+    }
+}
diff --git a/common/state/src/state.rs b/common/state/src/state.rs
index 6697f30394..9cd166a80d 100644
--- a/common/state/src/state.rs
+++ b/common/state/src/state.rs
@@ -21,6 +21,7 @@ use common::{
     time::DayPeriod,
     trade::Trades,
     vol::{ReadVol, WriteVol},
+    weather::{Weather, WeatherGrid},
 };
 use common_base::span;
 use common_ecs::{PhysicsMetrics, SysMetrics};
@@ -206,6 +207,7 @@ impl State {
         // Register synced resources used by the ECS.
         ecs.insert(TimeOfDay(0.0));
         ecs.insert(Calendar::default());
+        ecs.insert(WeatherGrid::new(Vec2::zero()));
 
         // Register unsynced resources used by the ECS.
         ecs.insert(Time(0.0));
@@ -346,13 +348,23 @@ impl State {
     /// last game tick.
     pub fn terrain_changes(&self) -> Fetch<TerrainChanges> { self.ecs.read_resource() }
 
+    /// Get a reference the current in-game weather grid.
+    pub fn weather_grid(&self) -> Fetch<WeatherGrid> { self.ecs.read_resource() }
+
+    /// Get a mutable reference the current in-game weather grid.
+    pub fn weather_grid_mut(&mut self) -> FetchMut<WeatherGrid> { self.ecs.write_resource() }
+
+    /// Get the current weather at a position.
+    pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
+        self.weather_grid().get_interpolated(pos)
+    }
+
     /// Get the current in-game time of day.
     ///
     /// Note that this should not be used for physics, animations or other such
     /// localised timings.
     pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
 
-    /// Get the current in-game day period (period of the day/night cycle)
     /// Get the current in-game day period (period of the day/night cycle)
     pub fn get_day_period(&self) -> DayPeriod { self.get_time_of_day().into() }
 
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index 35137620d9..2c9ddbe0d3 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -1,4 +1,4 @@
-use common::weather::CHUNKS_PER_CELL;
+use common::weather::{WeatherGrid, CHUNKS_PER_CELL, WEATHER_DT};
 use common_ecs::{dispatch, System};
 use common_state::State;
 use specs::DispatcherBuilder;
@@ -18,17 +18,18 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
 pub fn init(state: &mut State, world: &world::World) {
     // How many chunks wide a weather cell is.
     // 16 here means that a weather cell is 16x16 chunks.
-    let sim = sim::WeatherSim::new(world.sim().get_size() / CHUNKS_PER_CELL, world);
+    let weather_size = world.sim().get_size() / CHUNKS_PER_CELL;
+    let sim = sim::WeatherSim::new(weather_size, world);
     state.ecs_mut().insert(sim);
     // Tick weather every 2 seconds
     state
         .ecs_mut()
         .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(
-            sim::DT,
+            WEATHER_DT,
         )));
     state
         .ecs_mut()
         .insert(SysScheduler::<sync::Sys>::every(Duration::from_secs_f32(
-            sim::DT,
+            WEATHER_DT,
         )));
 }
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index fac7b330a7..589fe19d7f 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -3,7 +3,7 @@ use common::{
     resources::TimeOfDay,
     terrain::TerrainChunkSize,
     vol::RectVolSize,
-    weather::{Weather, CHUNKS_PER_CELL},
+    weather::{Weather, WeatherGrid, CELL_SIZE, CHUNKS_PER_CELL},
 };
 use itertools::Itertools;
 use noise::{NoiseFn, SuperSimplex, Turbulence};
@@ -43,18 +43,9 @@ pub struct WeatherInfo {
 pub struct WeatherSim {
     cells: Grid<Cell>,       // The variables used for simulation
     consts: Grid<Constants>, // The constants from the world used for simulation
-    weather: Grid<Weather>,  // The current weather.
     info: Grid<WeatherInfo>,
 }
 
-/*
-const MAX_WIND_SPEED: f32 = 128.0;
-*/
-pub(crate) const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
-
-/// How often the weather is updated, in seconds
-pub(crate) const DT: f32 = 5.0; // CELL_SIZE as f32 / MAX_WIND_SPEED;
-
 fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
     if points.len() < 3 {
         return None;
@@ -140,7 +131,6 @@ impl WeatherSim {
                     })
                     .collect_vec(),
             ),
-            weather: Grid::new(size, Weather::default()),
             info: Grid::new(size, WeatherInfo::default()),
         };
         this.cells.iter_mut().for_each(|(point, cell)| {
@@ -150,8 +140,6 @@ impl WeatherSim {
         this
     }
 
-    pub fn get_weather(&self) -> &Grid<Weather> { &self.weather }
-
     /*
     fn get_cell(&self, p: Vec2<i32>, time: f64) -> Cell {
         *self.cells.get(p).unwrap_or(&sample_cell(p, time))
@@ -160,7 +148,7 @@ impl WeatherSim {
 
     // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
     // Time step is cell size / maximum wind speed
-    pub fn tick(&mut self, time_of_day: &TimeOfDay) {
+    pub fn tick(&mut self, time_of_day: &TimeOfDay, out: &mut WeatherGrid) {
         let time = time_of_day.0;
 
         let base_nz = Turbulence::new(
@@ -173,7 +161,7 @@ impl WeatherSim {
 
         let rain_nz = SuperSimplex::new();
 
-        for (point, cell) in self.weather.iter_mut() {
+        for (point, cell) in out.iter_mut() {
             let wpos = cell_to_wpos(point);
 
             let pos = wpos.as_::<f64>() + time as f64 * 0.1;
@@ -391,4 +379,6 @@ impl WeatherSim {
         }
         */
     }
+
+    pub fn size(&self) -> Vec2<u32> { self.cells.size().as_() }
 }
diff --git a/server/src/weather/sync.rs b/server/src/weather/sync.rs
index 8114c33cfd..c7e875f8bc 100644
--- a/server/src/weather/sync.rs
+++ b/server/src/weather/sync.rs
@@ -1,3 +1,4 @@
+use common::weather::WeatherGrid;
 use common_ecs::{Origin, Phase, System};
 use common_net::msg::ServerGeneral;
 use specs::{Join, ReadExpect, ReadStorage, Write};
@@ -11,7 +12,7 @@ pub struct Sys;
 
 impl<'a> System<'a> for Sys {
     type SystemData = (
-        ReadExpect<'a, WeatherSim>,
+        ReadExpect<'a, WeatherGrid>,
         Write<'a, SysScheduler<Self>>,
         ReadStorage<'a, Client>,
     );
@@ -20,14 +21,16 @@ impl<'a> System<'a> for Sys {
     const ORIGIN: Origin = Origin::Server;
     const PHASE: Phase = Phase::Create;
 
-    fn run(_job: &mut common_ecs::Job<Self>, (sim, mut scheduler, clients): Self::SystemData) {
+    fn run(
+        _job: &mut common_ecs::Job<Self>,
+        (weather_grid, mut scheduler, clients): Self::SystemData,
+    ) {
         if scheduler.should_run() {
             let mut lazy_msg = None;
             for client in clients.join() {
                 if lazy_msg.is_none() {
-                    lazy_msg = Some(
-                        client.prepare(ServerGeneral::WeatherUpdate(sim.get_weather().clone())),
-                    );
+                    lazy_msg =
+                        Some(client.prepare(ServerGeneral::WeatherUpdate(weather_grid.clone())));
                 }
                 lazy_msg.as_ref().map(|msg| client.send_prepared(msg));
             }
diff --git a/server/src/weather/tick.rs b/server/src/weather/tick.rs
index 06285af235..db5154b73c 100644
--- a/server/src/weather/tick.rs
+++ b/server/src/weather/tick.rs
@@ -1,4 +1,4 @@
-use common::resources::TimeOfDay;
+use common::{resources::TimeOfDay, weather::WeatherGrid};
 use common_ecs::{Origin, Phase, System};
 use specs::{Read, Write, WriteExpect};
 
@@ -13,6 +13,7 @@ impl<'a> System<'a> for Sys {
     type SystemData = (
         Read<'a, TimeOfDay>,
         WriteExpect<'a, WeatherSim>,
+        WriteExpect<'a, WeatherGrid>,
         Write<'a, SysScheduler<Self>>,
     );
 
@@ -22,10 +23,13 @@ impl<'a> System<'a> for Sys {
 
     fn run(
         _job: &mut common_ecs::Job<Self>,
-        (game_time, mut sim, mut scheduler): Self::SystemData,
+        (game_time, mut sim, mut grid, mut scheduler): Self::SystemData,
     ) {
         if scheduler.should_run() {
-            sim.tick(&*game_time);
+            if grid.size() != sim.size() {
+                *grid = WeatherGrid::new(sim.size());
+            }
+            sim.tick(&game_time, &mut grid);
         }
     }
 }
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 8ea7bd3c2e..d254f32071 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -157,11 +157,11 @@ impl AmbientMgr {
     }
 
     fn check_rain_necessity(&mut self, client: &Client) -> bool {
-        client.current_weather().rain > 0.001
+        client.weather_at_player().rain > 0.001
     }
 
     fn check_thunder_necessity(&mut self, client: &Client) -> bool {
-        client.current_weather().rain * 500.0 > 0.7
+        client.weather_at_player().rain * 500.0 > 0.7
     }
 
     fn check_leaves_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
@@ -238,7 +238,7 @@ impl AmbientChannel {
         let focus_off = camera.get_focus_pos().map(f32::trunc);
         let cam_pos = camera.dependents().cam_pos + focus_off;
         // Float from around -30.0 to 30.0
-        let client_wind_speed_sq = client.current_weather().wind.magnitude_squared();
+        let client_wind_speed_sq = client.weather_at_player().wind.magnitude_squared();
 
         let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
             (chunk.meta().alt(), chunk.meta().tree_density())
@@ -268,13 +268,13 @@ impl AmbientChannel {
         // multipler at end will have to change depending on how intense rain normally
         // is
         // TODO: make rain diminish with distance above terrain
-        let rain_intensity = client.current_weather().rain * 500.0;
+        let rain_intensity = client.weather_at_player().rain * 500.0;
 
         return rain_intensity.min(0.9);
     }
 
     fn get_thunder_volume(&mut self, client: &Client) -> f32 {
-        let thunder_intensity = client.current_weather().rain * 500.0;
+        let thunder_intensity = client.weather_at_player().rain * 500.0;
 
         if thunder_intensity < 0.7 {
             0.0
diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs
index 55c2c8e914..e56e83ea07 100644
--- a/voxygen/src/audio/music.rs
+++ b/voxygen/src/audio/music.rs
@@ -332,7 +332,7 @@ impl MusicMgr {
 
         let is_dark = (state.get_day_period().is_dark()) as bool;
         let current_period_of_day = Self::get_current_day_period(is_dark);
-        let current_weather = client.current_weather();
+        let current_weather = client.weather_at_player();
         let current_biome = client.current_biome();
         let current_site = client.current_site();
 
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index f335c5a29d..f0f25cc281 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -2388,7 +2388,7 @@ impl Hud {
                 .font_size(self.fonts.cyri.scale(14))
                 .set(self.ids.time, ui_widgets);
 
-            let weather = client.current_weather();
+            let weather = client.weather_at_player();
             Text::new(&format!(
                 "Weather({kind:.5}): {{cloud: {cloud:.5}, rain: {rain:.5}, wind: <{wind_x:.5}, \
                  {wind_y:.2}>}}",
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 5c8cd58079..410b1529f7 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -182,7 +182,7 @@ impl Lod {
             }
         }
         // Update weather texture
-        let weather = client.get_weather();
+        let weather = client.state().weather_grid();
         let size = weather.size().as_::<u32>();
         renderer.update_texture(
             &self.data.weather,
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 4b57e01642..e047d8b3f7 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -692,7 +692,10 @@ impl Scene {
             scene_data.ambiance,
             self.camera.get_mode(),
             scene_data.sprite_render_distance as f32 - 20.0,
-            client.current_weather_wpos(cam_pos.xy()).wind,
+            client
+                .state()
+                .weather_at(focus_off.xy() + cam_pos.xy())
+                .wind,
         )]);
         renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
         renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
@@ -704,13 +707,14 @@ impl Scene {
         self.debug.maintain(renderer);
 
         // Maintain the terrain.
-        let (_visible_bounds, visible_light_volume, visible_psr_bounds) = self.terrain.maintain(
-            renderer,
-            scene_data,
-            focus_pos,
-            self.loaded_distance,
-            &self.camera,
-        );
+        let (_visible_bounds, visible_light_volume, visible_psr_bounds, visible_occlusion_volume) =
+            self.terrain.maintain(
+                renderer,
+                scene_data,
+                focus_pos,
+                self.loaded_distance,
+                &self.camera,
+            );
 
         // Maintain the figures.
         let _figure_bounds = self.figure_mgr.maintain(
@@ -752,7 +756,8 @@ impl Scene {
             * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
 
         let directed_mats = |d_view_mat: math::Mat4<f32>,
-                             d_dir: math::Vec3<f32>|
+                             d_dir: math::Vec3<f32>,
+                             volume: &Vec<math::Vec3<f32>>|
          -> (Mat4<f32>, Mat4<f32>) {
             // NOTE: Light view space, right-handed.
             let v_p_orig = math::Vec3::from(d_view_mat * math::Vec4::from_direction(new_dir));
@@ -765,7 +770,7 @@ impl Scene {
             // (right-handed).
             let bounds1 = math::fit_psr(
                 view_mat.map_cols(math::Vec4::from),
-                visible_light_volume.iter().copied(),
+                volume.iter().copied(),
                 math::Vec4::homogenized,
             );
             let n_e = f64::from(-bounds1.max.z);
@@ -997,17 +1002,15 @@ impl Scene {
             )
         };
 
-        let weather = client.current_weather_wpos(focus_off.xy() + cam_pos.xy());
+        let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
         if true || weather.rain > 0.001
         // TODO: check if rain map mode is on
         {
-            // If this value is changed also change it in cloud-frag.glsl
-            const FALL_RATE: f32 = 70.0;
-            let rain_dir =
-                math::Vec3::from(-Vec3::unit_z() + weather.wind / FALL_RATE).normalized();
+            let rain_dir = math::Vec3::from(weather.rain_dir());
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_dir, up);
 
-            let (shadow_mat, texture_mat) = directed_mats(rain_view_mat, rain_dir);
+            let (shadow_mat, texture_mat) =
+                directed_mats(rain_view_mat, rain_dir, &visible_occlusion_volume);
 
             let rain_occlusion_locals = RainOcclusionLocals::new(shadow_mat, texture_mat);
             renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
@@ -1029,7 +1032,8 @@ impl Scene {
             let mut directed_shadow_mats = Vec::with_capacity(6);
 
             let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up);
-            let (shadow_mat, texture_mat) = directed_mats(light_view_mat, directed_light_dir);
+            let (shadow_mat, texture_mat) =
+                directed_mats(light_view_mat, directed_light_dir, &visible_light_volume);
 
             let shadow_locals = ShadowLocals::new(shadow_mat, texture_mat);
 
@@ -1166,7 +1170,7 @@ impl Scene {
                 prof_span!("rain occlusion");
                 if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
                     self.terrain
-                        .render_shadows(&mut occlusion_pass.draw_terrain_shadows(), focus_pos);
+                        .render_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
 
                     self.figure_mgr.render_shadows(
                         &mut occlusion_pass.draw_figure_shadows(),
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 35f5cee20b..599337dfcc 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -812,7 +812,13 @@ impl<V: RectRasterableVol> Terrain<V> {
         focus_pos: Vec3<f32>,
         loaded_distance: f32,
         camera: &Camera,
-    ) -> (Aabb<f32>, Vec<math::Vec3<f32>>, math::Aabr<f32>) {
+    ) -> (
+        // TODO: Better return type?
+        Aabb<f32>,
+        Vec<math::Vec3<f32>>,
+        math::Aabr<f32>,
+        Vec<math::Vec3<f32>>,
+    ) {
         let camera::Dependents {
             view_mat,
             proj_mat_treeculler,
@@ -1395,11 +1401,56 @@ impl<V: RectRasterableVol> Terrain<V> {
             })
         };
         drop(guard);
+        span!(guard, "Rain occlusion magic");
+        let weather = scene_data.state.weather_at(focus_pos.xy());
+        let visible_occlusion_volume = if weather.rain > 0.0 {
+            let occlusion_box = Aabb {
+                min: visible_bounding_box
+                    .min
+                    .map2(focus_pos - 10.0, |a, b| a.max(b)),
+                max: visible_bounding_box
+                    .max
+                    .map2(focus_pos + 10.0, |a, b| a.min(b)),
+            };
+            let visible_bounding_box = math::Aabb::<f32> {
+                min: math::Vec3::from(occlusion_box.min - focus_off),
+                max: math::Vec3::from(occlusion_box.max - focus_off),
+            };
+            let visible_bounds_fine = math::Aabb {
+                min: math::Vec3::from(visible_bounding_box.min.as_::<f64>()),
+                max: math::Vec3::from(visible_bounding_box.max.as_::<f64>()),
+            };
+            // TODO: move out shared calculations
+            let inv_proj_view =
+                math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
+                    .as_::<f64>()
+                    .inverted();
 
+            let ray_direction = math::Vec3::<f32>::from(weather.rain_dir());
+
+            // NOTE: We use proj_mat_treeculler here because
+            // calc_focused_light_volume_points makes the assumption that the
+            // near plane lies before the far plane.
+            let visible_occlusion_volume = math::calc_focused_light_volume_points(
+                inv_proj_view,
+                ray_direction.as_::<f64>(),
+                visible_bounds_fine,
+                1e-6,
+            )
+            .map(|v| v.as_::<f32>())
+            .collect::<Vec<_>>();
+
+            visible_occlusion_volume
+        } else {
+            Vec::new()
+        };
+
+        drop(guard);
         (
             visible_bounding_box,
             visible_light_volume,
             visible_psr_bounds,
+            visible_occlusion_volume,
         )
     }
 
@@ -1452,6 +1503,38 @@ impl<V: RectRasterableVol> Terrain<V> {
             .for_each(|(model, locals)| drawer.draw(model, locals));
     }
 
+    pub fn render_occlusion<'a>(
+        &'a self,
+        drawer: &mut TerrainShadowDrawer<'_, 'a>,
+        focus_pos: Vec3<f32>,
+    ) {
+        span!(_guard, "render_occlusion", "Terrain::render_occlusion");
+        let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
+            (e as i32).div_euclid(sz as i32)
+        });
+        // For rain occlusion we only need to render the closest chunks.
+        // TODO: Is this a good value?
+        const RAIN_OCCLUSION_CHUNKS: usize = 16;
+        let chunk_iter = Spiral2d::new()
+            .filter_map(|rpos| {
+                let pos = focus_chunk + rpos;
+                self.chunks.get(&pos)
+            })
+            .take(self.chunks.len().min(RAIN_OCCLUSION_CHUNKS));
+
+        chunk_iter
+            // Find a way to keep this?
+            // .filter(|chunk| chunk.can_shadow_sun())
+            .filter_map(|chunk| {
+                // TODO: Should the fuid model also be considered here?
+                chunk
+                    .opaque_model
+                    .as_ref()
+                    .map(|model| (model, &chunk.locals))
+            })
+            .for_each(|(model, locals)| drawer.draw(model, locals));
+    }
+
     pub fn chunks_for_point_shadows(
         &self,
         focus_pos: Vec3<f32>,

From 6059a30f276420f32e8ecf947440e7e5a25df6e6 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Wed, 30 Mar 2022 13:28:03 -0700
Subject: [PATCH 35/71] Rain diminishes with distance above terrain

---
 voxygen/src/audio/ambient.rs | 35 +++++++++++++++++++++++++++++------
 1 file changed, 29 insertions(+), 6 deletions(-)

diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index d254f32071..437d6d2108 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -51,7 +51,7 @@ impl AmbientMgr {
             // check if current conditions necessitate the current tag at all
             let should_create: bool = match tag {
                 AmbientChannelTag::Wind => self.check_wind_necessity(client, camera),
-                AmbientChannelTag::Rain => self.check_rain_necessity(client),
+                AmbientChannelTag::Rain => self.check_rain_necessity(client, camera),
                 AmbientChannelTag::Thunder => self.check_thunder_necessity(client),
                 AmbientChannelTag::Leaves => self.check_leaves_necessity(client, camera),
             };
@@ -156,8 +156,19 @@ impl AmbientMgr {
         return alt_multiplier * tree_multiplier > 0.0;
     }
 
-    fn check_rain_necessity(&mut self, client: &Client) -> bool {
-        client.weather_at_player().rain > 0.001
+    fn check_rain_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let terrain_alt = if let Some(chunk) = client.current_chunk() {
+            chunk.meta().alt()
+        } else {
+            0.0
+        };
+        // make rain diminish with camera distance above terrain
+        let camera_multiplier = 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
+
+        client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0
     }
 
     fn check_thunder_necessity(&mut self, client: &Client) -> bool {
@@ -192,7 +203,7 @@ impl AmbientChannel {
             // Get target volume of wind
             AmbientChannelTag::Wind => self.get_wind_volume(client, camera),
             // Get target volume of rain
-            AmbientChannelTag::Rain => self.get_rain_volume(client),
+            AmbientChannelTag::Rain => self.get_rain_volume(client, camera),
             // Get target volume of thunder
             AmbientChannelTag::Thunder => self.get_thunder_volume(client),
             // Get target volume of leaves
@@ -264,11 +275,23 @@ impl AmbientChannel {
             * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
     }
 
-    fn get_rain_volume(&mut self, client: &Client) -> f32 {
+    fn get_rain_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
         // multipler at end will have to change depending on how intense rain normally
         // is
+
         // TODO: make rain diminish with distance above terrain
-        let rain_intensity = client.weather_at_player().rain * 500.0;
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let terrain_alt = if let Some(chunk) = client.current_chunk() {
+            chunk.meta().alt()
+        } else {
+            0.0
+        };
+        // make rain diminish with camera distance above terrain
+        let camera_multiplier = 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
+
+        let rain_intensity = (client.weather_at_player().rain * 500.0) * camera_multiplier;
 
         return rain_intensity.min(0.9);
     }

From 6d40caed23ade19fa82e362913c67d38af83644c Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 30 Mar 2022 19:59:39 +0200
Subject: [PATCH 36/71] wind adjust

---
 server/src/weather/sim.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 589fe19d7f..45421dc7c5 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -176,9 +176,9 @@ impl WeatherSim {
             cell.cloud = (1.0 - pressure) * 0.5;
             cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
             cell.wind = Vec2::new(
-                rain_nz.get(spos.into_array()) as f32,
-                rain_nz.get((spos + 1.0).into_array()) as f32,
-            ) * 250.0
+                rain_nz.get(spos.into_array()).powi(3) as f32,
+                rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
+            ) * 200.0
                 * (1.0 - pressure);
         }
         /*

From 08b098978973d86b1c4da4b2c7a1b3b8d391feb8 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 6 Apr 2022 14:00:06 +0200
Subject: [PATCH 37/71] add slider for rain map resolution

---
 assets/voxygen/i18n/en/hud/settings.ron  |  1 +
 client/src/lib.rs                        |  2 +-
 common/net/src/msg/server.rs             |  2 +-
 voxygen/src/hud/mod.rs                   |  1 -
 voxygen/src/hud/settings_window/video.rs | 45 ++++++++++++++-
 voxygen/src/lib.rs                       |  3 +-
 voxygen/src/render/mod.rs                |  5 ++
 voxygen/src/render/renderer.rs           | 73 +++++++++++++++---------
 voxygen/src/session/mod.rs               |  4 +-
 9 files changed, 101 insertions(+), 35 deletions(-)

diff --git a/assets/voxygen/i18n/en/hud/settings.ron b/assets/voxygen/i18n/en/hud/settings.ron
index daad5949d4..93d1656faa 100644
--- a/assets/voxygen/i18n/en/hud/settings.ron
+++ b/assets/voxygen/i18n/en/hud/settings.ron
@@ -106,6 +106,7 @@
         "hud.settings.shadow_rendering_mode.cheap": "Cheap",
         "hud.settings.shadow_rendering_mode.map": "Map",
         "hud.settings.shadow_rendering_mode.map.resolution": "Resolution",
+        "hud.settings.rain_occlusion.resolution": "Rain Occlusion Resolution",
         "hud.settings.lod_detail": "LoD Detail",
         "hud.settings.save_window_size": "Save window size",
         "hud.settings.reset_graphics": "Reset to Defaults",
diff --git a/client/src/lib.rs b/client/src/lib.rs
index d09e09f493..f22705089b 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -48,7 +48,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
     uid::{Uid, UidAllocator},
     vol::RectVolSize,
-    weather::{self, Weather, WeatherGrid},
+    weather::{Weather, WeatherGrid},
 };
 #[cfg(feature = "tracy")] use common_base::plot;
 use common_base::{prof_span, span};
diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs
index 8c119a7820..be95b5ddbe 100644
--- a/common/net/src/msg/server.rs
+++ b/common/net/src/msg/server.rs
@@ -15,7 +15,7 @@ use common::{
     trade::{PendingTrade, SitePrices, TradeId, TradeResult},
     uid::Uid,
     uuid::Uuid,
-    weather::{Weather, WeatherGrid},
+    weather::WeatherGrid,
 };
 use hashbrown::HashMap;
 use serde::{Deserialize, Serialize};
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index f0f25cc281..10a21abbab 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -101,7 +101,6 @@ use common::{
     uid::Uid,
     util::{srgba_to_linear, Dir},
     vol::RectRasterableVol,
-    weather::Weather,
 };
 use common_base::{prof_span, span};
 use common_net::{
diff --git a/voxygen/src/hud/settings_window/video.rs b/voxygen/src/hud/settings_window/video.rs
index 3bf10af9ea..6667abaa2e 100644
--- a/voxygen/src/hud/settings_window/video.rs
+++ b/voxygen/src/hud/settings_window/video.rs
@@ -116,6 +116,9 @@ widget_ids! {
         shadow_mode_map_resolution_text,
         shadow_mode_map_resolution_slider,
         shadow_mode_map_resolution_value,
+        rain_map_resolution_text,
+        rain_map_resolution_slider,
+        rain_map_resolution_value,
         save_window_size_button,
 
     }
@@ -1116,11 +1119,51 @@ impl<'a> Widget for Video<'a> {
                 .set(state.ids.shadow_mode_map_resolution_value, ui);
         }
 
+        // Rain occlusion texture size
+        // Display the shadow map mode if selected.
+        Text::new(
+            self.localized_strings
+                .get("hud.settings.rain_occlusion.resolution"),
+        )
+        .down_from(state.ids.shadow_mode_list, 10.0)
+        .font_size(self.fonts.cyri.scale(14))
+        .font_id(self.fonts.cyri.conrod_id)
+        .color(TEXT_COLOR)
+        .set(state.ids.rain_map_resolution_text, ui);
+
+        if let Some(new_val) = ImageSlider::discrete(
+            (render_mode.rain_occlusion.resolution.log2() * 4.0).round() as i8,
+            -8,
+            8,
+            self.imgs.slider_indicator,
+            self.imgs.slider,
+        )
+        .w_h(104.0, 22.0)
+        .right_from(state.ids.rain_map_resolution_text, 8.0)
+        .track_breadth(12.0)
+        .slider_length(10.0)
+        .pad_track((5.0, 5.0))
+        .set(state.ids.rain_map_resolution_slider, ui)
+        {
+            events.push(GraphicsChange::ChangeRenderMode(Box::new(RenderMode {
+                rain_occlusion: ShadowMapMode {
+                    resolution: 2.0f32.powf(f32::from(new_val) / 4.0),
+                },
+                ..render_mode.clone()
+            })));
+        }
+        Text::new(&format!("{}", render_mode.rain_occlusion.resolution))
+            .right_from(state.ids.rain_map_resolution_slider, 8.0)
+            .font_size(self.fonts.cyri.scale(14))
+            .font_id(self.fonts.cyri.conrod_id)
+            .color(TEXT_COLOR)
+            .set(state.ids.rain_map_resolution_value, ui);
+
         // GPU Profiler
         Text::new(self.localized_strings.get("hud.settings.gpu_profiler"))
             .font_size(self.fonts.cyri.scale(14))
             .font_id(self.fonts.cyri.conrod_id)
-            .down_from(state.ids.shadow_mode_list, 8.0)
+            .down_from(state.ids.rain_map_resolution_text, 8.0)
             .color(TEXT_COLOR)
             .set(state.ids.gpu_profiler_label, ui);
 
diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs
index 1411a128b8..e74d85ff69 100644
--- a/voxygen/src/lib.rs
+++ b/voxygen/src/lib.rs
@@ -11,7 +11,8 @@
     trait_alias,
     option_get_or_insert_default,
     map_try_insert,
-    slice_as_chunks
+    slice_as_chunks,
+    unzip_option
 )]
 #![recursion_limit = "2048"]
 
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index cdfb34d2d6..0ee2f9b9ee 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -347,6 +347,7 @@ pub struct RenderMode {
     pub fluid: FluidMode,
     pub lighting: LightingMode,
     pub shadow: ShadowMode,
+    pub rain_occlusion: ShadowMapMode,
     pub bloom: BloomMode,
     /// 0.0..1.0
     pub point_glow: f32,
@@ -366,6 +367,8 @@ impl Default for RenderMode {
             fluid: FluidMode::default(),
             lighting: LightingMode::default(),
             shadow: ShadowMode::default(),
+            // TODO: should 0.5 be default for rain_occlusion?
+            rain_occlusion: ShadowMapMode { resolution: 0.5 },
             bloom: BloomMode::default(),
             point_glow: 0.35,
             experimental_shaders: HashSet::default(),
@@ -385,6 +388,7 @@ impl RenderMode {
                 fluid: self.fluid,
                 lighting: self.lighting,
                 shadow: self.shadow,
+                rain_occlusion: self.rain_occlusion,
                 bloom: self.bloom,
                 point_glow: self.point_glow,
                 experimental_shaders: self.experimental_shaders,
@@ -407,6 +411,7 @@ pub struct PipelineModes {
     fluid: FluidMode,
     lighting: LightingMode,
     pub shadow: ShadowMode,
+    pub rain_occlusion: ShadowMapMode,
     bloom: BloomMode,
     point_glow: f32,
     experimental_shaders: HashSet<ExperimentalShader>,
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index e210217844..5b672d7806 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -369,7 +369,7 @@ impl Renderer {
         let rain_occlusion_view = RainOcclusionMap::create_view(
             &device,
             (dims.width, dims.height),
-            &ShadowMapMode::try_from(pipeline_modes.shadow).unwrap_or_default(),
+            &pipeline_modes.rain_occlusion,
         )
         .map_err(|err| {
             warn!("Could not create rain occlusion map views: {:?}", err);
@@ -724,48 +724,65 @@ impl Renderer {
                 State::Nothing => None, // Should never hit this
             };
 
-            if let (Some(((point_depth, directed_depth), rain_depth)), ShadowMode::Map(mode)) =
+            let mut update_shadow_bind = false;
+            let (shadow_views, rain_views) = shadow_views.unzip();
+
+            if let (Some((point_depth, directed_depth)), ShadowMode::Map(mode)) =
                 (shadow_views, self.pipeline_modes.shadow)
             {
-                match (
-                    ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode),
-                    RainOcclusionMap::create_view(&self.device, (dims.x, dims.y), &mode),
-                ) {
-                    (Ok((new_point_depth, new_directed_depth)), Ok(new_rain_depth)) => {
+                match ShadowMap::create_shadow_views(&self.device, (dims.x, dims.y), &mode) {
+                    Ok((new_point_depth, new_directed_depth)) => {
                         *point_depth = new_point_depth;
                         *directed_depth = new_directed_depth;
-                        *rain_depth = new_rain_depth;
 
-                        // Recreate the shadow bind group if needed
-                        if let State::Complete {
-                            shadow:
-                                Shadow {
-                                    bind,
-                                    map: ShadowMap::Enabled(shadow_map),
-                                    rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
-                                    ..
-                                },
-                            ..
-                        } = &mut self.state
-                        {
-                            *bind = self.layouts.global.bind_shadow_textures(
-                                &self.device,
-                                &shadow_map.point_depth,
-                                &shadow_map.directed_depth,
-                                &rain_occlusion_map.depth,
-                            );
-                        }
+                        update_shadow_bind = true;
                     },
-                    (shadow, rain) => {
+                    shadow => {
                         if let Err(err) = shadow {
                             warn!("Could not create shadow map views: {:?}", err);
                         }
+                    },
+                }
+            }
+            if let Some(rain_depth) = rain_views {
+                match RainOcclusionMap::create_view(
+                    &self.device,
+                    (dims.x, dims.y),
+                    &self.pipeline_modes.rain_occlusion,
+                ) {
+                    Ok(new_rain_depth) => {
+                        *rain_depth = new_rain_depth;
+
+                        update_shadow_bind = true;
+                    },
+                    rain => {
                         if let Err(err) = rain {
                             warn!("Could not create rain occlusion map view: {:?}", err);
                         }
                     },
                 }
             }
+            if update_shadow_bind {
+                // Recreate the shadow bind group if needed
+                if let State::Complete {
+                    shadow:
+                        Shadow {
+                            bind,
+                            map: ShadowMap::Enabled(shadow_map),
+                            rain_map: RainOcclusionMap::Enabled(rain_occlusion_map),
+                            ..
+                        },
+                    ..
+                } = &mut self.state
+                {
+                    *bind = self.layouts.global.bind_shadow_textures(
+                        &self.device,
+                        &shadow_map.point_depth,
+                        &shadow_map.directed_depth,
+                        &rain_occlusion_map.depth,
+                    );
+                }
+            }
         } else {
             self.is_minimized = true;
         }
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 73f7eae955..003210e7a2 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -266,8 +266,8 @@ impl SessionState {
                         | InventoryUpdateEvent::Swapped
                         | InventoryUpdateEvent::Given
                         | InventoryUpdateEvent::Collected(_)
-                        | InventoryUpdateEvent::EntityCollectFailed(_)
-                        | InventoryUpdateEvent::BlockCollectFailed(_)
+                        | InventoryUpdateEvent::EntityCollectFailed { .. }
+                        | InventoryUpdateEvent::BlockCollectFailed { .. }
                         | InventoryUpdateEvent::Craft => {
                             global_state
                                 .audio

From a6fd5d5c8bcd135d934e694b7abfb65fe08779bd Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Sat, 9 Apr 2022 01:01:26 -0700
Subject: [PATCH 38/71] Some organization. Faster volume lerping.

---
 voxygen/src/audio/ambient.rs | 312 +++++++++++++++++------------------
 voxygen/src/audio/mod.rs     |  17 +-
 voxygen/src/scene/mod.rs     |   4 -
 3 files changed, 163 insertions(+), 170 deletions(-)

diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 437d6d2108..14aa0cf89d 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -23,7 +23,6 @@ pub struct AmbientCollection {
     tracks: Vec<AmbientItem>,
 }
 
-/// Configuration for a single music track in the soundtrack
 #[derive(Debug, Deserialize)]
 pub struct AmbientItem {
     path: String,
@@ -46,55 +45,50 @@ impl AmbientMgr {
         camera: &Camera,
     ) {
         let ambience_volume = audio.get_ambience_volume();
-        // iterate through each tag
+        // Iterate through each tag
         for tag in AmbientChannelTag::iter() {
-            // check if current conditions necessitate the current tag at all
-            let should_create: bool = match tag {
-                AmbientChannelTag::Wind => self.check_wind_necessity(client, camera),
-                AmbientChannelTag::Rain => self.check_rain_necessity(client, camera),
-                AmbientChannelTag::Thunder => self.check_thunder_necessity(client),
-                AmbientChannelTag::Leaves => self.check_leaves_necessity(client, camera),
-            };
-            // if the conditions warrant creating a channel of that tag
-            if should_create && audio.get_ambient_channel(tag).is_none() {
-                // iterate through the supposed number of channels - one for each tag
+            // If the conditions warrant creating a channel of that tag
+            if self.check_ambience_necessity(tag, client, camera)
+                && audio.get_ambient_channel(tag).is_none()
+            {
+                // Iterate through the supposed number of channels - one for each tag
                 for index in 0..AmbientChannelTag::iter().len() {
-                    // if index would exceed current number of channels, create a new one with
+                    // If index would exceed current number of channels, create a new one with
                     // current tag
                     if index >= audio.ambient_channels.len() {
                         audio.new_ambient_channel(tag);
                         break;
                     }
                 }
-                // even if the conditions don't warrant the creation of a
+                // If the conditions don't warrant the creation of a
                 // channel with that tag, but a channel with
-                // that tag remains nonetheless, run the code
+                // that tag remains nonetheless, run the volume code
             } else if audio.get_ambient_channel(tag).is_some() {
                 for index in 0..AmbientChannelTag::iter().len() {
-                    // update with sfx volume
+                    // Update with sfx volume
                     audio.ambient_channels[index].set_volume(ambience_volume);
-                    // if current channel's tag is not the current tag, move on to next channel
+                    // If current channel's tag is not the current tag, move on to next channel
                     if audio.ambient_channels[index].get_tag() == tag {
-                        // maintain: get the correct multiplier of whatever the tag of the current
+                        // Maintain: get the correct multiplier of whatever the tag of the current
                         // channel is
                         let target_volume =
                             audio.ambient_channels[index].maintain(state, client, camera);
-                        // get multiplier of the current channel
+                        // Get multiplier of the current channel
                         let initial_volume = audio.ambient_channels[index].get_multiplier();
 
-                        // lerp multiplier of current channel
+                        // Lerp multiplier of current channel
                         audio.ambient_channels[index].set_multiplier(Lerp::lerp(
                             initial_volume,
                             target_volume,
-                            0.01,
+                            0.03,
                         ));
 
-                        // set the duration of the loop to whatever the current value is (0.0 by
+                        // Set the duration of the loop to whatever the current value is (0.0 by
                         // default)
                         let next_track_change =
                             audio.ambient_channels[index].get_next_track_change();
 
-                        // if the sound should loop at this point:
+                        // If the sound should loop at this point:
                         if audio.ambient_channels[index]
                             .get_began_playing()
                             .elapsed()
@@ -103,92 +97,94 @@ impl AmbientMgr {
                         {
                             let ambience = self.ambience.read();
                             let track = ambience.tracks.iter().find(|track| track.tag == tag);
-                            // set the channel's start point at this instant
+                            // Set the channel's start point at this instant
                             audio.ambient_channels[index].set_began_playing(Instant::now());
                             if let Some(track) = track {
-                                // set loop duration to the one specified in the ron
+                                // Set loop duration to the one specified in the ron
                                 audio.ambient_channels[index].set_next_track_change(track.length);
-                                // play the file of the current tag at the current multiplier
+                                // Play the file of the current tag at the current multiplier
                                 let current_multiplier =
                                     audio.ambient_channels[index].get_multiplier();
                                 audio.play_ambient(tag, &track.path, current_multiplier);
                             }
                         };
 
-                        // remove channel if not playing
+                        // Remove channel if not playing
                         if audio.ambient_channels[index].get_multiplier() == 0.0 {
                             audio.ambient_channels[index].stop();
                             audio.ambient_channels.remove(index);
                         };
-                        // move on to next tag
+                        // Move on to next tag
                         break;
                     } else {
-                        // channel tag and current tag don't match, move on to next channel
+                        // Channel tag and current tag don't match, move on to next channel
                         continue;
                     }
                 }
             } else {
-                // no need to run code at all, move on to the next tag
+                // No need to run code at all, move on to the next tag
                 continue;
             }
         }
     }
 
-    fn check_wind_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
+    fn check_ambience_necessity(
+        &mut self,
+        tag: AmbientChannelTag,
+        client: &Client,
+        camera: &Camera,
+    ) -> bool {
+        match tag {
+            AmbientChannelTag::Wind => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-            (chunk.meta().alt(), chunk.meta().tree_density())
-        } else {
-            (0.0, 0.0)
-        };
+                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+                    (chunk.meta().alt(), chunk.meta().tree_density())
+                } else {
+                    (0.0, 0.0)
+                };
 
-        // Wind volume increases with altitude
-        let alt_multiplier = (cam_pos.z / 1200.0).abs();
+                let alt_multiplier = (cam_pos.z / 1200.0).abs();
 
-        // Tree density factors into wind volume. The more trees,
-        // the lower wind volume. The trees make more of an impact
-        // the closer the camera is to the ground.
-        let tree_multiplier =
-            ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
+                let tree_multiplier = ((1.0 - tree_density)
+                    + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
+                .min(1.0);
 
-        return alt_multiplier * tree_multiplier > 0.0;
-    }
+                return alt_multiplier * tree_multiplier > 0.0;
+            },
+            AmbientChannelTag::Rain => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-    fn check_rain_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
+                let terrain_alt = if let Some(chunk) = client.current_chunk() {
+                    chunk.meta().alt()
+                } else {
+                    0.0
+                };
+                let camera_multiplier =
+                    1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
 
-        let terrain_alt = if let Some(chunk) = client.current_chunk() {
-            chunk.meta().alt()
-        } else {
-            0.0
-        };
-        // make rain diminish with camera distance above terrain
-        let camera_multiplier = 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
+                return client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0;
+            },
+            AmbientChannelTag::Thunder => return client.weather_at_player().rain * 500.0 > 0.7,
+            AmbientChannelTag::Leaves => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0
-    }
+                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+                    (chunk.meta().alt(), chunk.meta().tree_density())
+                } else {
+                    (0.0, 0.0)
+                };
+                let tree_multiplier = 1.0
+                    - (((1.0 - tree_density)
+                        + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
+                    .min(1.0));
 
-    fn check_thunder_necessity(&mut self, client: &Client) -> bool {
-        client.weather_at_player().rain * 500.0 > 0.7
-    }
-
-    fn check_leaves_necessity(&mut self, client: &Client, camera: &Camera) -> bool {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
-
-        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-            (chunk.meta().alt(), chunk.meta().tree_density())
-        } else {
-            (0.0, 0.0)
-        };
-        let tree_multiplier = 1.0
-            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
-                .min(1.0));
-
-        return tree_multiplier > 0.1;
+                return tree_multiplier > 0.1;
+            },
+        }
     }
 }
 
@@ -199,16 +195,7 @@ impl AmbientChannel {
         let focus_off = camera.get_focus_pos().map(f32::trunc);
         let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        let mut target_volume: f32 = match tag {
-            // Get target volume of wind
-            AmbientChannelTag::Wind => self.get_wind_volume(client, camera),
-            // Get target volume of rain
-            AmbientChannelTag::Rain => self.get_rain_volume(client, camera),
-            // Get target volume of thunder
-            AmbientChannelTag::Thunder => self.get_thunder_volume(client),
-            // Get target volume of leaves
-            AmbientChannelTag::Leaves => self.get_leaves_volume(client, camera),
-        };
+        let mut target_volume: f32 = self.get_ambience_volume(tag, client, camera);
 
         target_volume = self.check_camera(state, client, cam_pos, target_volume);
 
@@ -228,7 +215,7 @@ impl AmbientChannel {
         } else {
             0.0
         };
-        // Checks if the camera is underwater to stop ambient sounds
+        // Checks if the camera is underwater to diminish ambient sounds
         if state
             .terrain()
             .get((cam_pos).map(|e| e.floor() as i32))
@@ -245,88 +232,97 @@ impl AmbientChannel {
         volume_multiplier.clamped(0.0, 1.0)
     }
 
-    fn get_wind_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
-        // Float from around -30.0 to 30.0
-        let client_wind_speed_sq = client.weather_at_player().wind.magnitude_squared();
+    // Gets appropriate volume for each tag
+    fn get_ambience_volume(
+        &mut self,
+        tag: AmbientChannelTag,
+        client: &Client,
+        camera: &Camera,
+    ) -> f32 {
+        match tag {
+            AmbientChannelTag::Wind => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-            (chunk.meta().alt(), chunk.meta().tree_density())
-        } else {
-            (0.0, 0.0)
-        };
+                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+                    (chunk.meta().alt(), chunk.meta().tree_density())
+                } else {
+                    (0.0, 0.0)
+                };
 
-        // Wind volume increases with altitude
-        let alt_multiplier = (cam_pos.z / 1200.0).abs();
+                // Wind volume increases with altitude
+                let alt_multiplier = (cam_pos.z / 1200.0).abs();
 
-        // Tree density factors into wind volume. The more trees,
-        // the lower wind volume. The trees make more of an impact
-        // the closer the camera is to the ground.
-        let tree_multiplier =
-            ((1.0 - tree_density) + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
+                // Tree density factors into wind volume. The more trees,
+                // the lower wind volume. The trees make more of an impact
+                // the closer the camera is to the ground.
+                let tree_multiplier = ((1.0 - tree_density)
+                    + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
+                .min(1.0);
 
-        // Lastly, we of course have to take into account actual wind speed from
-        // weathersim
-        let wind_speed_multiplier = (client_wind_speed_sq / 30.0_f32.powi(2)).min(1.0);
+                // Lastly, we of course have to take into account actual wind speed from
+                // weathersim
+                // Client wind speed is a float approx. -30.0 to 30.0 (polarity depending on
+                // direction)
+                let wind_speed_multiplier = (client.weather_at_player().wind.magnitude_squared()
+                    / 30.0_f32.powi(2))
+                .min(1.0);
 
-        return alt_multiplier
-            * tree_multiplier
-            * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2)).min(1.0);
-    }
+                return alt_multiplier
+                    * tree_multiplier
+                    * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
+                        .min(1.0);
+            },
+            AmbientChannelTag::Rain => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-    fn get_rain_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
-        // multipler at end will have to change depending on how intense rain normally
-        // is
+                let terrain_alt = if let Some(chunk) = client.current_chunk() {
+                    chunk.meta().alt()
+                } else {
+                    0.0
+                };
+                // Make rain diminish with camera distance above terrain
+                let camera_multiplier =
+                    1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
 
-        // TODO: make rain diminish with distance above terrain
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
+                let rain_intensity = (client.weather_at_player().rain * 500.0) * camera_multiplier;
 
-        let terrain_alt = if let Some(chunk) = client.current_chunk() {
-            chunk.meta().alt()
-        } else {
-            0.0
-        };
-        // make rain diminish with camera distance above terrain
-        let camera_multiplier = 1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
+                return rain_intensity.min(0.9);
+            },
+            AmbientChannelTag::Thunder => {
+                let rain_intensity = client.weather_at_player().rain * 500.0;
 
-        let rain_intensity = (client.weather_at_player().rain * 500.0) * camera_multiplier;
+                if rain_intensity < 0.7 {
+                    0.0
+                } else {
+                    rain_intensity
+                }
+            },
+            AmbientChannelTag::Leaves => {
+                let focus_off = camera.get_focus_pos().map(f32::trunc);
+                let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        return rain_intensity.min(0.9);
-    }
+                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
+                    (chunk.meta().alt(), chunk.meta().tree_density())
+                } else {
+                    (0.0, 0.0)
+                };
 
-    fn get_thunder_volume(&mut self, client: &Client) -> f32 {
-        let thunder_intensity = client.weather_at_player().rain * 500.0;
+                // Tree density factors into leaves volume. The more trees,
+                // the higher volume. The trees make more of an impact
+                // the closer the camera is to the ground
+                let tree_multiplier = 1.0
+                    - (((1.0 - tree_density)
+                        + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
+                    .min(1.0));
 
-        if thunder_intensity < 0.7 {
-            0.0
-        } else {
-            thunder_intensity
-        }
-    }
-
-    fn get_leaves_volume(&mut self, client: &Client, camera: &Camera) -> f32 {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
-
-        let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-            (chunk.meta().alt(), chunk.meta().tree_density())
-        } else {
-            (0.0, 0.0)
-        };
-
-        // Tree density factors into leaves volume. The more trees,
-        // the higher volume. The trees make more of an impact
-        // the closer the camera is to the ground
-        let tree_multiplier = 1.0
-            - (((1.0 - tree_density) + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
-                .min(1.0));
-
-        if tree_multiplier > 0.1 {
-            tree_multiplier
-        } else {
-            0.0
+                if tree_multiplier > 0.1 {
+                    tree_multiplier
+                } else {
+                    0.0
+                }
+            },
         }
     }
 }
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 78091460af..d9a78286cc 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -331,7 +331,7 @@ impl AudioFrontend {
         Ok(())
     }
 
-    // plays a file at a given volume in the channel with a given tag
+    // Plays a file at a given volume in the channel with a given tag
     fn play_ambient(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -346,7 +346,7 @@ impl AudioFrontend {
         }
     }
 
-    // adds a new ambient channel of the given tag at zero volume
+    // Adds a new ambient channel of the given tag at zero volume
     fn new_ambient_channel(&mut self, channel_tag: AmbientChannelTag) {
         if let Some(audio_stream) = &self.audio_stream {
             let ambient_channel = AmbientChannel::new(audio_stream, channel_tag, 0.0);
@@ -354,7 +354,7 @@ impl AudioFrontend {
         }
     }
 
-    // retrieves the channel currently having the given tag
+    // Retrieves the channel currently having the given tag
     fn get_ambient_channel(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -379,7 +379,8 @@ impl AudioFrontend {
         }
     }
 
-    // sets the volume of the channel with the given tag to the given volume
+    // Unused code that may be useful in the future:
+    // Sets the volume of the channel with the given tag to the given volume
     // fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag,
     // volume_multiplier: f32) {     if self.audio_stream.is_some() {
     //         let sfx_volume = self.get_sfx_volume();
@@ -390,7 +391,7 @@ impl AudioFrontend {
     //     }
     // }
 
-    // retrieves volume (pre-sfx-setting) of the channel with a given tag
+    // Retrieves volume (pre-sfx-setting) of the channel with a given tag
     // fn get_ambient_volume(&mut self, channel_tag: AmbientChannelTag) -> f32 {
     //     if self.audio_stream.is_some() {
     //         if let Some(channel) = self.get_ambient_channel(channel_tag) {
@@ -462,13 +463,13 @@ impl AudioFrontend {
         }
     }
 
-    // this retrieves the current setting for sfx volume
+    // Retrieves the current setting for sfx volume
     pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume * self.master_volume }
 
-    // this retrieves the current setting for ambience volume
+    // Retrieves the current setting for ambience volume
     pub fn get_ambience_volume(&self) -> f32 { self.ambience_volume * self.master_volume }
 
-    // this retrieves the current setting for music volume
+    // Retrieves the current setting for music volume
     pub fn get_music_volume(&self) -> f32 { self.music_volume * self.master_volume }
 
     pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index e047d8b3f7..db8242ed2d 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -103,8 +103,6 @@ pub struct Scene {
     pub sfx_mgr: SfxMgr,
     music_mgr: MusicMgr,
     ambient_mgr: AmbientMgr,
-    // ambient_wind_mgr: AmbientWindMgr,
-    // ambient_rain_mgr: AmbientRainMgr,
 }
 
 pub struct SceneData<'a> {
@@ -321,8 +319,6 @@ impl Scene {
             ambient_mgr: AmbientMgr {
                 ambience: ambient::load_ambience_items(),
             },
-            // ambient_wind_mgr: AmbientWindMgr::default(),
-            // ambient_rain_mgr: AmbientRainMgr::default(),
         }
     }
 

From 3eabe24f127a1a58210b5208a9fe2b0238e89766 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 6 Apr 2022 15:40:58 +0200
Subject: [PATCH 39/71] base occlusion texture size of off voxels

---
 Cargo.lock                                    | 28 ++++++-----
 assets/voxygen/shaders/clouds-frag.glsl       |  4 +-
 assets/voxygen/shaders/include/sky.glsl       |  3 --
 assets/voxygen/shaders/terrain-frag.glsl      |  1 +
 client/src/lib.rs                             |  1 +
 common/src/weather.rs                         | 37 ++++++++++++++-
 common/state/src/state.rs                     |  4 ++
 server/src/weather/mod.rs                     |  2 +-
 server/src/weather/sim.rs                     | 47 ++++++++++---------
 server/src/weather/sync.rs                    |  2 -
 voxygen/src/audio/ambient.rs                  | 16 +++----
 voxygen/src/audio/mod.rs                      |  5 +-
 voxygen/src/render/mod.rs                     |  3 +-
 .../src/render/pipelines/rain_occlusion.rs    |  2 +-
 voxygen/src/render/renderer.rs                | 20 ++++----
 .../src/render/renderer/rain_occlusion_map.rs | 11 +++--
 voxygen/src/scene/mod.rs                      | 41 ++++++++--------
 voxygen/src/scene/terrain.rs                  | 46 +++++++-----------
 voxygen/src/session/mod.rs                    |  2 +-
 19 files changed, 154 insertions(+), 121 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 52dfcad198..72959cd1f7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -654,16 +654,16 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "3.1.8"
+version = "3.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
+checksum = "3124f3f75ce09e22d1410043e1e24f2ecc44fad3afe4f08408f1f7663d68da2b"
 dependencies = [
  "atty",
  "bitflags",
  "clap_derive",
+ "clap_lex",
  "indexmap",
  "lazy_static",
- "os_str_bytes",
  "strsim 0.10.0",
  "termcolor",
  "textwrap 0.15.0",
@@ -682,6 +682,15 @@ dependencies = [
  "syn 1.0.90",
 ]
 
+[[package]]
+name = "clap_lex"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669"
+dependencies = [
+ "os_str_bytes",
+]
+
 [[package]]
 name = "clipboard-win"
 version = "3.1.1"
@@ -3980,9 +3989,6 @@ name = "os_str_bytes"
 version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
-dependencies = [
- "memchr",
-]
 
 [[package]]
 name = "owned_ttf_parser"
@@ -6362,7 +6368,7 @@ dependencies = [
  "async-channel",
  "authc",
  "byteorder",
- "clap 3.1.8",
+ "clap 3.1.10",
  "hashbrown 0.11.2",
  "image",
  "num 0.4.0",
@@ -6551,7 +6557,7 @@ dependencies = [
  "bincode",
  "bitflags",
  "bytes",
- "clap 3.1.8",
+ "clap 3.1.10",
  "criterion",
  "crossbeam-channel",
  "futures-core",
@@ -6674,7 +6680,7 @@ name = "veloren-server-cli"
 version = "0.12.0"
 dependencies = [
  "ansi-parser",
- "clap 3.1.8",
+ "clap 3.1.10",
  "crossterm 0.23.2",
  "lazy_static",
  "mimalloc",
@@ -6825,7 +6831,7 @@ dependencies = [
 name = "veloren-voxygen-i18n"
 version = "0.10.0"
 dependencies = [
- "clap 3.1.8",
+ "clap 3.1.10",
  "deunicode",
  "git2",
  "hashbrown 0.11.2",
@@ -6842,7 +6848,7 @@ dependencies = [
  "arr_macro",
  "bincode",
  "bitvec",
- "clap 3.1.8",
+ "clap 3.1.10",
  "criterion",
  "csv",
  "deflate",
diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 41f9f46e61..2397d0ec4a 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -139,13 +139,13 @@ void main() {
                 break;
             }
             float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
-            vec2 drop_size = vec2(0.0008, 0.05);
 
-            if (fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
+            if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                 continue;
             }
             vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
 
+            vec2 drop_size = vec2(0.0008, 0.05);
             float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
             float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
             float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 5069377b14..dc94dc5153 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -121,11 +121,8 @@ float cloud_tendency_at(vec2 pos) {
     return sample_weather(pos).r;
 }
 
-const float RAIN_CLOUD = 0.05;
-
 float rain_density_at(vec2 pos) {
     return sample_weather(pos).g;
-    //return clamp((cloud_tendency_at(pos) - RAIN_CLOUD) * 10, 0, 1);
 }
 
 float cloud_shadow(vec3 pos, vec3 light_dir) {
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index d5291b208e..e24b27482d 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -234,6 +234,7 @@ void main() {
     #ifdef EXPERIMENTAL_RAIN
         vec3 pos = f_pos + focus_off.xyz;
         float rain_density = rain_density_at(pos.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
+        // Toggle to see rain_occlusion
         // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
         // return;
         if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
diff --git a/client/src/lib.rs b/client/src/lib.rs
index f22705089b..e2f1b8d95c 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -163,6 +163,7 @@ impl WeatherLerp {
     }
 
     fn update(&mut self, to_update: &mut WeatherGrid) {
+        prof_span!("WeatherLerp::update");
         let old = &self.old.0;
         let new = &self.new.0;
         if new.size() == Vec2::zero() {
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 9a20d79d92..069d3d3163 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -82,6 +82,21 @@ pub struct WeatherGrid {
     weather: Grid<Weather>,
 }
 
+fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 }
+
+// TODO: Move consts from world to common to avoid duplication
+const LOCALITY: [Vec2<i32>; 9] = [
+    Vec2::new(0, 0),
+    Vec2::new(0, 1),
+    Vec2::new(1, 0),
+    Vec2::new(0, -1),
+    Vec2::new(-1, 0),
+    Vec2::new(1, 1),
+    Vec2::new(1, -1),
+    Vec2::new(-1, 1),
+    Vec2::new(-1, -1),
+];
+
 impl WeatherGrid {
     pub fn new(size: Vec2<u32>) -> Self {
         Self {
@@ -100,7 +115,7 @@ impl WeatherGrid {
     /// Get the weather at a given world position by doing bilinear
     /// interpolation between four cells.
     pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather {
-        let cell_pos = wpos / CELL_SIZE as f32;
+        let cell_pos = to_cell_pos(wpos);
         let rpos = cell_pos.map(|e| e.fract());
         let cell_pos = cell_pos.map(|e| e.floor());
 
@@ -125,4 +140,24 @@ impl WeatherGrid {
             rpos.y,
         )
     }
+
+    /// Get the max weather near a position
+    pub fn get_max_near(&self, wpos: Vec2<f32>) -> Weather {
+        let cell_pos: Vec2<i32> = to_cell_pos(wpos).as_();
+        LOCALITY
+            .iter()
+            .map(|l| {
+                self.weather
+                    .get(cell_pos + l)
+                    .cloned()
+                    .unwrap_or_default()
+            })
+            .reduce(|a, b| Weather {
+                cloud: a.cloud.max(b.cloud),
+                rain: a.rain.max(b.rain),
+                wind: a.wind.map2(b.wind, |a, b| a.max(b)),
+            })
+            // There will always be 9 elements in locality
+            .unwrap()
+    }
 }
diff --git a/common/state/src/state.rs b/common/state/src/state.rs
index 9cd166a80d..6f3e8de8a7 100644
--- a/common/state/src/state.rs
+++ b/common/state/src/state.rs
@@ -359,6 +359,10 @@ impl State {
         self.weather_grid().get_interpolated(pos)
     }
 
+    pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
+        self.weather_grid().get_max_near(pos)
+    }
+
     /// Get the current in-game time of day.
     ///
     /// Note that this should not be used for physics, animations or other such
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index 2c9ddbe0d3..135beda71c 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -1,4 +1,4 @@
-use common::weather::{WeatherGrid, CHUNKS_PER_CELL, WEATHER_DT};
+use common::weather::{CHUNKS_PER_CELL, WEATHER_DT};
 use common_ecs::{dispatch, System};
 use common_state::State;
 use specs::DispatcherBuilder;
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 45421dc7c5..8c5e8fac7f 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -1,15 +1,21 @@
 use common::{
-    grid::Grid,
     resources::TimeOfDay,
-    terrain::TerrainChunkSize,
-    vol::RectVolSize,
-    weather::{Weather, WeatherGrid, CELL_SIZE, CHUNKS_PER_CELL},
+    weather::{WeatherGrid, CELL_SIZE},
 };
-use itertools::Itertools;
 use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
 use world::World;
 
+
+
+/*
+#[derive(Clone, Copy, Default)]
+struct Cell {
+    wind: Vec2<f32>,
+    temperature: f32,
+    moisture: f32,
+    cloud: f32,
+}
 #[derive(Default)]
 pub struct Constants {
     alt: f32,
@@ -18,13 +24,6 @@ pub struct Constants {
     temp: f32,
 }
 
-#[derive(Clone, Copy, Default)]
-struct Cell {
-    wind: Vec2<f32>,
-    temperature: f32,
-    moisture: f32,
-    cloud: f32,
-}
 /// Used to sample weather that isn't simulated
 fn sample_cell(_p: Vec2<i32>, _time: f64) -> Cell {
     Cell {
@@ -40,12 +39,6 @@ pub struct WeatherInfo {
     pub lightning_chance: f32,
 }
 
-pub struct WeatherSim {
-    cells: Grid<Cell>,       // The variables used for simulation
-    consts: Grid<Constants>, // The constants from the world used for simulation
-    info: Grid<WeatherInfo>,
-}
-
 fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
     if points.len() < 3 {
         return None;
@@ -82,13 +75,21 @@ fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
         Some(Vec3::new(xy * yz - xz * yy, xy * xz - yz * xx, det_z).normalized())
     }
 }
-
+*/
 fn cell_to_wpos(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 }
 
+pub struct WeatherSim {
+    // cells: Grid<Cell>,       // The variables used for simulation
+    // consts: Grid<Constants>, // The constants from the world used for simulation
+    // info: Grid<WeatherInfo>,
+    size: Vec2<u32>,
+}
+
 impl WeatherSim {
-    pub fn new(size: Vec2<u32>, world: &World) -> Self {
+    pub fn new(size: Vec2<u32>, _world: &World) -> Self {
+        /*
         let size = size.as_();
-        let mut this = Self {
+        let this = Self {
             cells: Grid::new(size, Cell::default()),
             consts: Grid::from_raw(
                 size,
@@ -138,6 +139,8 @@ impl WeatherSim {
             *cell = sample_cell(point, time);
         });
         this
+        */
+        Self { size }
     }
 
     /*
@@ -380,5 +383,5 @@ impl WeatherSim {
         */
     }
 
-    pub fn size(&self) -> Vec2<u32> { self.cells.size().as_() }
+    pub fn size(&self) -> Vec2<u32> { self.size }
 }
diff --git a/server/src/weather/sync.rs b/server/src/weather/sync.rs
index c7e875f8bc..b5d58c22ed 100644
--- a/server/src/weather/sync.rs
+++ b/server/src/weather/sync.rs
@@ -5,8 +5,6 @@ use specs::{Join, ReadExpect, ReadStorage, Write};
 
 use crate::{client::Client, sys::SysScheduler};
 
-use super::sim::WeatherSim;
-
 #[derive(Default)]
 pub struct Sys;
 
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 14aa0cf89d..e89a84578a 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -151,7 +151,7 @@ impl AmbientMgr {
                     + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
                 .min(1.0);
 
-                return alt_multiplier * tree_multiplier > 0.0;
+                alt_multiplier * tree_multiplier > 0.0
             },
             AmbientChannelTag::Rain => {
                 let focus_off = camera.get_focus_pos().map(f32::trunc);
@@ -165,9 +165,9 @@ impl AmbientMgr {
                 let camera_multiplier =
                     1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
 
-                return client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0;
+                client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0
             },
-            AmbientChannelTag::Thunder => return client.weather_at_player().rain * 500.0 > 0.7,
+            AmbientChannelTag::Thunder => client.weather_at_player().rain * 500.0 > 0.7,
             AmbientChannelTag::Leaves => {
                 let focus_off = camera.get_focus_pos().map(f32::trunc);
                 let cam_pos = camera.dependents().cam_pos + focus_off;
@@ -182,7 +182,7 @@ impl AmbientMgr {
                         + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
                     .min(1.0));
 
-                return tree_multiplier > 0.1;
+                tree_multiplier > 0.1
             },
         }
     }
@@ -199,7 +199,7 @@ impl AmbientChannel {
 
         target_volume = self.check_camera(state, client, cam_pos, target_volume);
 
-        return target_volume;
+        target_volume
     }
 
     fn check_camera(
@@ -268,10 +268,10 @@ impl AmbientChannel {
                     / 30.0_f32.powi(2))
                 .min(1.0);
 
-                return alt_multiplier
+                alt_multiplier
                     * tree_multiplier
                     * (wind_speed_multiplier + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
-                        .min(1.0);
+                        .min(1.0)
             },
             AmbientChannelTag::Rain => {
                 let focus_off = camera.get_focus_pos().map(f32::trunc);
@@ -288,7 +288,7 @@ impl AmbientChannel {
 
                 let rain_intensity = (client.weather_at_player().rain * 500.0) * camera_multiplier;
 
-                return rain_intensity.min(0.9);
+                rain_intensity.min(0.9)
             },
             AmbientChannelTag::Thunder => {
                 let rain_intensity = client.weather_at_player().rain * 500.0;
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index d9a78286cc..fe29f0d147 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -371,12 +371,9 @@ impl AudioFrontend {
             }
             if tag_match {
                 return found_channel;
-            } else {
-                return None;
             }
-        } else {
-            return None;
         }
+        None
     }
 
     // Unused code that may be useful in the future:
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index 0ee2f9b9ee..206fd7962e 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -367,8 +367,7 @@ impl Default for RenderMode {
             fluid: FluidMode::default(),
             lighting: LightingMode::default(),
             shadow: ShadowMode::default(),
-            // TODO: should 0.5 be default for rain_occlusion?
-            rain_occlusion: ShadowMapMode { resolution: 0.5 },
+            rain_occlusion: ShadowMapMode::default(),
             bloom: BloomMode::default(),
             point_glow: 0.35,
             experimental_shaders: HashSet::default(),
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index aeafdf24d5..0ff61f769c 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -104,7 +104,7 @@ impl RainOcclusionFigurePipeline {
                 topology: wgpu::PrimitiveTopology::TriangleList,
                 strip_index_format: None,
                 front_face: wgpu::FrontFace::Ccw,
-                cull_mode: None,
+                cull_mode: Some(wgpu::Face::Back),
                 clamp_depth: true,
                 polygon_mode: wgpu::PolygonMode::Fill,
                 conservative: false,
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index 5b672d7806..c22ab247f4 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -49,6 +49,10 @@ pub type ColLightInfo = (Vec<[u8; 4]>, Vec2<u16>);
 const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000;
 const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000;
 
+// For rain occlusion we only need to render the closest chunks.
+// TODO: Is this a good value?
+pub const RAIN_OCCLUSION_CHUNKS: usize = 9;
+
 /// A type that stores all the layouts associated with this renderer that never
 /// change when the RenderMode is modified.
 struct ImmutableLayouts {
@@ -366,15 +370,12 @@ impl Renderer {
         })
         .ok();
 
-        let rain_occlusion_view = RainOcclusionMap::create_view(
-            &device,
-            (dims.width, dims.height),
-            &pipeline_modes.rain_occlusion,
-        )
-        .map_err(|err| {
-            warn!("Could not create rain occlusion map views: {:?}", err);
-        })
-        .ok();
+        let rain_occlusion_view =
+            RainOcclusionMap::create_view(&device, &pipeline_modes.rain_occlusion)
+                .map_err(|err| {
+                    warn!("Could not create rain occlusion map views: {:?}", err);
+                })
+                .ok();
 
         let shaders = Shaders::load_expect("");
         let shaders_watcher = shaders.reload_watcher();
@@ -747,7 +748,6 @@ impl Renderer {
             if let Some(rain_depth) = rain_views {
                 match RainOcclusionMap::create_view(
                     &self.device,
-                    (dims.x, dims.y),
                     &self.pipeline_modes.rain_occlusion,
                 ) {
                     Ok(new_rain_depth) => {
diff --git a/voxygen/src/render/renderer/rain_occlusion_map.rs b/voxygen/src/render/renderer/rain_occlusion_map.rs
index 45ca1bb933..3fb37b2a5e 100644
--- a/voxygen/src/render/renderer/rain_occlusion_map.rs
+++ b/voxygen/src/render/renderer/rain_occlusion_map.rs
@@ -1,9 +1,10 @@
-use crate::render::pipelines::rain_occlusion;
+use crate::render::{pipelines::rain_occlusion, renderer::RAIN_OCCLUSION_CHUNKS};
 
 use super::{
     super::{texture::Texture, RenderError, ShadowMapMode},
     Renderer,
 };
+use common::{terrain::TerrainChunkSize, vol::RectVolSize};
 use vek::*;
 
 /// A type that holds shadow map data.  Since shadow mapping may not be
@@ -115,15 +116,17 @@ impl RainOcclusionMap {
     /// Returns (point, directed)
     pub(super) fn create_view(
         device: &wgpu::Device,
-        size: (u32, u32),
         mode: &ShadowMapMode,
     ) -> Result<Texture, RenderError> {
         // (Attempt to) apply resolution factor to rain occlusion map resolution.
         let resolution_factor = mode.resolution.clamped(0.25, 4.0);
 
         let max_texture_size = Renderer::max_texture_size_raw(device);
+        let size =
+            (RAIN_OCCLUSION_CHUNKS as f32).sqrt().ceil() as u32 * TerrainChunkSize::RECT_SIZE * 2;
+
         // Limit to max texture size, rather than erroring.
-        let size = Vec2::new(size.0, size.1).map(|e| {
+        let size = size.map(|e| {
             let size = e as f32 * resolution_factor;
             // NOTE: We know 0 <= e since we clamped the resolution factor to be between
             // 0.25 and 4.0.
@@ -221,6 +224,4 @@ impl RainOcclusionMap {
             Self::Disabled(dummy) => dummy,
         }
     }
-
-    pub fn is_enabled(&self) -> bool { matches!(self, Self::Enabled(_)) }
 }
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index db8242ed2d..3826252de5 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -797,7 +797,7 @@ impl Scene {
             // space (left-handed).
             let bounds0 = math::fit_psr(
                 light_all_mat,
-                visible_light_volume.iter().copied(),
+                volume.iter().copied(),
                 math::Vec4::homogenized,
             );
             // Vague idea: project z_n from the camera view to the light view (where it's
@@ -976,7 +976,7 @@ impl Scene {
                     },
             } = math::fit_psr(
                 shadow_all_mat,
-                visible_light_volume.iter().copied(),
+                volume.iter().copied(),
                 math::Vec4::homogenized,
             );
             let s_x = 2.0 / (xmax - xmin);
@@ -998,10 +998,11 @@ impl Scene {
             )
         };
 
-        let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
-        if true || weather.rain > 0.001
-        // TODO: check if rain map mode is on
-        {
+        let weather = client
+            .state()
+            .max_weather_near(focus_off.xy() + cam_pos.xy());
+        if weather.rain > 0.0 {
+            let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
             let rain_dir = math::Vec3::from(weather.rain_dir());
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_dir, up);
 
@@ -1009,6 +1010,7 @@ impl Scene {
                 directed_mats(rain_view_mat, rain_dir, &visible_occlusion_volume);
 
             let rain_occlusion_locals = RainOcclusionLocals::new(shadow_mat, texture_mat);
+
             renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
         }
 
@@ -1131,6 +1133,7 @@ impl Scene {
         let is_daylight = sun_dir.z < 0.0;
         let focus_pos = self.camera.get_focus_pos();
         let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
+        let is_rain = state.max_weather_near(cam_pos.xy()).rain > 0.0;
 
         let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
 
@@ -1161,20 +1164,20 @@ impl Scene {
                     self.terrain.chunks_for_point_shadows(focus_pos),
                 )
             }
-            // Render rain occlusion texture
-            {
-                prof_span!("rain occlusion");
-                if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
-                    self.terrain
-                        .render_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
+        }
+        // Render rain occlusion texture
+        if is_rain {
+            prof_span!("rain occlusion");
+            if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
+                self.terrain
+                    .render_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
 
-                    self.figure_mgr.render_shadows(
-                        &mut occlusion_pass.draw_figure_shadows(),
-                        state,
-                        tick,
-                        camera_data,
-                    );
-                }
+                self.figure_mgr.render_shadows(
+                    &mut occlusion_pass.draw_figure_shadows(),
+                    state,
+                    tick,
+                    camera_data,
+                );
             }
         }
 
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 599337dfcc..dbc9608dc8 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -10,6 +10,7 @@ use crate::{
     },
     render::{
         pipelines::{self, ColLights},
+        renderer::RAIN_OCCLUSION_CHUNKS,
         ColLightInfo, FirstPassDrawer, FluidVertex, GlobalModel, Instances, LodData, Mesh, Model,
         RenderError, Renderer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts,
         TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
@@ -1295,6 +1296,10 @@ impl<V: RectRasterableVol> Terrain<V> {
             min: focus_pos - 2.0,
             max: focus_pos + 2.0,
         });
+        let inv_proj_view =
+            math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
+                .as_::<f64>()
+                .inverted();
 
         // PSCs: Potential shadow casters
         let ray_direction = scene_data.get_sun_dir();
@@ -1315,10 +1320,6 @@ impl<V: RectRasterableVol> Terrain<V> {
             };
             let focus_off = math::Vec3::from(focus_off);
             let visible_bounds_fine = visible_bounding_box.as_::<f64>();
-            let inv_proj_view =
-                math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
-                    .as_::<f64>()
-                    .inverted();
             let ray_direction = math::Vec3::<f32>::from(ray_direction);
             // NOTE: We use proj_mat_treeculler here because
             // calc_focused_light_volume_points makes the assumption that the
@@ -1402,45 +1403,35 @@ impl<V: RectRasterableVol> Terrain<V> {
         };
         drop(guard);
         span!(guard, "Rain occlusion magic");
-        let weather = scene_data.state.weather_at(focus_pos.xy());
-        let visible_occlusion_volume = if weather.rain > 0.0 {
-            let occlusion_box = Aabb {
-                min: visible_bounding_box
-                    .min
-                    .map2(focus_pos - 10.0, |a, b| a.max(b)),
-                max: visible_bounding_box
-                    .max
-                    .map2(focus_pos + 10.0, |a, b| a.min(b)),
-            };
+        // Check if there is rain near the camera
+        let max_weather = scene_data.state.max_weather_near(focus_pos.xy());
+        let visible_occlusion_volume = if max_weather.rain > 0.0 {
+            let occlusion_box = visible_bounding_box/*.intersection(Aabb {
+                min: focus_pos + camera.dependents().cam_pos - 100.0,
+                max: focus_pos + camera.dependents().cam_pos + 100.0,
+            })*/;
             let visible_bounding_box = math::Aabb::<f32> {
                 min: math::Vec3::from(occlusion_box.min - focus_off),
                 max: math::Vec3::from(occlusion_box.max - focus_off),
             };
             let visible_bounds_fine = math::Aabb {
-                min: math::Vec3::from(visible_bounding_box.min.as_::<f64>()),
-                max: math::Vec3::from(visible_bounding_box.max.as_::<f64>()),
+                min: visible_bounding_box.min.as_::<f64>(),
+                max: visible_bounding_box.max.as_::<f64>(),
             };
-            // TODO: move out shared calculations
-            let inv_proj_view =
-                math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays())
-                    .as_::<f64>()
-                    .inverted();
-
+            let weather = scene_data.state.weather_at(focus_pos.xy());
             let ray_direction = math::Vec3::<f32>::from(weather.rain_dir());
 
             // NOTE: We use proj_mat_treeculler here because
             // calc_focused_light_volume_points makes the assumption that the
             // near plane lies before the far plane.
-            let visible_occlusion_volume = math::calc_focused_light_volume_points(
+            math::calc_focused_light_volume_points(
                 inv_proj_view,
                 ray_direction.as_::<f64>(),
                 visible_bounds_fine,
                 1e-6,
             )
             .map(|v| v.as_::<f32>())
-            .collect::<Vec<_>>();
-
-            visible_occlusion_volume
+            .collect::<Vec<_>>()
         } else {
             Vec::new()
         };
@@ -1512,9 +1503,6 @@ impl<V: RectRasterableVol> Terrain<V> {
         let focus_chunk = Vec2::from(focus_pos).map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
             (e as i32).div_euclid(sz as i32)
         });
-        // For rain occlusion we only need to render the closest chunks.
-        // TODO: Is this a good value?
-        const RAIN_OCCLUSION_CHUNKS: usize = 16;
         let chunk_iter = Spiral2d::new()
             .filter_map(|rpos| {
                 let pos = focus_chunk + rpos;
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 003210e7a2..6a521c7d67 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -1617,7 +1617,7 @@ impl PlayState for SessionState {
                             &outcome,
                             &scene_data,
                             &mut global_state.audio,
-                            &client.state(),
+                            client.state(),
                             cam_pos,
                         );
                         self.hud

From a93240d2a6fe72cee7d892ae2b1f936bf395c56b Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 19 Apr 2022 21:15:58 +0200
Subject: [PATCH 40/71] cleaner get_ambient_channel

---
 voxygen/src/audio/mod.rs | 16 +++-------------
 1 file changed, 3 insertions(+), 13 deletions(-)

diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index fe29f0d147..1f00c4b454 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -360,20 +360,10 @@ impl AudioFrontend {
         channel_tag: AmbientChannelTag,
     ) -> Option<&mut AmbientChannel> {
         if self.audio_stream.is_some() {
-            let mut tag_match = false;
-            let mut found_channel = None;
-            for channel in self.ambient_channels.iter_mut() {
-                if channel.get_tag() == channel_tag {
-                    tag_match = true;
-                    found_channel = Some(channel);
-                    break;
-                }
-            }
-            if tag_match {
-                return found_channel;
-            }
+            self.ambient_channels.iter_mut().find(|channel| channel.get_tag() == channel_tag)
+        } else {
+            None
         }
-        None
     }
 
     // Unused code that may be useful in the future:

From 6274d54e56ea644752182e3de428465eb08ba9f5 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 19 Apr 2022 21:28:21 +0200
Subject: [PATCH 41/71] fix todos

---
 voxygen/src/render/pipelines/mod.rs | 1 -
 voxygen/src/scene/lod.rs            | 3 ++-
 voxygen/src/scene/terrain.rs        | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index 7f3c7cb194..2175cbe57d 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -431,7 +431,6 @@ impl GlobalsLayouts {
             wgpu::BindGroupLayoutEntry {
                 binding: 14,
                 visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
-                // TODO: is this relevant?
                 ty: wgpu::BindingType::Buffer {
                     ty: wgpu::BufferBindingType::Uniform,
                     has_dynamic_offset: false,
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 410b1529f7..58f1c58306 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -13,6 +13,7 @@ use common::{
     lod,
     spiral::Spiral2d,
     util::srgba_to_linear,
+    weather
 };
 use hashbrown::HashMap;
 use std::ops::Range;
@@ -52,7 +53,7 @@ impl Lod {
             client.world_data().lod_base.raw(),
             client.world_data().lod_alt.raw(),
             client.world_data().lod_horizon.raw(),
-            client.world_data().chunk_size().as_() / 16, // TODO: Send this from the server.
+            client.world_data().chunk_size().as_() / weather::CHUNKS_PER_CELL,
             settings.graphics.lod_detail.max(100).min(2500),
             /* TODO: figure out how we want to do this without color borders?
              * water_color().into_array().into(), */
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index dbc9608dc8..70c58a7b8d 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -1514,7 +1514,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             // Find a way to keep this?
             // .filter(|chunk| chunk.can_shadow_sun())
             .filter_map(|chunk| {
-                // TODO: Should the fuid model also be considered here?
+                // TODO: Should the fluid model also be considered here?
                 chunk
                     .opaque_model
                     .as_ref()

From b82bb5b2471b3300e67b36cc5b96866524bfe3c7 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 19 Apr 2022 21:43:40 +0200
Subject: [PATCH 42/71] Don't have rain be behind an expiremental shader

---
 assets/voxygen/shaders/clouds-frag.glsl      | 88 ++++++++++----------
 assets/voxygen/shaders/fluid-frag/shiny.glsl | 40 +++++----
 assets/voxygen/shaders/terrain-frag.glsl     | 58 +++++++------
 server/src/weather/sim.rs                    |  2 -
 voxygen/src/audio/mod.rs                     |  4 +-
 voxygen/src/render/mod.rs                    |  2 -
 6 files changed, 93 insertions(+), 101 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 2397d0ec4a..e76f1ee464 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -99,59 +99,57 @@ void main() {
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
     #endif
 
-    #ifdef EXPERIMENTAL_RAIN
-        vec3 old_color = color.rgb;
+    vec3 old_color = color.rgb;
 
-        // If this value is changed also change it in common/src/weather.rs
-        float fall_rate = 70.0;
-        dir.xy += wind_vel * dir.z / fall_rate;
-        dir = normalize(dir);
+    // If this value is changed also change it in common/src/weather.rs
+    float fall_rate = 70.0;
+    dir.xy += wind_vel * dir.z / fall_rate;
+    dir = normalize(dir);
 
-        float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
-        vec2 dir_2d = normalize(dir.xy);
-        vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
+    float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+    vec2 dir_2d = normalize(dir.xy);
+    vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
-        vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-        float rain_dist = 250.0;
-        for (int i = 0; i < 7; i ++) {
-            float old_rain_dist = rain_dist;
-            rain_dist *= 0.3;
+    vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+    float rain_dist = 250.0;
+    for (int i = 0; i < 7; i ++) {
+        float old_rain_dist = rain_dist;
+        rain_dist *= 0.3;
 
-            vec2 drop_density = vec2(30, 1);
+        vec2 drop_density = vec2(30, 1);
 
-            vec2 rain_pos = (view_pos * rain_dist);
-            rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
+        vec2 rain_pos = (view_pos * rain_dist);
+        rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
 
-            vec2 cell = floor(rain_pos * drop_density) / drop_density;
+        vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
-            float drop_depth = mix(
-                old_rain_dist,
-                rain_dist,
-                fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
-            );
-            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
-            float dist_to_rain = length(rpos);
-            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
-                continue;
-            }
-
-            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-                break;
-            }
-            float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
-
-            if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
-                continue;
-            }
-            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
-
-            vec2 drop_size = vec2(0.0008, 0.05);
-            float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
-            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
-            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
-            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
+        float drop_depth = mix(
+            old_rain_dist,
+            rain_dist,
+            fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
+        );
+        vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
+        float dist_to_rain = length(rpos);
+        if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+            continue;
         }
-    #endif
+
+        if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+            break;
+        }
+        float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+
+        if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
+            continue;
+        }
+        vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+        vec2 drop_size = vec2(0.0008, 0.05);
+        float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
+        float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
+        float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+        color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
+    }
 
     tgt_color = vec4(color.rgb, 1);
 }
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index f91f5fc7d7..334784b835 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -149,30 +149,28 @@ void main() {
         wave_sample_dist / slope
     );
 
-    #ifdef EXPERIMENTAL_RAIN
-        float rain_density = rain_density_at(f_pos.xy + focus_off.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
-        if (rain_density > 0 && surf_norm.z > 0.5) {
-            vec3 drop_density = vec3(2, 2, 2);
-            vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
-            drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
-            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+    float rain_density = rain_density_at(f_pos.xy + focus_off.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
+    if (rain_density > 0 && surf_norm.z > 0.5) {
+        vec3 drop_density = vec3(2, 2, 2);
+        vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
+        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
-                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
+            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-                float drop_rad = 0.125;
-                nmap.xy += (drop_pos - near_cell).xy
-                    * max(1.0 - abs(dist - drop_rad) * 50, 0)
-                    * 2500
-                    * sign(dist - drop_rad)
-                    * max(drop_pos.z - near_cell.z, 0);
-            }
+            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+            float drop_rad = 0.125;
+            nmap.xy += (drop_pos - near_cell).xy
+                * max(1.0 - abs(dist - drop_rad) * 50, 0)
+                * 2500
+                * sign(dist - drop_rad)
+                * max(drop_pos.z - near_cell.z, 0);
         }
-    #endif
+    }
 
     nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1));
 
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index e24b27482d..e37eac610f 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -231,39 +231,37 @@ void main() {
     vec3 k_d = vec3(1.0);
     vec3 k_s = vec3(R_s);
 
-    #ifdef EXPERIMENTAL_RAIN
-        vec3 pos = f_pos + focus_off.xyz;
-        float rain_density = rain_density_at(pos.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
-        // Toggle to see rain_occlusion
-        // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
-        // return;
-        if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
-            vec3 drop_density = vec3(2, 2, 2);
-            vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
-            drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
-            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+    vec3 pos = f_pos + focus_off.xyz;
+    float rain_density = rain_density_at(pos.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
+    // Toggle to see rain_occlusion
+    // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
+    // return;
+    if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
+        vec3 drop_density = vec3(2, 2, 2);
+        vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
+        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
-                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
+            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-                float drop_rad = 0.1;
-                float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
-                k_a += distort;
-                k_d += distort;
-                k_s += distort;
-                f_norm.xy += (drop_pos - near_cell).xy
-                    * max(1.0 - abs(dist - drop_rad) * 30, 0)
-                    * 500.0
-                    * max(drop_pos.z - near_cell.z, 0)
-                    * sign(dist - drop_rad)
-                    * max(drop_pos.z - near_cell.z, 0);
-            }
+            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+            float drop_rad = 0.1;
+            float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
+            k_a += distort;
+            k_d += distort;
+            k_s += distort;
+            f_norm.xy += (drop_pos - near_cell).xy
+                * max(1.0 - abs(dist - drop_rad) * 30, 0)
+                * 500.0
+                * max(drop_pos.z - near_cell.z, 0)
+                * sign(dist - drop_rad)
+                * max(drop_pos.z - near_cell.z, 0);
         }
-    #endif
+    }
 
     // float sun_light = get_sun_brightness(sun_dir);
     // float moon_light = get_moon_brightness(moon_dir);
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 8c5e8fac7f..51ad06621d 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -6,8 +6,6 @@ use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
 use world::World;
 
-
-
 /*
 #[derive(Clone, Copy, Default)]
 struct Cell {
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 1f00c4b454..92f6808493 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -360,7 +360,9 @@ impl AudioFrontend {
         channel_tag: AmbientChannelTag,
     ) -> Option<&mut AmbientChannel> {
         if self.audio_stream.is_some() {
-            self.ambient_channels.iter_mut().find(|channel| channel.get_tag() == channel_tag)
+            self.ambient_channels
+                .iter_mut()
+                .find(|channel| channel.get_tag() == channel_tag)
         } else {
             None
         }
diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs
index 206fd7962e..a88e2f686f 100644
--- a/voxygen/src/render/mod.rs
+++ b/voxygen/src/render/mod.rs
@@ -476,8 +476,6 @@ pub enum ExperimentalShader {
     /// Display grid lines to visualize the distribution of shadow map texels
     /// for the directional light from the sun.
     DirectionalShadowMapTexelGrid,
-    /// Enable rain, unfinished and goes through blocks
-    Rain,
     /// Enable rainbows
     Rainbows,
 }

From daebe0070a40e3edcf19850ea8e48d4b75c36b62 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 20 Apr 2022 12:31:00 +0200
Subject: [PATCH 43/71] add to changelog

---
 CHANGELOG.md | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 774a51e9c3..ad5a79c74e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Added an option for experience number accumulation.
 - Added an option for damage number rounding (when greater than or equal to 1.0).
 - Added sliders for incoming/non-incoming damage accumulation duration.
+- New ambience sounds
+- Slider for ambience volume
+- Weather generated on server is sent to clients, and seen on clients as rain/clouds.
 
 ### Changed
 
@@ -69,6 +72,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed an issue where the hurt animation would "jump" whenever you lost/gained health.
 - Fixed a bug where multiple damage sources in the same tick would show up as a singular attack.
 - Fixed an issue where, if the same amount of healing and damage was received in the same tick, nothing would be shown.
+- UI sfx now play from UI instead of from camera (allowing stereo sfx)
+- Most sfx now correctly play when camera is underwater
+- All sounds now stop upon quitting to main menu
+
 ## [0.12.0] - 2022-02-19
 
 ### Added

From 05e7de5629d7b11fee06f752433f2797b6e3fc66 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Sun, 24 Apr 2022 20:08:00 -0700
Subject: [PATCH 44/71] Adjust some values for ambience volume

---
 voxygen/src/audio/ambient.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index e89a84578a..53a04645f8 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -80,7 +80,7 @@ impl AmbientMgr {
                         audio.ambient_channels[index].set_multiplier(Lerp::lerp(
                             initial_volume,
                             target_volume,
-                            0.03,
+                            0.02,
                         ));
 
                         // Set the duration of the loop to whatever the current value is (0.0 by
@@ -225,7 +225,7 @@ impl AmbientChannel {
             volume_multiplier *= 0.1;
         }
         // Is the camera roughly under the terrain?
-        if cam_pos.z < terrain_alt - 10.0 {
+        if cam_pos.z < terrain_alt - 20.0 {
             volume_multiplier = 0.0;
         }
 

From 6215ccd5225a8075d48ead08424a7907e75c978e Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Fri, 6 May 2022 18:59:10 +0200
Subject: [PATCH 45/71] Remove dead code

---
 client/src/lib.rs              |   2 +-
 common/src/weather.rs          |   4 +-
 server/src/weather/sim.rs      | 328 ---------------------------------
 voxygen/src/audio/mod.rs       |   8 +-
 voxygen/src/render/renderer.rs |   2 +-
 voxygen/src/scene/terrain.rs   |   6 +-
 6 files changed, 8 insertions(+), 342 deletions(-)

diff --git a/client/src/lib.rs b/client/src/lib.rs
index e2f1b8d95c..fe153ecc54 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -1685,7 +1685,7 @@ impl Client {
             self.invite = None;
         }
 
-        // TODO: put this somewhere else? Otherwise update comments here.
+        // Lerp the clientside weather. 
         self.weather.update(&mut self.state.weather_grid_mut());
 
         // Lerp towards the target time of day - this ensures a smooth transition for
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 069d3d3163..08cddcb767 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -12,7 +12,7 @@ pub struct Weather {
     pub cloud: f32,
     /// Rain per time, between 0 and 1
     pub rain: f32,
-    // Wind direction in block / second
+    /// Wind velocity in block / second
     pub wind: Vec2<f32>,
 }
 
@@ -75,8 +75,6 @@ pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
 /// How often the weather is updated, in seconds
 pub const WEATHER_DT: f32 = 5.0;
 
-// pub const MAX_WIND_SPEED: f32 = CELL_SIZE as f32 / WEATHER_DT;
-
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct WeatherGrid {
     weather: Grid<Weather>,
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index 51ad06621d..e46060cb92 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -6,148 +6,17 @@ use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
 use world::World;
 
-/*
-#[derive(Clone, Copy, Default)]
-struct Cell {
-    wind: Vec2<f32>,
-    temperature: f32,
-    moisture: f32,
-    cloud: f32,
-}
-#[derive(Default)]
-pub struct Constants {
-    alt: f32,
-    normal: Vec3<f32>,
-    humid: f32,
-    temp: f32,
-}
-
-/// Used to sample weather that isn't simulated
-fn sample_cell(_p: Vec2<i32>, _time: f64) -> Cell {
-    Cell {
-        wind: Vec2::new(20.0, 20.0),
-        temperature: 0.5,
-        moisture: 0.1,
-        cloud: 0.0,
-    }
-}
-
-#[derive(Clone, Copy, Default)]
-pub struct WeatherInfo {
-    pub lightning_chance: f32,
-}
-
-fn sample_plane_normal(points: &[Vec3<f32>]) -> Option<Vec3<f32>> {
-    if points.len() < 3 {
-        return None;
-    }
-    let sum = points.iter().cloned().sum::<Vec3<f32>>();
-    let centroid = sum / (points.len() as f32);
-
-    let (xx, xy, xz, yy, yz, zz) = {
-        let (mut xx, mut xy, mut xz, mut yy, mut yz, mut zz) = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-        for p in points {
-            let p = *p - centroid;
-            xx += p.x * p.x;
-            xy += p.x * p.y;
-            xz += p.x * p.z;
-            yy += p.y * p.y;
-            yz += p.y * p.z;
-            zz += p.z * p.z;
-        }
-        (xx, xy, xz, yy, yz, zz)
-    };
-
-    let det_x: f32 = yy * zz - yz * yz;
-    let det_y: f32 = xx * zz - xz * xz;
-    let det_z: f32 = xx * yy - xy * xy;
-
-    let det_max = det_x.max(det_y).max(det_z);
-    if det_max <= 0.0 {
-        None
-    } else if det_max == det_x {
-        Some(Vec3::new(det_x, xz * yz - xy * zz, xy * yz - xz * yy).normalized())
-    } else if det_max == det_y {
-        Some(Vec3::new(xy * yz - xy * zz, det_y, xy * xz - yz * xx).normalized())
-    } else {
-        Some(Vec3::new(xy * yz - xz * yy, xy * xz - yz * xx, det_z).normalized())
-    }
-}
-*/
 fn cell_to_wpos(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 }
 
 pub struct WeatherSim {
-    // cells: Grid<Cell>,       // The variables used for simulation
-    // consts: Grid<Constants>, // The constants from the world used for simulation
-    // info: Grid<WeatherInfo>,
     size: Vec2<u32>,
 }
 
 impl WeatherSim {
     pub fn new(size: Vec2<u32>, _world: &World) -> Self {
-        /*
-        let size = size.as_();
-        let this = Self {
-            cells: Grid::new(size, Cell::default()),
-            consts: Grid::from_raw(
-                size,
-                (0..size.x * size.y)
-                    .map(|i| Vec2::new(i % size.x, i / size.x))
-                    .map(|p| {
-                        let mut temp_sum = 0.0;
-
-                        let mut alt_sum = 0.0;
-
-                        let mut humid_sum = 1000.0;
-
-                        let mut points: Vec<Vec3<f32>> =
-                            Vec::with_capacity((CHUNKS_PER_CELL * CHUNKS_PER_CELL) as usize);
-                        for y in 0..CHUNKS_PER_CELL as i32 {
-                            for x in 0..CHUNKS_PER_CELL as i32 {
-                                let chunk_pos = p * CHUNKS_PER_CELL as i32 + Vec2::new(x, y);
-                                if let Some(chunk) = world.sim().get(chunk_pos) {
-                                    let wpos = chunk_pos * TerrainChunkSize::RECT_SIZE.as_();
-                                    let a = world.sim().get_alt_approx(wpos).unwrap_or(0.0);
-                                    alt_sum += a;
-
-                                    let wpos = wpos.as_().with_z(a);
-                                    points.push(wpos);
-
-                                    let height_p = 1.0 - a / world.sim().max_height;
-
-                                    let env = chunk.get_environment();
-                                    temp_sum += env.temp;
-                                    humid_sum += (env.humid * (1.0 + env.near_water)) * height_p;
-                                }
-                            }
-                        }
-                        Constants {
-                            alt: alt_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
-                            humid: humid_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
-                            temp: temp_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32,
-                            normal: sample_plane_normal(&points).unwrap(),
-                        }
-                    })
-                    .collect_vec(),
-            ),
-            info: Grid::new(size, WeatherInfo::default()),
-        };
-        this.cells.iter_mut().for_each(|(point, cell)| {
-            let time = 0.0;
-            *cell = sample_cell(point, time);
-        });
-        this
-        */
         Self { size }
     }
 
-    /*
-    fn get_cell(&self, p: Vec2<i32>, time: f64) -> Cell {
-        *self.cells.get(p).unwrap_or(&sample_cell(p, time))
-    }
-    */
-
-    // https://minds.wisconsin.edu/bitstream/handle/1793/66950/LitzauSpr2013.pdf
     // Time step is cell size / maximum wind speed
     pub fn tick(&mut self, time_of_day: &TimeOfDay, out: &mut WeatherGrid) {
         let time = time_of_day.0;
@@ -182,203 +51,6 @@ impl WeatherSim {
             ) * 200.0
                 * (1.0 - pressure);
         }
-        /*
-        let mut swap = Grid::new(self.cells.size(), Cell::default());
-        // Dissipate wind, humidty and pressure
-        // Dissipation is represented by the target cell expanding into the 8 adjacent
-        // cells. The target cell’s contents are then distributed based the
-        // percentage that overlaps the surrounding cell.
-        for (point, cell) in self.cells.iter() {
-            swap[point] = {
-                let spread = [
-                    (*cell, 0),
-                    (self.get_cell(point + Vec2::new(1, 0), time), 1),
-                    (self.get_cell(point + Vec2::new(-1, 0), time), 1),
-                    (self.get_cell(point + Vec2::new(0, 1), time), 1),
-                    (self.get_cell(point + Vec2::new(0, -1), time), 1),
-                    // Diagonal so less overlap
-                    (self.get_cell(point + Vec2::new(1, 1), time), 2),
-                    (self.get_cell(point + Vec2::new(1, -1), time), 2),
-                    (self.get_cell(point + Vec2::new(-1, 1), time), 2),
-                    (self.get_cell(point + Vec2::new(-1, -1), time), 2),
-                ];
-                let mut cell = Cell::default();
-
-                for (c, p) in spread {
-                    let factor = |rate: f32| {
-                        let rate = (1.0 + rate).powf(DT);
-                        //              1.0
-                        //      ___________________
-                        //     /                   \
-                        // +---+-------------------+---+
-                        // | 2 |         1         | 2 |
-                        // +---+-------------------+---+
-                        // |   |                   |   |
-                        // |   |                   |   |
-                        // |   |                   |   |
-                        // | 1 |         0         | 1 |
-                        // |   |                   |   |
-                        // |   |                   |   |
-                        // |   |                   |   |
-                        // +---+-------------------+---+  \
-                        // | 2 |         1         | 2 |  | rate
-                        // +---+-------------------+---+  /
-                        // \___________________________/
-                        //        2.0 * rate + 1.0
-                        // area_0 = 1.0 * 1.0
-                        // area_1 = rate * 1.0
-                        // area_2 = rate * rate
-
-                        let area = (1.0 + 2.0 * rate).powf(2.0);
-                        match p {
-                            0 => 1.0 * 1.0 / area,   // area_0 / area
-                            1 => rate * 1.0 / area,  // area_1 / area
-                            _ => rate * rate / area, // area_2 / area
-                        }
-                    };
-                    //  0.0 <= dissipation rate <= 1.0 because we only spread to direct neighbours.
-                    cell.wind += c.wind * factor(0.009);
-                    cell.temperature += c.temperature * factor(0.01);
-                    cell.moisture += c.moisture * factor(0.008);
-                    cell.cloud += c.cloud * factor(0.005);
-                }
-                cell
-            }
-        }
-        self.cells = swap.clone();
-        swap.iter_mut().for_each(|(_, cell)| {
-            *cell = Cell::default();
-        });
-
-        // Wind spread
-        // Wind is modeled by taking the target cell
-        // contents and moving it to different cells.
-        // We assume wind will not travel more than 1 cell per tick.
-
-        // Need to spread wind from outside simulation to simulate that the veloren
-        // island is not in a box.
-        for y in -1..=self.cells.size().y {
-            for x in -1..=self.cells.size().x {
-                let point = Vec2::new(x, y);
-                let cell = self.get_cell(point, time);
-                let a = CELL_SIZE - cell.wind.x.abs() * DT;
-                let b = CELL_SIZE - cell.wind.y.abs() * DT;
-                let wind_dir = Vec2::new(cell.wind.x.signum(), cell.wind.y.signum()).as_();
-                let spread = [
-                    (point, a * b / (CELL_SIZE * CELL_SIZE)),
-                    (
-                        point + wind_dir.with_y(0),
-                        (CELL_SIZE - a) * b / (CELL_SIZE * CELL_SIZE),
-                    ),
-                    (
-                        point + wind_dir.with_x(0),
-                        a * (CELL_SIZE - b) / (CELL_SIZE * CELL_SIZE),
-                    ),
-                    (
-                        point + wind_dir,
-                        (CELL_SIZE - a) * (CELL_SIZE - b) / (CELL_SIZE * CELL_SIZE),
-                    ),
-                ];
-                for (p, factor) in spread {
-                    if let Some(c) = swap.get_mut(p) {
-                        c.wind += cell.wind * factor;
-                        c.temperature += cell.temperature * factor;
-                        c.moisture += cell.moisture * factor;
-                        c.cloud += cell.cloud * factor;
-                    }
-                }
-            }
-        }
-
-        // Evaporate moisture and condense clouds
-        for (point, cell) in swap.iter() {
-            let dt = 1.0 - 0.96f32.powf(DT);
-            let day_light = ((2.0
-                * ((time_of_day.0 as f32 / (24.0 * 60.0 * 60.0)) % 1.0 - 0.5).abs())
-                * (1.0 - self.weather[point].cloud / CLOUD_MAX))
-                .clamp(0.0, 1.0);
-
-            self.cells[point].temperature =
-                cell.temperature * (1.0 - dt) + dt * (self.consts[point].temp + 0.1 * day_light);
-
-            let temp_part = ((cell.temperature + WATER_BOILING_POINT)
-                / (WATER_BOILING_POINT * 2.0))
-                .powf(4.0)
-                .clamp(0.0, 1.0);
-
-            // Drag wind based on pressure difference.
-            // note: pressure scales linearly with temperature in this simulation
-            self.cells[point].wind = cell.wind
-                + [
-                    Vec2::new(1, 0),
-                    Vec2::new(1, 1),
-                    Vec2::new(0, 1),
-                    Vec2::new(-1, 0),
-                    Vec2::new(-1, -1),
-                    Vec2::new(0, -1),
-                    Vec2::new(1, -1),
-                    Vec2::new(-1, 1),
-                ]
-                .iter()
-                .filter(|&&p| swap.get(p).is_some())
-                .map(|&p| {
-                    let diff =
-                        (swap.get(p).unwrap().temperature - cell.temperature) / WATER_BOILING_POINT;
-                    p.as_().normalized() * diff * DT
-                })
-                .sum::<Vec2<f32>>();
-
-            // Curve wind based on topography
-            if let Some(xy) = if self.consts[point].normal.z < 1.0 {
-                Some(self.consts[point].normal.xy().normalized())
-            } else {
-                None
-            } {
-                if self.cells[point].wind.dot(xy) > 0.0 {
-                    const WIND_CHECK_START: f32 = 500.0;
-                    const WIND_CHECK_STOP: f32 = 3000.0;
-                    let alt_m = (self.consts[point].alt - WIND_CHECK_START)
-                        / (WIND_CHECK_STOP - WIND_CHECK_START);
-                    let reflected = self.cells[point].wind.reflected(xy) * (1.0 - 0.9f32.powf(DT));
-                    if reflected.x.is_nan() || reflected.y.is_nan() {
-                        panic!("ref is nan");
-                    }
-                    if self.cells[point].wind.x.is_nan() || self.cells[point].wind.y.is_nan() {
-                        panic!("wind is nan");
-                    }
-                    let drag = (1.0 - alt_m) * self.consts[point].normal.z;
-                    self.cells[point].wind = self.cells[point].wind * drag
-                        + reflected * alt_m * (1.0 - self.consts[point].normal.z);
-                }
-            }
-
-            // If positive condense moisture to clouds, if negative evaporate clouds into
-            // moisture.
-            let condensation = cell.moisture * (1.0 - 0.99f32.powf((1.0 - temp_part) * DT))
-                - cell.cloud * (1.0 - 0.98f32.powf(temp_part * DT)) / CLOUD_MAX;
-
-            const CLOUD_MAX: f32 = 15.0;
-            const RAIN_P: f32 = 0.96;
-            let rain_cloud = temp_part * CLOUD_MAX;
-
-            let rain_p_t = ((cell.cloud - rain_cloud) / (CLOUD_MAX - rain_cloud)).max(0.0);
-            let rain = rain_p_t * (1.0 - RAIN_P.powf(DT));
-
-            // Evaporate from ground.
-            self.cells[point].moisture =
-                cell.moisture + (self.consts[point].humid / 100.0) * temp_part * DT - condensation;
-
-            self.cells[point].cloud = (cell.cloud + condensation - rain).max(0.0);
-
-            self.weather[point].cloud = (cell.cloud / CLOUD_MAX).clamp(0.0, 1.0);
-            self.weather[point].rain = rain_p_t;
-            self.weather[point].wind = cell.wind;
-
-            self.info[point].lightning_chance = self.weather[point].cloud.powf(2.0)
-                * (self.weather[point].rain * 0.9 + 0.1)
-                * temp_part;
-        }
-        */
     }
 
     pub fn size(&self) -> Vec2<u32> { self.size }
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index 92f6808493..a03aa7871e 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -354,7 +354,7 @@ impl AudioFrontend {
         }
     }
 
-    // Retrieves the channel currently having the given tag
+    /// Retrieves the channel currently having the given tag
     fn get_ambient_channel(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -452,13 +452,13 @@ impl AudioFrontend {
         }
     }
 
-    // Retrieves the current setting for sfx volume
+    /// Retrieves the current setting for sfx volume
     pub fn get_sfx_volume(&self) -> f32 { self.sfx_volume * self.master_volume }
 
-    // Retrieves the current setting for ambience volume
+    /// Retrieves the current setting for ambience volume
     pub fn get_ambience_volume(&self) -> f32 { self.ambience_volume * self.master_volume }
 
-    // Retrieves the current setting for music volume
+    /// Retrieves the current setting for music volume
     pub fn get_music_volume(&self) -> f32 { self.music_volume * self.master_volume }
 
     pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index c22ab247f4..34a48b5f9f 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -50,7 +50,7 @@ const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000;
 const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000;
 
 // For rain occlusion we only need to render the closest chunks.
-// TODO: Is this a good value?
+/// How many chunks are maximally rendered for rain occlusion.
 pub const RAIN_OCCLUSION_CHUNKS: usize = 9;
 
 /// A type that stores all the layouts associated with this renderer that never
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 70c58a7b8d..c663799531 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -814,7 +814,6 @@ impl<V: RectRasterableVol> Terrain<V> {
         loaded_distance: f32,
         camera: &Camera,
     ) -> (
-        // TODO: Better return type?
         Aabb<f32>,
         Vec<math::Vec3<f32>>,
         math::Aabr<f32>,
@@ -1406,10 +1405,7 @@ impl<V: RectRasterableVol> Terrain<V> {
         // Check if there is rain near the camera
         let max_weather = scene_data.state.max_weather_near(focus_pos.xy());
         let visible_occlusion_volume = if max_weather.rain > 0.0 {
-            let occlusion_box = visible_bounding_box/*.intersection(Aabb {
-                min: focus_pos + camera.dependents().cam_pos - 100.0,
-                max: focus_pos + camera.dependents().cam_pos + 100.0,
-            })*/;
+            let occlusion_box = visible_bounding_box;
             let visible_bounding_box = math::Aabb::<f32> {
                 min: math::Vec3::from(occlusion_box.min - focus_off),
                 max: math::Vec3::from(occlusion_box.max - focus_off),

From dc8424c549f2c22167d32e65ac744418b3f765b2 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Fri, 6 May 2022 12:20:36 -0700
Subject: [PATCH 46/71] Moving audio code

---
 assets/voxygen/audio/soundtrack.ron |   1 -
 client/src/lib.rs                   |   2 +-
 server/src/weather/sim.rs           |   4 +-
 voxygen/src/audio/ambient.rs        | 111 +++++++++++++---------------
 voxygen/src/audio/channel.rs        |   5 +-
 voxygen/src/audio/mod.rs            |  26 +++----
 6 files changed, 70 insertions(+), 79 deletions(-)

diff --git a/assets/voxygen/audio/soundtrack.ron b/assets/voxygen/audio/soundtrack.ron
index ed5c016050..26efe80a21 100644
--- a/assets/voxygen/audio/soundtrack.ron
+++ b/assets/voxygen/audio/soundtrack.ron
@@ -1,4 +1,3 @@
-// TODO: Add an ambient-soundtrack that runs independently from the musical soundtrack
 // Times: Some(Day), Some(Night), None [both]
 // Biomes: Grassland, Forest, Desert, Snowland, Lake, Mountain, Ocean, Jungle, Savannah, Taiga
 //          planned biomes: Swamp
diff --git a/client/src/lib.rs b/client/src/lib.rs
index fe153ecc54..8e7ac858a2 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -1685,7 +1685,7 @@ impl Client {
             self.invite = None;
         }
 
-        // Lerp the clientside weather. 
+        // Lerp the clientside weather.
         self.weather.update(&mut self.state.weather_grid_mut());
 
         // Lerp towards the target time of day - this ensures a smooth transition for
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index e46060cb92..e505c98ef3 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -13,9 +13,7 @@ pub struct WeatherSim {
 }
 
 impl WeatherSim {
-    pub fn new(size: Vec2<u32>, _world: &World) -> Self {
-        Self { size }
-    }
+    pub fn new(size: Vec2<u32>, _world: &World) -> Self { Self { size } }
 
     // Time step is cell size / maximum wind speed
     pub fn tick(&mut self, time_of_day: &TimeOfDay, out: &mut WeatherGrid) {
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 53a04645f8..d1855b7b88 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -48,7 +48,7 @@ impl AmbientMgr {
         // Iterate through each tag
         for tag in AmbientChannelTag::iter() {
             // If the conditions warrant creating a channel of that tag
-            if self.check_ambience_necessity(tag, client, camera)
+            if AmbientMgr::check_ambience_necessity(tag, client, camera)
                 && audio.get_ambient_channel(tag).is_none()
             {
                 // Iterate through the supposed number of channels - one for each tag
@@ -71,8 +71,7 @@ impl AmbientMgr {
                     if audio.ambient_channels[index].get_tag() == tag {
                         // Maintain: get the correct multiplier of whatever the tag of the current
                         // channel is
-                        let target_volume =
-                            audio.ambient_channels[index].maintain(state, client, camera);
+                        let target_volume = AmbientChannel::maintain(tag, state, client, camera);
                         // Get multiplier of the current channel
                         let initial_volume = audio.ambient_channels[index].get_multiplier();
 
@@ -128,12 +127,7 @@ impl AmbientMgr {
         }
     }
 
-    fn check_ambience_necessity(
-        &mut self,
-        tag: AmbientChannelTag,
-        client: &Client,
-        camera: &Camera,
-    ) -> bool {
+    fn check_ambience_necessity(tag: AmbientChannelTag, client: &Client, camera: &Camera) -> bool {
         match tag {
             AmbientChannelTag::Wind => {
                 let focus_off = camera.get_focus_pos().map(f32::trunc);
@@ -188,57 +182,9 @@ impl AmbientMgr {
     }
 }
 
-impl AmbientChannel {
-    pub fn maintain(&mut self, state: &State, client: &Client, camera: &Camera) -> f32 {
-        let tag = self.get_tag();
-
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
-
-        let mut target_volume: f32 = self.get_ambience_volume(tag, client, camera);
-
-        target_volume = self.check_camera(state, client, cam_pos, target_volume);
-
-        target_volume
-    }
-
-    fn check_camera(
-        &mut self,
-        state: &State,
-        client: &Client,
-        cam_pos: Vec3<f32>,
-        initial_volume: f32,
-    ) -> f32 {
-        let mut volume_multiplier = initial_volume;
-        let terrain_alt = if let Some(chunk) = client.current_chunk() {
-            chunk.meta().alt()
-        } else {
-            0.0
-        };
-        // Checks if the camera is underwater to diminish ambient sounds
-        if state
-            .terrain()
-            .get((cam_pos).map(|e| e.floor() as i32))
-            .map(|b| b.is_liquid())
-            .unwrap_or(false)
-        {
-            volume_multiplier *= 0.1;
-        }
-        // Is the camera roughly under the terrain?
-        if cam_pos.z < terrain_alt - 20.0 {
-            volume_multiplier = 0.0;
-        }
-
-        volume_multiplier.clamped(0.0, 1.0)
-    }
-
+impl AmbientChannelTag {
     // Gets appropriate volume for each tag
-    fn get_ambience_volume(
-        &mut self,
-        tag: AmbientChannelTag,
-        client: &Client,
-        camera: &Camera,
-    ) -> f32 {
+    pub fn get_tag_volume(tag: AmbientChannelTag, client: &Client, camera: &Camera) -> f32 {
         match tag {
             AmbientChannelTag::Wind => {
                 let focus_off = camera.get_focus_pos().map(f32::trunc);
@@ -327,6 +273,53 @@ impl AmbientChannel {
     }
 }
 
+impl AmbientChannel {
+    pub fn maintain(
+        tag: AmbientChannelTag,
+        state: &State,
+        client: &Client,
+        camera: &Camera,
+    ) -> f32 {
+        let focus_off = camera.get_focus_pos().map(f32::trunc);
+        let cam_pos = camera.dependents().cam_pos + focus_off;
+
+        let mut target_volume: f32 = AmbientChannelTag::get_tag_volume(tag, client, camera);
+
+        target_volume = AmbientChannel::check_camera(state, client, cam_pos, target_volume);
+
+        target_volume
+    }
+
+    fn check_camera(
+        state: &State,
+        client: &Client,
+        cam_pos: Vec3<f32>,
+        initial_volume: f32,
+    ) -> f32 {
+        let mut volume_multiplier = initial_volume;
+        let terrain_alt = if let Some(chunk) = client.current_chunk() {
+            chunk.meta().alt()
+        } else {
+            0.0
+        };
+        // Checks if the camera is underwater to diminish ambient sounds
+        if state
+            .terrain()
+            .get((cam_pos).map(|e| e.floor() as i32))
+            .map(|b| b.is_liquid())
+            .unwrap_or(false)
+        {
+            volume_multiplier *= 0.1;
+        }
+        // Is the camera roughly under the terrain?
+        if cam_pos.z < terrain_alt - 20.0 {
+            volume_multiplier = 0.0;
+        }
+
+        volume_multiplier.clamped(0.0, 1.0)
+    }
+}
+
 pub fn load_ambience_items() -> AssetHandle<AmbientCollection> {
     AmbientCollection::load_or_insert_with("voxygen.audio.ambient", |error| {
         warn!(
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index bc5f4ccd4d..0f2b612f71 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -166,6 +166,7 @@ pub enum AmbientChannelTag {
     Thunder,
     Leaves,
 }
+
 /// A AmbientChannel uses a non-positional audio sink designed to play sounds
 /// which are always heard at the camera's position.
 pub struct AmbientChannel {
@@ -297,11 +298,11 @@ impl SfxChannel {
     }
 }
 
-pub struct UIChannel {
+pub struct UiChannel {
     sink: Sink,
 }
 
-impl UIChannel {
+impl UiChannel {
     pub fn new(stream: &OutputStreamHandle) -> Self {
         Self {
             sink: Sink::try_new(stream).unwrap(),
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index a03aa7871e..dbf4e590b1 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -8,7 +8,7 @@ pub mod sfx;
 pub mod soundcache;
 
 use channel::{
-    AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UIChannel,
+    AmbientChannel, AmbientChannelTag, MusicChannel, MusicChannelTag, SfxChannel, UiChannel,
 };
 use fader::Fader;
 use music::MusicTransitionManifest;
@@ -40,17 +40,17 @@ pub struct AudioFrontend {
     //pub device_list: Vec<String>,
     //pub audio_device: Option<Device>,
     pub stream: Option<rodio::OutputStream>,
-    pub audio_stream: Option<rodio::OutputStreamHandle>,
+    audio_stream: Option<rodio::OutputStreamHandle>,
 
-    pub music_channels: Vec<MusicChannel>,
-    pub ambient_channels: Vec<AmbientChannel>,
-    pub sfx_channels: Vec<SfxChannel>,
-    pub ui_channels: Vec<UIChannel>,
-    pub sfx_volume: f32,
-    pub ambience_volume: f32,
-    pub music_volume: f32,
-    pub master_volume: f32,
-    pub listener: Listener,
+    music_channels: Vec<MusicChannel>,
+    ambient_channels: Vec<AmbientChannel>,
+    sfx_channels: Vec<SfxChannel>,
+    ui_channels: Vec<UiChannel>,
+    sfx_volume: f32,
+    ambience_volume: f32,
+    music_volume: f32,
+    master_volume: f32,
+    listener: Listener,
 
     mtm: AssetHandle<MusicTransitionManifest>,
 }
@@ -87,7 +87,7 @@ impl AudioFrontend {
 
         let mut ui_channels = Vec::with_capacity(num_ui_channels);
         if let Some(audio_stream) = &audio_stream {
-            ui_channels.resize_with(num_ui_channels, || UIChannel::new(audio_stream))
+            ui_channels.resize_with(num_ui_channels, || UiChannel::new(audio_stream))
         }
 
         Self {
@@ -164,7 +164,7 @@ impl AudioFrontend {
         None
     }
 
-    fn get_ui_channel(&mut self) -> Option<&mut UIChannel> {
+    fn get_ui_channel(&mut self) -> Option<&mut UiChannel> {
         if self.audio_stream.is_some() {
             let sfx_volume = self.get_sfx_volume();
             if let Some(channel) = self.ui_channels.iter_mut().find(|c| c.is_done()) {

From fd12c8abdba332e202e48121ccca52804c415640 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sat, 28 May 2022 15:40:13 +0200
Subject: [PATCH 47/71] Remove dead shader code, clean up weather.rs

---
 assets/voxygen/shaders/fluid-frag/shiny.glsl |  8 +--
 assets/voxygen/shaders/include/sky.glsl      |  3 +-
 client/src/lib.rs                            | 10 ++--
 common/src/weather.rs                        | 52 ++++++++++----------
 server/src/weather/mod.rs                    |  9 ++--
 5 files changed, 42 insertions(+), 40 deletions(-)

diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index 334784b835..4cf052b57e 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -58,7 +58,7 @@ vec3 warp_normal(vec3 norm, vec3 pos, float time) {
         + smooth_rand(pos * 0.25, time * 0.25) * 0.1);
 }
 
-float wave_height(vec3 pos, vec3 surf_norm) {
+float wave_height(vec3 pos) {
     float timer = tick.x * 0.75;
 
     pos *= 0.5;
@@ -134,9 +134,9 @@ void main() {
 
     vec3 wave_pos = mod(f_pos + focus_off.xyz, vec3(100.0));
     float wave_sample_dist = 0.025;
-    float wave00 = wave_height(wave_pos, surf_norm);
-    float wave10 = wave_height(wave_pos + vec3(wave_sample_dist, 0, 0), surf_norm);
-    float wave01 = wave_height(wave_pos + vec3(0, wave_sample_dist, 0), surf_norm);
+    float wave00 = wave_height(wave_pos);
+    float wave10 = wave_height(wave_pos + vec3(wave_sample_dist, 0, 0));
+    float wave01 = wave_height(wave_pos + vec3(0, wave_sample_dist, 0));
 
     // Possibility of div by zero when slope = 0,
     // however this only results in no water surface appearing
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index dc94dc5153..eb47eb2a6f 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -113,11 +113,10 @@ layout(set = 0, binding = 12) uniform texture2D t_weather;
 layout(set = 0, binding = 13) uniform sampler s_weather;
 
 vec4 sample_weather(vec2 wpos) {
-    return textureLod(sampler2D(t_weather, s_weather), pos_to_uv(t_alt, s_alt, wpos - focus_off.xy), 0); // TODO: make this work for any world size
+    return textureLod(sampler2D(t_weather, s_weather), pos_to_uv(t_alt, s_alt, wpos - focus_off.xy), 0);
 }
 
 float cloud_tendency_at(vec2 pos) {
-    //float nz = textureLod(sampler2D(t_noise, s_noise), (pos + wind_offset) / 60000.0 / cloud_scale, 0).x - 0.3;
     return sample_weather(pos).r;
 }
 
diff --git a/client/src/lib.rs b/client/src/lib.rs
index 8e7ac858a2..273b8b3beb 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -162,6 +162,7 @@ impl WeatherLerp {
         self.old = mem::replace(&mut self.new, (weather, Instant::now()));
     }
 
+    // TODO: Make impårovements to this interpolation, it's main issue is assuming that updates come at regular intervals. 
     fn update(&mut self, to_update: &mut WeatherGrid) {
         prof_span!("WeatherLerp::update");
         let old = &self.old.0;
@@ -173,7 +174,7 @@ impl WeatherLerp {
             *to_update = new.clone();
         }
         if old.size() == new.size() {
-            // Assume updates are regular
+            // Assumes updates are regular
             let t = (self.new.1.elapsed().as_secs_f32()
                 / self.new.1.duration_since(self.old.1).as_secs_f32())
             .clamp(0.0, 1.0);
@@ -182,7 +183,7 @@ impl WeatherLerp {
                 .iter_mut()
                 .zip(old.iter().zip(new.iter()))
                 .for_each(|((_, current), ((_, old), (_, new)))| {
-                    *current = Weather::lerp(old, new, t);
+                    *current = Weather::lerp_unclamped(old, new, t);
                 });
         }
     }
@@ -191,8 +192,8 @@ impl WeatherLerp {
 impl Default for WeatherLerp {
     fn default() -> Self {
         Self {
-            old: (WeatherGrid::new(Vec2::broadcast(0)), Instant::now()),
-            new: (WeatherGrid::new(Vec2::broadcast(0)), Instant::now()),
+            old: (WeatherGrid::new(Vec2::zero()), Instant::now()),
+            new: (WeatherGrid::new(Vec2::zero()), Instant::now()),
         }
     }
 }
@@ -1461,6 +1462,7 @@ impl Client {
             .map(|v| v.0)
     }
 
+    /// Returns Weather::default if no player position exists.
     pub fn weather_at_player(&self) -> Weather {
         self.position()
             .map(|wpos| self.state.weather_at(wpos.xy()))
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 08cddcb767..12561facaa 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -20,24 +20,23 @@ impl Weather {
     pub fn new(cloud: f32, rain: f32, wind: Vec2<f32>) -> Self { Self { cloud, rain, wind } }
 
     pub fn get_kind(&self) -> WeatherKind {
-        match (
-            (self.cloud * 10.0) as i32,
-            (self.rain * 10.0) as i32,
-            (self.wind.magnitude() * 10.0) as i32,
-        ) {
-            // Over 24.5 m/s wind is a storm
-            (_, _, 245..) => WeatherKind::Storm,
-            (_, 1..=10, _) => WeatherKind::Rain,
-            (4..=10, _, _) => WeatherKind::Cloudy,
-            _ => WeatherKind::Clear,
+        // Over 24.5 m/s wind is a storm
+        if self.wind.magnitude_squared() >= 24.5f32.powi(2) {
+            WeatherKind::Storm
+        } else if (0.1..=1.0).contains(&self.rain) {
+            WeatherKind::Rain
+        } else if (0.2..=1.0).contains(&self.cloud) {
+            WeatherKind::Cloudy
+        } else {
+            WeatherKind::Clear
         }
     }
 
-    pub fn lerp(from: &Self, to: &Self, t: f32) -> Self {
+    pub fn lerp_unclamped(from: &Self, to: &Self, t: f32) -> Self {
         Self {
-            cloud: f32::lerp(from.cloud, to.cloud, t),
-            rain: f32::lerp(from.rain, to.rain, t),
-            wind: Vec2::<f32>::lerp(from.wind, to.wind, t),
+            cloud: f32::lerp_unclamped(from.cloud, to.cloud, t),
+            rain: f32::lerp_unclamped(from.rain, to.rain, t),
+            wind: Vec2::<f32>::lerp_unclamped(from.wind, to.wind, t),
         }
     }
 
@@ -68,18 +67,18 @@ impl fmt::Display for WeatherKind {
     }
 }
 
+// How many chunks wide a weather cell is.
+// So one weather cell has (CHUNKS_PER_CELL * CHUNKS_PER_CELL) chunks.
 pub const CHUNKS_PER_CELL: u32 = 16;
 
 pub const CELL_SIZE: u32 = CHUNKS_PER_CELL * TerrainChunkSize::RECT_SIZE.x;
 
-/// How often the weather is updated, in seconds
-pub const WEATHER_DT: f32 = 5.0;
-
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct WeatherGrid {
     weather: Grid<Weather>,
 }
 
+/// Returns the center of the weather cell at the given position
 fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 }
 
 // TODO: Move consts from world to common to avoid duplication
@@ -97,6 +96,7 @@ const LOCALITY: [Vec2<i32>; 9] = [
 
 impl WeatherGrid {
     pub fn new(size: Vec2<u32>) -> Self {
+        size.map(|e| debug_assert!(i32::try_from(e).is_ok()));
         Self {
             weather: Grid::new(size.as_(), Weather::default()),
         }
@@ -114,24 +114,24 @@ impl WeatherGrid {
     /// interpolation between four cells.
     pub fn get_interpolated(&self, wpos: Vec2<f32>) -> Weather {
         let cell_pos = to_cell_pos(wpos);
-        let rpos = cell_pos.map(|e| e.fract());
+        let rpos = cell_pos.map(|e| e.fract() + (1.0 - e.signum()) / 2.0);
         let cell_pos = cell_pos.map(|e| e.floor());
 
-        let wpos = cell_pos.as_::<i32>();
-        Weather::lerp(
-            &Weather::lerp(
-                self.weather.get(wpos).unwrap_or(&Weather::default()),
+        let cpos = cell_pos.as_::<i32>();
+        Weather::lerp_unclamped(
+            &Weather::lerp_unclamped(
+                self.weather.get(cpos).unwrap_or(&Weather::default()),
                 self.weather
-                    .get(wpos + Vec2::unit_x())
+                    .get(cpos + Vec2::unit_x())
                     .unwrap_or(&Weather::default()),
                 rpos.x,
             ),
-            &Weather::lerp(
+            &Weather::lerp_unclamped(
                 self.weather
-                    .get(wpos + Vec2::unit_x())
+                    .get(cpos + Vec2::unit_y())
                     .unwrap_or(&Weather::default()),
                 self.weather
-                    .get(wpos + Vec2::one())
+                    .get(cpos + Vec2::one())
                     .unwrap_or(&Weather::default()),
                 rpos.x,
             ),
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index 135beda71c..bf6dd6f757 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -1,4 +1,4 @@
-use common::weather::{CHUNKS_PER_CELL, WEATHER_DT};
+use common::weather::CHUNKS_PER_CELL;
 use common_ecs::{dispatch, System};
 use common_state::State;
 use specs::DispatcherBuilder;
@@ -16,12 +16,13 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
 }
 
 pub fn init(state: &mut State, world: &world::World) {
-    // How many chunks wide a weather cell is.
-    // 16 here means that a weather cell is 16x16 chunks.
     let weather_size = world.sim().get_size() / CHUNKS_PER_CELL;
     let sim = sim::WeatherSim::new(weather_size, world);
     state.ecs_mut().insert(sim);
-    // Tick weather every 2 seconds
+
+    /// How often the weather is updated, in seconds
+    pub const WEATHER_DT: f32 = 5.0;
+
     state
         .ecs_mut()
         .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(

From fa589e915e343ed0259b7bd583423278353d8c47 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sat, 28 May 2022 16:58:20 +0200
Subject: [PATCH 48/71] make rain view distance smaller, dont put wind in
 texture

---
 assets/voxygen/shaders/clouds-frag.glsl      | 4 ++--
 assets/voxygen/shaders/fluid-frag/shiny.glsl | 2 +-
 voxygen/src/scene/lod.rs                     | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index e76f1ee464..deca307a2b 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -111,8 +111,8 @@ void main() {
     vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
     vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-    float rain_dist = 250.0;
-    for (int i = 0; i < 7; i ++) {
+    float rain_dist = 50.0;
+    for (int i = 0; i < 4; i ++) {
         float old_rain_dist = rain_dist;
         rain_dist *= 0.3;
 
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index 4cf052b57e..20c6b7160e 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -153,8 +153,8 @@ void main() {
     if (rain_density > 0 && surf_norm.z > 0.5) {
         vec3 drop_density = vec3(2, 2, 2);
         vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
-        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
         vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+        drop_pos.z += noise_2d(cell2d * 13.1) * 10;
         drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
         vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 58f1c58306..c78b1e6727 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -195,8 +195,8 @@ impl Lod {
                     [
                         (w.cloud * 255.0) as u8,
                         (w.rain * 255.0) as u8,
-                        (w.wind.x + 128.0).clamp(0.0, 255.0) as u8,
-                        (w.wind.y + 128.0).clamp(0.0, 255.0) as u8,
+                        0,
+                        0,
                     ]
                 })
                 .collect::<Vec<_>>(),

From 0d21361e05c04f119f412bb4cf0334df8832fdec Mon Sep 17 00:00:00 2001
From: Treeco <5021038-Treeco@users.noreply.gitlab.com>
Date: Mon, 30 May 2022 22:02:22 +0100
Subject: [PATCH 49/71] Add player-relative rain direction

---
 assets/voxygen/shaders/clouds-frag.glsl       |  9 ++---
 assets/voxygen/shaders/include/globals.glsl   |  1 -
 .../shaders/include/rain_occlusion.glsl       |  3 ++
 .../shaders/rain-occlusion-directed-vert.glsl |  3 ++
 .../shaders/rain-occlusion-figure-vert.glsl   |  3 ++
 common/src/weather.rs                         |  6 ++--
 voxygen/src/render/pipelines/mod.rs           | 11 +++---
 .../src/render/pipelines/rain_occlusion.rs    | 19 +++++++---
 voxygen/src/scene/mod.rs                      | 36 ++++++++++++-------
 voxygen/src/scene/simple.rs                   |  1 -
 10 files changed, 57 insertions(+), 35 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index deca307a2b..5c5e842537 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -101,10 +101,7 @@ void main() {
 
     vec3 old_color = color.rgb;
 
-    // If this value is changed also change it in common/src/weather.rs
-    float fall_rate = 70.0;
-    dir.xy += wind_vel * dir.z / fall_rate;
-    dir = normalize(dir);
+    dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
     float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
     vec2 dir_2d = normalize(dir.xy);
@@ -119,7 +116,7 @@ void main() {
         vec2 drop_density = vec2(30, 1);
 
         vec2 rain_pos = (view_pos * rain_dist);
-        rain_pos += vec2(0, tick.x * fall_rate + cam_wpos.z);
+        rain_pos.y += integrated_rain_vel;
 
         vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
@@ -144,7 +141,7 @@ void main() {
         }
         vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
 
-        vec2 drop_size = vec2(0.0008, 0.05);
+        vec2 drop_size = vec2(0.0008, 0.03);
         float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
         float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
         float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl
index 8e822cf988..cc75645b39 100644
--- a/assets/voxygen/shaders/include/globals.glsl
+++ b/assets/voxygen/shaders/include/globals.glsl
@@ -19,7 +19,6 @@ layout(std140, set = 0, binding = 0) uniform u_globals {
     uvec4 medium;
     ivec4 select_pos;
     vec4 gamma_exposure;
-    vec2 wind_vel;
     float ambiance;
     // 0 - FirstPerson
     // 1 - ThirdPerson
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index a00d2e549a..8768489481 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -12,6 +12,9 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 occlusionMatrices;
     mat4 occlusion_texture_mat;
+    mat4 rel_rain_dir_mat;
+    float integrated_rain_vel;
+    vec3 occlusion_dummy; // Fix alignment.
 };
 
 float rain_occlusion_at(in vec3 fragPos)
diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
index bb7b30906d..335ba92214 100644
--- a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
@@ -28,6 +28,9 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 rainOcclusionMatrices;
     mat4 texture_mat;
+    mat4 rel_rain_dir_mat;
+    float integrated_rain_vel;
+    vec3 occlusion_dummy; // Fix alignment.
 };
 
 /* Accurate packed shadow maps for many lights at once!
diff --git a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
index ae9ecb90a7..800a74b6b3 100644
--- a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
@@ -30,6 +30,9 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 rainOcclusionMatrices;
     mat4 texture_mat;
+    mat4 rel_rain_dir_mat;
+    float integrated_rain_vel;
+    vec3 occlusion_dummy; // Fix alignment.
 };
 
 /* Accurate packed shadow maps for many lights at once!
diff --git a/common/src/weather.rs b/common/src/weather.rs
index 12561facaa..0e415da3b4 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -5,6 +5,8 @@ use vek::{Lerp, Vec2, Vec3};
 
 use crate::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize};
 
+pub const FALL_RATE: f32 = 20.0;
+
 /// Weather::default is Clear, 0 degrees C and no wind
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
 pub struct Weather {
@@ -42,9 +44,7 @@ impl Weather {
 
     // Get the rain direction for this weather
     pub fn rain_dir(&self) -> Vec3<f32> {
-        // If this value is changed also change it in cloud-frag.glsl
-        const FALL_RATE: f32 = 70.0;
-        (-Vec3::unit_z() + self.wind / FALL_RATE).normalized()
+        (-Vec3::unit_z() + self.wind / (2.0 * FALL_RATE)).normalized()
     }
 }
 
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index 2175cbe57d..a6ce260b80 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -64,12 +64,11 @@ pub struct Globals {
     medium: [u32; 4],
     select_pos: [i32; 4],
     gamma_exposure: [f32; 4],
-    wind_vel: [f32; 2],
     ambiance: f32,
     cam_mode: u32,
     sprite_render_distance: f32,
-    /// To keep 16-byte-aligned.
-    globals_dummy: f32,
+    // To keep 16-byte-aligned.
+    globals_dummy: [f32; 1],
 }
 
 #[repr(C)]
@@ -110,8 +109,8 @@ impl Globals {
         ambiance: f32,
         cam_mode: CameraMode,
         sprite_render_distance: f32,
-        wind_vel: Vec2<f32>,
     ) -> Self {
+        // dbg!(core::mem::size_of::<Self>() % 16);
         Self {
             view_mat: view_mat.into_col_arrays(),
             proj_mat: proj_mat.into_col_arrays(),
@@ -156,11 +155,10 @@ impl Globals {
                 .unwrap_or_else(Vec4::zero)
                 .into_array(),
             gamma_exposure: [gamma, exposure, 0.0, 0.0],
-            wind_vel: [wind_vel.x, wind_vel.y],
             ambiance: ambiance.clamped(0.0, 1.0),
             cam_mode: cam_mode as u32,
             sprite_render_distance,
-            globals_dummy: 0.0,
+            globals_dummy: [0.0; 1],
         }
     }
 
@@ -206,7 +204,6 @@ impl Default for Globals {
             1.0,
             CameraMode::ThirdPerson,
             250.0,
-            Vec2::zero(),
         )
     }
 }
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index 0ff61f769c..2ea313955d 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -5,21 +5,32 @@ use bytemuck::{Pod, Zeroable};
 use vek::*;
 
 #[repr(C)]
-#[derive(Copy, Clone, Debug, Zeroable, Pod)]
+#[derive(Copy, Clone, Debug, Zeroable, Pod, Default)]
 pub struct Locals {
     shadow_matrices: [[f32; 4]; 4],
     texture_mats: [[f32; 4]; 4],
+    rel_rain_dir_mat: [[f32; 4]; 4],
+    integrated_rain_vel: f32,
+    // To keep 16-byte-aligned.
+    occlusion_dummy: [f32; 3],
 }
 
 impl Locals {
-    pub fn new(shadow_mat: Mat4<f32>, texture_mat: Mat4<f32>) -> Self {
+    pub fn new(
+        shadow_mat: Mat4<f32>,
+        texture_mat: Mat4<f32>,
+        rel_rain_dir_mat: Mat4<f32>,
+        integrated_rain_vel: f32,
+    ) -> Self {
+        // dbg!(core::mem::size_of::<Self>() % 16);
         Self {
             shadow_matrices: shadow_mat.into_col_arrays(),
             texture_mats: texture_mat.into_col_arrays(),
+            rel_rain_dir_mat: rel_rain_dir_mat.into_col_arrays(),
+            integrated_rain_vel,
+            occlusion_dummy: [0.0; 3],
         }
     }
-
-    pub fn default() -> Self { Self::new(Mat4::identity(), Mat4::identity()) }
 }
 
 pub type BoundLocals = Bound<Consts<Locals>>;
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 3826252de5..7240d0c9f7 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -103,6 +103,8 @@ pub struct Scene {
     pub sfx_mgr: SfxMgr,
     music_mgr: MusicMgr,
     ambient_mgr: AmbientMgr,
+
+    integrated_rain_vel: f32,
 }
 
 pub struct SceneData<'a> {
@@ -319,6 +321,7 @@ impl Scene {
             ambient_mgr: AmbientMgr {
                 ambience: ambient::load_ambience_items(),
             },
+            integrated_rain_vel: 0.0,
         }
     }
 
@@ -475,11 +478,18 @@ impl Scene {
         // Get player position.
         let ecs = scene_data.state.ecs();
 
+        let dt = ecs.fetch::<DeltaTime>().0;
+
         let player_pos = ecs
             .read_storage::<comp::Pos>()
             .get(scene_data.player_entity)
             .map_or(Vec3::zero(), |pos| pos.0);
 
+        let player_vel = ecs
+            .read_storage::<comp::Vel>()
+            .get(scene_data.player_entity)
+            .map_or(Vec3::zero(), |vel| vel.0);
+
         let player_rolling = ecs
             .read_storage::<comp::CharacterState>()
             .get(scene_data.player_entity)
@@ -538,11 +548,8 @@ impl Scene {
         };
 
         // Tick camera for interpolation.
-        self.camera.update(
-            scene_data.state.get_time(),
-            scene_data.state.get_delta_time(),
-            scene_data.mouse_smoothing,
-        );
+        self.camera
+            .update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
 
         // Compute camera matrices.
         self.camera.compute_dependents(&*scene_data.state.terrain());
@@ -613,7 +620,6 @@ impl Scene {
         renderer.update_consts(&mut self.data.lights, lights);
 
         // Update event lights
-        let dt = ecs.fetch::<DeltaTime>().0;
         self.event_lights.drain_filter(|el| {
             el.timeout -= dt;
             el.timeout <= 0.0
@@ -688,10 +694,6 @@ impl Scene {
             scene_data.ambiance,
             self.camera.get_mode(),
             scene_data.sprite_render_distance as f32 - 20.0,
-            client
-                .state()
-                .weather_at(focus_off.xy() + cam_pos.xy())
-                .wind,
         )]);
         renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
         renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
@@ -1003,13 +1005,21 @@ impl Scene {
             .max_weather_near(focus_off.xy() + cam_pos.xy());
         if weather.rain > 0.0 {
             let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
-            let rain_dir = math::Vec3::from(weather.rain_dir());
+            let rain_dir = weather.rain_dir();
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_dir, up);
+            let rain_vel = rain_dir * common::weather::FALL_RATE - player_vel;
+            self.integrated_rain_vel += rain_vel.magnitude() * dt;
+            let rel_rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
 
             let (shadow_mat, texture_mat) =
-                directed_mats(rain_view_mat, rain_dir, &visible_occlusion_volume);
+                directed_mats(rain_view_mat, rain_dir.into(), &visible_occlusion_volume);
 
-            let rain_occlusion_locals = RainOcclusionLocals::new(shadow_mat, texture_mat);
+            let rain_occlusion_locals = RainOcclusionLocals::new(
+                shadow_mat,
+                texture_mat,
+                rel_rain_dir_mat,
+                self.integrated_rain_vel,
+            );
 
             renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
         }
diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs
index 3bf60e5ed7..7e5d284e30 100644
--- a/voxygen/src/scene/simple.rs
+++ b/voxygen/src/scene/simple.rs
@@ -276,7 +276,6 @@ impl Scene {
             scene_data.ambiance,
             self.camera.get_mode(),
             250.0,
-            Vec2::zero(),
         )]);
 
         self.figure_model_cache

From 9104a075308ff3abf7eb84ccc5a7637d3b057ff5 Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Mon, 30 May 2022 23:25:56 -0700
Subject: [PATCH 50/71] Code cleanup, doubled volume of leaves.ogg

---
 assets/voxygen/audio/ambience/leaves.ogg |  4 +-
 client/src/lib.rs                        |  3 +-
 voxygen/src/audio/ambient.rs             | 76 +++++-------------------
 voxygen/src/scene/lod.rs                 | 11 +---
 4 files changed, 22 insertions(+), 72 deletions(-)

diff --git a/assets/voxygen/audio/ambience/leaves.ogg b/assets/voxygen/audio/ambience/leaves.ogg
index ee07a5c77a..a5ac1765f6 100644
--- a/assets/voxygen/audio/ambience/leaves.ogg
+++ b/assets/voxygen/audio/ambience/leaves.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:04fc161500a4ea9bdbc280c1f88df95baeb06d77c2ee3beb5feddcec25274ff5
-size 449134
+oid sha256:63a218856961a57daf7b81ee25a6440cce9d76121dc6c48861e96211741c7d62
+size 454547
diff --git a/client/src/lib.rs b/client/src/lib.rs
index 273b8b3beb..6398a07cf0 100644
--- a/client/src/lib.rs
+++ b/client/src/lib.rs
@@ -162,7 +162,8 @@ impl WeatherLerp {
         self.old = mem::replace(&mut self.new, (weather, Instant::now()));
     }
 
-    // TODO: Make impårovements to this interpolation, it's main issue is assuming that updates come at regular intervals. 
+    // TODO: Make impprovements to this interpolation, it's main issue is assuming
+    // that updates come at regular intervals.
     fn update(&mut self, to_update: &mut WeatherGrid) {
         prof_span!("WeatherLerp::update");
         let old = &self.old.0;
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index d1855b7b88..9e568cad71 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -48,8 +48,20 @@ impl AmbientMgr {
         // Iterate through each tag
         for tag in AmbientChannelTag::iter() {
             // If the conditions warrant creating a channel of that tag
-            if AmbientMgr::check_ambience_necessity(tag, client, camera)
-                && audio.get_ambient_channel(tag).is_none()
+            if match tag {
+                AmbientChannelTag::Wind => {
+                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.0
+                },
+                AmbientChannelTag::Rain => {
+                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.1
+                },
+                AmbientChannelTag::Thunder => {
+                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.0
+                },
+                AmbientChannelTag::Leaves => {
+                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.1
+                },
+            } && audio.get_ambient_channel(tag).is_none()
             {
                 // Iterate through the supposed number of channels - one for each tag
                 for index in 0..AmbientChannelTag::iter().len() {
@@ -126,60 +138,6 @@ impl AmbientMgr {
             }
         }
     }
-
-    fn check_ambience_necessity(tag: AmbientChannelTag, client: &Client, camera: &Camera) -> bool {
-        match tag {
-            AmbientChannelTag::Wind => {
-                let focus_off = camera.get_focus_pos().map(f32::trunc);
-                let cam_pos = camera.dependents().cam_pos + focus_off;
-
-                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-                    (chunk.meta().alt(), chunk.meta().tree_density())
-                } else {
-                    (0.0, 0.0)
-                };
-
-                let alt_multiplier = (cam_pos.z / 1200.0).abs();
-
-                let tree_multiplier = ((1.0 - tree_density)
-                    + ((cam_pos.z - terrain_alt).abs() / 150.0).powi(2))
-                .min(1.0);
-
-                alt_multiplier * tree_multiplier > 0.0
-            },
-            AmbientChannelTag::Rain => {
-                let focus_off = camera.get_focus_pos().map(f32::trunc);
-                let cam_pos = camera.dependents().cam_pos + focus_off;
-
-                let terrain_alt = if let Some(chunk) = client.current_chunk() {
-                    chunk.meta().alt()
-                } else {
-                    0.0
-                };
-                let camera_multiplier =
-                    1.0 - ((cam_pos.z - terrain_alt).abs() / 75.0).powi(2).min(1.0);
-
-                client.weather_at_player().rain > 0.001 || camera_multiplier > 0.0
-            },
-            AmbientChannelTag::Thunder => client.weather_at_player().rain * 500.0 > 0.7,
-            AmbientChannelTag::Leaves => {
-                let focus_off = camera.get_focus_pos().map(f32::trunc);
-                let cam_pos = camera.dependents().cam_pos + focus_off;
-
-                let (terrain_alt, tree_density) = if let Some(chunk) = client.current_chunk() {
-                    (chunk.meta().alt(), chunk.meta().tree_density())
-                } else {
-                    (0.0, 0.0)
-                };
-                let tree_multiplier = 1.0
-                    - (((1.0 - tree_density)
-                        + ((cam_pos.z - terrain_alt + 20.0).abs() / 150.0).powi(2))
-                    .min(1.0));
-
-                tree_multiplier > 0.1
-            },
-        }
-    }
 }
 
 impl AmbientChannelTag {
@@ -237,12 +195,10 @@ impl AmbientChannelTag {
                 rain_intensity.min(0.9)
             },
             AmbientChannelTag::Thunder => {
-                let rain_intensity = client.weather_at_player().rain * 500.0;
-
-                if rain_intensity < 0.7 {
+                if client.weather_at_player().rain * 500.0 < 0.7 {
                     0.0
                 } else {
-                    rain_intensity
+                    client.weather_at_player().rain * 500.0
                 }
             },
             AmbientChannelTag::Leaves => {
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index c78b1e6727..93a11fe4d7 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -13,7 +13,7 @@ use common::{
     lod,
     spiral::Spiral2d,
     util::srgba_to_linear,
-    weather
+    weather,
 };
 use hashbrown::HashMap;
 use std::ops::Range;
@@ -191,14 +191,7 @@ impl Lod {
             [size.x, size.y],
             &weather
                 .iter()
-                .map(|(_, w)| {
-                    [
-                        (w.cloud * 255.0) as u8,
-                        (w.rain * 255.0) as u8,
-                        0,
-                        0,
-                    ]
-                })
+                .map(|(_, w)| [(w.cloud * 255.0) as u8, (w.rain * 255.0) as u8, 0, 0])
                 .collect::<Vec<_>>(),
         );
     }

From 121cf3becc82707c99584252c888ad87b54d7754 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 31 May 2022 17:09:28 +0200
Subject: [PATCH 51/71] Comment fixes

---
 common/src/weather.rs     | 3 ++-
 server/src/weather/mod.rs | 4 +++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/common/src/weather.rs b/common/src/weather.rs
index 0e415da3b4..235ea06702 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -78,7 +78,8 @@ pub struct WeatherGrid {
     weather: Grid<Weather>,
 }
 
-/// Returns the center of the weather cell at the given position
+/// Transforms a world position to cell coordinates. Where (0.0, 0.0) in cell
+/// coordinates is the center of the weather cell located at (0, 0) in the grid.
 fn to_cell_pos(wpos: Vec2<f32>) -> Vec2<f32> { wpos / CELL_SIZE as f32 - 0.5 }
 
 // TODO: Move consts from world to common to avoid duplication
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index bf6dd6f757..f24d23660e 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -21,8 +21,10 @@ pub fn init(state: &mut State, world: &world::World) {
     state.ecs_mut().insert(sim);
 
     /// How often the weather is updated, in seconds
-    pub const WEATHER_DT: f32 = 5.0;
+    const WEATHER_DT: f32 = 5.0;
 
+    // NOTE: If weather computations get too heavy, this should not block the main
+    // thread.
     state
         .ecs_mut()
         .insert(SysScheduler::<tick::Sys>::every(Duration::from_secs_f32(

From 7752696387e1c35b138c2644f6daf1eb9355bf2b Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 31 May 2022 17:45:46 +0200
Subject: [PATCH 52/71] pos_to_uv -> wpos_to_uv

---
 assets/voxygen/shaders/include/lod.glsl |  3 ++-
 assets/voxygen/shaders/include/sky.glsl | 15 ++++++++-------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index 2235ced5d6..af1a4212b2 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -118,8 +118,9 @@ vec2 textureBicubic16(texture2D tex, sampler sampl, vec2 texCoords) {
     , sy);
 }
 
+// Gets the altitude at a position relative to focus_off.
 float alt_at(vec2 pos) {
-    vec4 alt_sample = textureLod/*textureBicubic16*/(sampler2D(t_alt, s_alt), pos_to_uv(t_alt, s_alt, pos), 0);
+    vec4 alt_sample = textureLod/*textureBicubic16*/(sampler2D(t_alt, s_alt), wpos_to_uv(t_alt, s_alt, focus_off.xy + pos), 0);
     return (/*round*/((alt_sample.r / 256.0 + alt_sample.g) * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z);
     //+ (texture(t_noise, pos * 0.002).x - 0.5) * 64.0;
 
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index eb47eb2a6f..1fc1356439 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -101,10 +101,11 @@ float cloud_scale = view_distance.z / 150.0;
 layout(set = 0, binding = 5) uniform texture2D t_alt;
 layout(set = 0, binding = 6) uniform sampler s_alt;
 
-vec2 pos_to_uv(texture2D tex, sampler s, vec2 pos) {
+// Transforms coordinate in the range 0..WORLD_SIZE to 0..1
+vec2 wpos_to_uv(texture2D tex, sampler s, vec2 wpos) {
     // Want: (pixel + 0.5) / W
     vec2 texSize = textureSize(sampler2D(tex, s), 0);
-    vec2 uv_pos = (focus_off.xy + pos + 16) / (32.0 * texSize);
+    vec2 uv_pos = (wpos + 16) / (32.0 * texSize);
     return vec2(uv_pos.x, /*1.0 - */uv_pos.y);
 }
 
@@ -113,15 +114,15 @@ layout(set = 0, binding = 12) uniform texture2D t_weather;
 layout(set = 0, binding = 13) uniform sampler s_weather;
 
 vec4 sample_weather(vec2 wpos) {
-    return textureLod(sampler2D(t_weather, s_weather), pos_to_uv(t_alt, s_alt, wpos - focus_off.xy), 0);
+    return textureLod(sampler2D(t_weather, s_weather), wpos_to_uv(t_alt, s_alt, wpos), 0);
 }
 
-float cloud_tendency_at(vec2 pos) {
-    return sample_weather(pos).r;
+float cloud_tendency_at(vec2 wpos) {
+    return sample_weather(wpos).r;
 }
 
-float rain_density_at(vec2 pos) {
-    return sample_weather(pos).g;
+float rain_density_at(vec2 wpos) {
+    return sample_weather(wpos).g;
 }
 
 float cloud_shadow(vec3 pos, vec3 light_dir) {

From 9f26afb4a50f824e6361887a85daeb6707de1da5 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Tue, 31 May 2022 18:03:39 +0200
Subject: [PATCH 53/71] refactor rain_dir to rain_vel

---
 common/src/weather.rs        |  9 ++++-----
 voxygen/src/scene/mod.rs     | 12 ++++++------
 voxygen/src/scene/terrain.rs |  2 +-
 3 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/common/src/weather.rs b/common/src/weather.rs
index 235ea06702..be39dc73a3 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -5,8 +5,6 @@ use vek::{Lerp, Vec2, Vec3};
 
 use crate::{grid::Grid, terrain::TerrainChunkSize, vol::RectVolSize};
 
-pub const FALL_RATE: f32 = 20.0;
-
 /// Weather::default is Clear, 0 degrees C and no wind
 #[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
 pub struct Weather {
@@ -42,9 +40,10 @@ impl Weather {
         }
     }
 
-    // Get the rain direction for this weather
-    pub fn rain_dir(&self) -> Vec3<f32> {
-        (-Vec3::unit_z() + self.wind / (2.0 * FALL_RATE)).normalized()
+    // Get the rain velocity for this weather
+    pub fn rain_vel(&self) -> Vec3<f32> {
+        const FALL_RATE: f32 = 20.0;
+        Vec3::new(self.wind.x, self.wind.y, -FALL_RATE)
     }
 }
 
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 7240d0c9f7..01fd3e66aa 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1005,14 +1005,14 @@ impl Scene {
             .max_weather_near(focus_off.xy() + cam_pos.xy());
         if weather.rain > 0.0 {
             let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
-            let rain_dir = weather.rain_dir();
-            let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_dir, up);
-            let rain_vel = rain_dir * common::weather::FALL_RATE - player_vel;
-            self.integrated_rain_vel += rain_vel.magnitude() * dt;
-            let rel_rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
+            let rain_vel = weather.rain_vel();
+            let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
+            let relative_rain_vel = rain_vel - player_vel;
+            self.integrated_rain_vel += relative_rain_vel.magnitude() * dt;
+            let rel_rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), relative_rain_vel);
 
             let (shadow_mat, texture_mat) =
-                directed_mats(rain_view_mat, rain_dir.into(), &visible_occlusion_volume);
+                directed_mats(rain_view_mat, rain_vel.into(), &visible_occlusion_volume);
 
             let rain_occlusion_locals = RainOcclusionLocals::new(
                 shadow_mat,
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index c663799531..5d61660dee 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -1415,7 +1415,7 @@ impl<V: RectRasterableVol> Terrain<V> {
                 max: visible_bounding_box.max.as_::<f64>(),
             };
             let weather = scene_data.state.weather_at(focus_pos.xy());
-            let ray_direction = math::Vec3::<f32>::from(weather.rain_dir());
+            let ray_direction = math::Vec3::<f32>::from(weather.rain_vel().normalized());
 
             // NOTE: We use proj_mat_treeculler here because
             // calc_focused_light_volume_points makes the assumption that the

From cb969e5b870b3835c1eb191c8d1b8e2ed20e2cba Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 1 Jun 2022 15:04:02 +0200
Subject: [PATCH 54/71] Move atan2 to lod.glsl

---
 assets/voxygen/shaders/include/cloud/regular.glsl | 5 -----
 assets/voxygen/shaders/include/lod.glsl           | 5 +++++
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 01118f853f..9bec5e5ca9 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -181,11 +181,6 @@ vec4 cloud_at(vec3 pos, float dist, out vec3 emission, out float not_underground
     return vec4(sun_access, moon_access, vapor_density, air);
 }
 
-float atan2(in float y, in float x) {
-    bool s = (abs(x) > abs(y));
-    return mix(PI/2.0 - atan(x,y), atan(y,x), s);
-}
-
 #if (CLOUD_MODE == CLOUD_MODE_ULTRA)
     const uint QUALITY = 200u;
 #elif (CLOUD_MODE == CLOUD_MODE_HIGH)
diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index af1a4212b2..f5c408b0ef 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -28,6 +28,11 @@ vec4 cubic(float v) {
     return vec4(x, y, z, w) * (1.0/6.0);
 }
 
+float atan2(in float y, in float x) {
+    bool s = (abs(x) > abs(y));
+    return mix(PI/2.0 - atan(x,y), atan(y,x), s);
+}
+
 // NOTE: We assume the sampled coordinates are already in "texture pixels".
 vec4 textureBicubic(texture2D tex, sampler sampl, vec2 texCoords) {
     // TODO: remove all textureSize calls and replace with constants

From 27ec6d7469d7b1e4c490cd310c6d513ba6318f07 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Thu, 2 Jun 2022 22:51:17 +0200
Subject: [PATCH 55/71] add weather_zone command

---
 common/src/cmd.rs         | 18 +++++++++
 server/src/cmd.rs         | 73 ++++++++++++++++++++++++++++++++++-
 server/src/weather/mod.rs | 10 +++--
 server/src/weather/sim.rs | 80 +++++++++++++++++++++++++++++++--------
 4 files changed, 160 insertions(+), 21 deletions(-)

diff --git a/common/src/cmd.rs b/common/src/cmd.rs
index 479eae7585..3d13aad316 100644
--- a/common/src/cmd.rs
+++ b/common/src/cmd.rs
@@ -125,6 +125,13 @@ lazy_static! {
     .map(|s| s.to_string())
     .collect();
 
+    static ref WEATHERS: Vec<String> = vec![
+        "clear", "cloudy", "rain", "wind", "storm"
+    ]
+    .iter()
+    .map(|s| s.to_string())
+    .collect();
+
     pub static ref BUFF_PARSER: HashMap<String, BuffKind> = {
         let string_from_buff = |kind| match kind {
             BuffKind::Burning => "burning",
@@ -297,6 +304,7 @@ pub enum ServerChatCommand {
     Location,
     CreateLocation,
     DeleteLocation,
+    WeatherZone,
 }
 
 impl ServerChatCommand {
@@ -686,6 +694,15 @@ impl ServerChatCommand {
                 "Delete a location",
                 Some(Moderator),
             ),
+            ServerChatCommand::WeatherZone => cmd(
+                vec![
+                    Enum("weather kind", WEATHERS.clone(), Required),
+                    Float("radius", 500.0, Optional),
+                    Float("time", 300.0, Optional),
+                ],
+                "Create a weather zone",
+                Some(Admin),
+            ),
         }
     }
 
@@ -763,6 +780,7 @@ impl ServerChatCommand {
             ServerChatCommand::Location => "location",
             ServerChatCommand::CreateLocation => "create_location",
             ServerChatCommand::DeleteLocation => "delete_location",
+            ServerChatCommand::WeatherZone => "weather_zone",
         }
     }
 
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
index 677aec2b49..5795d6727f 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -10,6 +10,7 @@ use crate::{
         Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
     },
     sys::terrain::NpcData,
+    weather::WeatherSim,
     wiring,
     wiring::OutputFormula,
     Server, Settings, SpawnPoint, StateExt,
@@ -44,7 +45,7 @@ use common::{
     terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
     uid::{Uid, UidAllocator},
     vol::{ReadVol, RectVolSize},
-    Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
+    weather, Damage, DamageKind, DamageSource, Explosion, LoadoutBuilder, RadiusEffect,
 };
 use common_net::{
     msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral},
@@ -190,6 +191,7 @@ fn do_command(
         ServerChatCommand::Location => handle_location,
         ServerChatCommand::CreateLocation => handle_create_location,
         ServerChatCommand::DeleteLocation => handle_delete_location,
+        ServerChatCommand::WeatherZone => handle_weather_zone,
     };
 
     handler(server, client, target, args, cmd)
@@ -3595,3 +3597,72 @@ fn handle_delete_location(
         Err(action.help_string())
     }
 }
+
+fn handle_weather_zone(
+    server: &mut Server,
+    client: EcsEntity,
+    _target: EcsEntity,
+    args: Vec<String>,
+    action: &ServerChatCommand,
+) -> CmdResult<()> {
+    if let (Some(name), radius, time) = parse_cmd_args!(args, String, f32, f32) {
+        let radius = radius.map(|r| r / weather::CELL_SIZE as f32).unwrap_or(1.0);
+        let time = time.unwrap_or(100.0);
+
+        let mut add_zone = |weather: weather::Weather| {
+            if let Ok(pos) = position(server, client, "player") {
+                let pos = pos.0.xy() / weather::CELL_SIZE as f32;
+                server
+                    .state
+                    .ecs_mut()
+                    .write_resource::<WeatherSim>()
+                    .add_zone(weather, pos, radius, time);
+            }
+        };
+        match name.as_str() {
+            "clear" => {
+                add_zone(weather::Weather {
+                    cloud: 0.0,
+                    rain: 0.0,
+                    wind: Vec2::zero(),
+                });
+                Ok(())
+            },
+            "cloudy" => {
+                add_zone(weather::Weather {
+                    cloud: 0.4,
+                    rain: 0.0,
+                    wind: Vec2::zero(),
+                });
+                Ok(())
+            },
+            "rain" => {
+                add_zone(weather::Weather {
+                    cloud: 0.1,
+                    rain: 0.15,
+                    wind: Vec2::new(1.0, -1.0),
+                });
+                Ok(())
+            },
+            "wind" => {
+                add_zone(weather::Weather {
+                    cloud: 0.0,
+                    rain: 0.0,
+                    wind: Vec2::new(10.0, 10.0),
+                });
+                Ok(())
+            },
+            "storm" => {
+                add_zone(weather::Weather {
+                    cloud: 0.3,
+                    rain: 0.3,
+                    wind: Vec2::new(15.0,20.0),
+                });
+                Ok(())
+            },
+            _ => Err("Valid values are 'clear', 'rain', 'wind', 'storm'".to_string()),
+        }
+    } else {
+        Err(action.help_string())
+    }
+}
diff --git a/server/src/weather/mod.rs b/server/src/weather/mod.rs
index f24d23660e..46ad28540c 100644
--- a/server/src/weather/mod.rs
+++ b/server/src/weather/mod.rs
@@ -10,6 +10,11 @@ mod sim;
 mod sync;
 mod tick;
 
+pub use sim::WeatherSim;
+
+/// How often the weather is updated, in seconds
+const WEATHER_DT: f32 = 5.0;
+
 pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
     dispatch::<tick::Sys>(dispatch_builder, &[]);
     dispatch::<sync::Sys>(dispatch_builder, &[&tick::Sys::sys_name()]);
@@ -17,12 +22,9 @@ pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
 
 pub fn init(state: &mut State, world: &world::World) {
     let weather_size = world.sim().get_size() / CHUNKS_PER_CELL;
-    let sim = sim::WeatherSim::new(weather_size, world);
+    let sim = WeatherSim::new(weather_size, world);
     state.ecs_mut().insert(sim);
 
-    /// How often the weather is updated, in seconds
-    const WEATHER_DT: f32 = 5.0;
-
     // NOTE: If weather computations get too heavy, this should not block the main
     // thread.
     state
diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index e505c98ef3..bf82ce006f 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -1,19 +1,58 @@
 use common::{
+    grid::Grid,
     resources::TimeOfDay,
-    weather::{WeatherGrid, CELL_SIZE},
+    weather::{Weather, WeatherGrid, CELL_SIZE},
 };
 use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
 use world::World;
 
+use crate::weather::WEATHER_DT;
+
 fn cell_to_wpos(p: Vec2<i32>) -> Vec2<i32> { p * CELL_SIZE as i32 }
 
+#[derive(Clone)]
+struct WeatherZone {
+    weather: Weather,
+    /// Time, in seconds this zone lives.
+    time_to_live: f32,
+}
+
 pub struct WeatherSim {
     size: Vec2<u32>,
+    zones: Grid<Option<WeatherZone>>,
 }
 
 impl WeatherSim {
-    pub fn new(size: Vec2<u32>, _world: &World) -> Self { Self { size } }
+    pub fn new(size: Vec2<u32>, _world: &World) -> Self {
+        Self {
+            size,
+            zones: Grid::new(size.as_(), None),
+        }
+    }
+
+    /// Adds a weather zone as a circle at a position, with a given radius. Both
+    /// of which should be in weather cell units
+    pub fn add_zone(&mut self, weather: Weather, pos: Vec2<f32>, radius: f32, time: f32) {
+        let min: Vec2<i32> = (pos - radius).as_::<i32>().map(|e| e.max(0));
+        let max: Vec2<i32> = (pos + radius)
+            .ceil()
+            .as_::<i32>()
+            .map2(self.size.as_::<i32>(), |a, b| a.min(b));
+        for y in min.y..max.y {
+            for x in min.x..max.x {
+                let ipos = Vec2::new(x, y);
+                let p = ipos.as_::<f32>();
+
+                if p.distance_squared(pos) < radius.powi(2) {
+                    self.zones[ipos] = Some(WeatherZone {
+                        weather,
+                        time_to_live: time,
+                    });
+                }
+            }
+        }
+    }
 
     // Time step is cell size / maximum wind speed
     pub fn tick(&mut self, time_of_day: &TimeOfDay, out: &mut WeatherGrid) {
@@ -30,24 +69,33 @@ impl WeatherSim {
         let rain_nz = SuperSimplex::new();
 
         for (point, cell) in out.iter_mut() {
-            let wpos = cell_to_wpos(point);
+            if let Some(zone) = &mut self.zones[point] {
+                *cell = zone.weather;
+                zone.time_to_live -= WEATHER_DT;
+                if zone.time_to_live <= 0.0 {
+                    self.zones[point] = None;
+                }
+            } else {
+                let wpos = cell_to_wpos(point);
 
-            let pos = wpos.as_::<f64>() + time as f64 * 0.1;
+                let pos = wpos.as_::<f64>() + time as f64 * 0.1;
 
-            let space_scale = 7_500.0;
-            let time_scale = 100_000.0;
-            let spos = (pos / space_scale).with_z(time as f64 / time_scale);
+                let space_scale = 7_500.0;
+                let time_scale = 100_000.0;
+                let spos = (pos / space_scale).with_z(time as f64 / time_scale);
 
-            let pressure = (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
+                let pressure =
+                    (base_nz.get(spos.into_array()) * 0.5 + 1.0).clamped(0.0, 1.0) as f32;
 
-            const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
-            cell.cloud = (1.0 - pressure) * 0.5;
-            cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
-            cell.wind = Vec2::new(
-                rain_nz.get(spos.into_array()).powi(3) as f32,
-                rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
-            ) * 200.0
-                * (1.0 - pressure);
+                const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
+                cell.cloud = (1.0 - pressure) * 0.5;
+                cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
+                cell.wind = Vec2::new(
+                    rain_nz.get(spos.into_array()).powi(3) as f32,
+                    rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
+                ) * 200.0
+                    * (1.0 - pressure);
+            }
         }
     }
 

From 72a0f5678897211b1ab3c70fe813bf716bfbd953 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Fri, 3 Jun 2022 14:42:04 +0200
Subject: [PATCH 56/71] better rainbows

---
 assets/voxygen/shaders/include/cloud/regular.glsl | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/assets/voxygen/shaders/include/cloud/regular.glsl b/assets/voxygen/shaders/include/cloud/regular.glsl
index 9bec5e5ca9..ddb4a2f552 100644
--- a/assets/voxygen/shaders/include/cloud/regular.glsl
+++ b/assets/voxygen/shaders/include/cloud/regular.glsl
@@ -247,6 +247,7 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
             float rainbow_t = (0.7 - dot(sun_dir.xyz, dir)) * 8 / 0.05;
             int rainbow_c = int(floor(rainbow_t));
             rainbow_t = fract(rainbow_t);
+            rainbow_t = rainbow_t * rainbow_t;
         #endif
         #endif
 
@@ -286,7 +287,6 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
             #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
             #ifdef EXPERIMENTAL_RAINBOWS
                 if (rainbow_c >= 0 && rainbow_c < 8) {
-                    float rain = rain_density_at(pos.xy);
                     vec3 colors[9] = {
                         surf_color,
                         vec3(0.9, 0.5, 0.9),
@@ -298,10 +298,16 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
                         vec3(1.0, 0.0, 0.0),
                         surf_color,
                     };
+                    float h = max(0.0, min(pos.z, 900.0 - pos.z) / 450.0);
+                    float rain = rain_density_at(pos.xy) * pow(h, 0.1);
+                    
+                    float sun = sun_access * get_sun_brightness();
+                    float energy = pow(rain * sun * min(cdist / 500.0, 1.0), 2.0) * 0.4;
+
                     surf_color = mix(
                         surf_color,
                         mix(colors[rainbow_c], colors[rainbow_c + 1], rainbow_t),
-                        rain * sun_access * sun_access * get_sun_brightness() * pow(min(cdist / 500.0, 1.0), 2.0)
+                        energy
                     );
                 }
             #endif

From ac82689f83ceeabc12e29e366beba6259d42f16c Mon Sep 17 00:00:00 2001
From: DaforLynx <darwinsez@gmail.com>
Date: Fri, 3 Jun 2022 18:25:17 -0700
Subject: [PATCH 57/71] Clean up audio code, fix egui bug

---
 assets/voxygen/audio/ambience/leaves.ogg      |   4 +-
 assets/voxygen/audio/ambient.ron              |   2 +-
 assets/voxygen/shaders/clouds-frag.glsl       |  69 +++---
 assets/voxygen/shaders/include/lod.glsl       |   2 +-
 .../shaders/include/rain_occlusion.glsl       |   6 +-
 assets/voxygen/shaders/include/sky.glsl       |   6 +-
 .../shaders/rain-occlusion-directed-vert.glsl |   6 +-
 voxygen/Cargo.toml                            |   4 +-
 voxygen/src/audio/ambient.rs                  | 216 +++++++-----------
 voxygen/src/audio/channel.rs                  |  28 +--
 voxygen/src/audio/mod.rs                      | 166 +++++++-------
 voxygen/src/audio/music.rs                    |   5 +
 voxygen/src/audio/sfx/mod.rs                  |   5 +-
 voxygen/src/hud/mod.rs                        |   8 +-
 voxygen/src/render/pipelines/lod_terrain.rs   |   8 +-
 voxygen/src/render/pipelines/mod.rs           |   3 +-
 .../src/render/pipelines/rain_occlusion.rs    |  22 +-
 voxygen/src/render/renderer.rs                |  16 +-
 voxygen/src/render/renderer/drawer.rs         |  12 +-
 .../src/render/renderer/rain_occlusion_map.rs |   6 +-
 voxygen/src/scene/mod.rs                      |  34 ++-
 voxygen/src/scene/terrain.rs                  |  12 +-
 voxygen/src/session/mod.rs                    |   7 +-
 23 files changed, 295 insertions(+), 352 deletions(-)

diff --git a/assets/voxygen/audio/ambience/leaves.ogg b/assets/voxygen/audio/ambience/leaves.ogg
index a5ac1765f6..22276e12f7 100644
--- a/assets/voxygen/audio/ambience/leaves.ogg
+++ b/assets/voxygen/audio/ambience/leaves.ogg
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:63a218856961a57daf7b81ee25a6440cce9d76121dc6c48861e96211741c7d62
-size 454547
+oid sha256:30a6d9bc0e63f7216eaaddef5d9ada465f241b2f1206b522dd388a552dba5708
+size 461250
diff --git a/assets/voxygen/audio/ambient.ron b/assets/voxygen/audio/ambient.ron
index 4d02185ce8..a1fb6a9d48 100644
--- a/assets/voxygen/audio/ambient.ron
+++ b/assets/voxygen/audio/ambient.ron
@@ -17,7 +17,7 @@
         ),
         (
             path:"voxygen.audio.ambience.leaves",
-            length: 27.0,
+            length: 26.0,
             tag: Leaves,
         ),
     ]
diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 5c5e842537..75998d2cd4 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -108,44 +108,47 @@ void main() {
     vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
     vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-    float rain_dist = 50.0;
-    for (int i = 0; i < 4; i ++) {
-        float old_rain_dist = rain_dist;
-        rain_dist *= 0.3;
+    float rain = rain_density_at(cam_wpos.xy);
+    if (rain > 0.0) {
+        float rain_dist = 50.0;
+        for (int i = 0; i < 4; i ++) {
+            float old_rain_dist = rain_dist;
+            rain_dist *= 0.3;
 
-        vec2 drop_density = vec2(30, 1);
+            vec2 drop_density = vec2(30, 1);
 
-        vec2 rain_pos = (view_pos * rain_dist);
-        rain_pos.y += integrated_rain_vel;
+            vec2 rain_pos = (view_pos * rain_dist);
+            rain_pos.y += integrated_rain_vel;
 
-        vec2 cell = floor(rain_pos * drop_density) / drop_density;
+            vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
-        float drop_depth = mix(
-            old_rain_dist,
-            rain_dist,
-            fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
-        );
-        vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
-        float dist_to_rain = length(rpos);
-        if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
-            continue;
+            float drop_depth = mix(
+                old_rain_dist,
+                rain_dist,
+                fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
+            );
+            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
+            float dist_to_rain = length(rpos);
+            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+                continue;
+            }
+
+            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+                break;
+            }
+            float rain_density = rain * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+
+            if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
+                continue;
+            }
+            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+            vec2 drop_size = vec2(0.0008, 0.03);
+            float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
+            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
+            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
         }
-
-        if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-            break;
-        }
-        float rain_density = rain_density_at(cam_wpos.xy + rpos.xy) * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
-
-        if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
-            continue;
-        }
-        vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
-
-        vec2 drop_size = vec2(0.0008, 0.03);
-        float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
-        float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
-        float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
-        color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
     }
 
     tgt_color = vec4(color.rgb, 1);
diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index f5c408b0ef..e56e2f5010 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -125,7 +125,7 @@ vec2 textureBicubic16(texture2D tex, sampler sampl, vec2 texCoords) {
 
 // Gets the altitude at a position relative to focus_off.
 float alt_at(vec2 pos) {
-    vec4 alt_sample = textureLod/*textureBicubic16*/(sampler2D(t_alt, s_alt), wpos_to_uv(t_alt, s_alt, focus_off.xy + pos), 0);
+    vec4 alt_sample = textureLod/*textureBicubic16*/(sampler2D(t_alt, s_alt), wpos_to_uv(focus_off.xy + pos), 0);
     return (/*round*/((alt_sample.r / 256.0 + alt_sample.g) * (/*1300.0*//*1278.7266845703125*/view_distance.w)) + /*140.0*/view_distance.z - focus_off.z);
     //+ (texture(t_noise, pos * 0.002).x - 0.5) * 64.0;
 
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index 8768489481..78d5c7d8b7 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -10,8 +10,8 @@ uniform samplerShadow s_directed_occlusion_maps;
 
 layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
-    mat4 occlusionMatrices;
-    mat4 occlusion_texture_mat;
+    mat4 rain_occlusion_matrices;
+    mat4 rain_occlusion_texture_mat;
     mat4 rel_rain_dir_mat;
     float integrated_rain_vel;
     vec3 occlusion_dummy; // Fix alignment.
@@ -21,7 +21,7 @@ float rain_occlusion_at(in vec3 fragPos)
 {
     float bias = -0.2;
 
-    vec4 rain_pos = occlusion_texture_mat * vec4(fragPos, 1.0) - vec4(0, 0, bias, 0);
+    vec4 rain_pos = rain_occlusion_texture_mat * vec4(fragPos, 1.0) - vec4(0, 0, bias, 0);
 
     float visibility = textureProj(sampler2DShadow(t_directed_occlusion_maps, s_directed_occlusion_maps), rain_pos);
 
diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl
index 1fc1356439..167ae6b407 100644
--- a/assets/voxygen/shaders/include/sky.glsl
+++ b/assets/voxygen/shaders/include/sky.glsl
@@ -102,9 +102,9 @@ layout(set = 0, binding = 5) uniform texture2D t_alt;
 layout(set = 0, binding = 6) uniform sampler s_alt;
 
 // Transforms coordinate in the range 0..WORLD_SIZE to 0..1
-vec2 wpos_to_uv(texture2D tex, sampler s, vec2 wpos) {
+vec2 wpos_to_uv(vec2 wpos) {
     // Want: (pixel + 0.5) / W
-    vec2 texSize = textureSize(sampler2D(tex, s), 0);
+    vec2 texSize = textureSize(sampler2D(t_alt, s_alt), 0);
     vec2 uv_pos = (wpos + 16) / (32.0 * texSize);
     return vec2(uv_pos.x, /*1.0 - */uv_pos.y);
 }
@@ -114,7 +114,7 @@ layout(set = 0, binding = 12) uniform texture2D t_weather;
 layout(set = 0, binding = 13) uniform sampler s_weather;
 
 vec4 sample_weather(vec2 wpos) {
-    return textureLod(sampler2D(t_weather, s_weather), wpos_to_uv(t_alt, s_alt, wpos), 0);
+    return textureLod(sampler2D(t_weather, s_weather), wpos_to_uv(wpos), 0);
 }
 
 float cloud_tendency_at(vec2 wpos) {
diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
index 335ba92214..f0feb6891c 100644
--- a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
@@ -26,8 +26,8 @@
 
 layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
-    mat4 rainOcclusionMatrices;
-    mat4 texture_mat;
+    mat4 rain_occlusion_matrices;
+    mat4 rain_occlusion_texture_mat;
     mat4 rel_rain_dir_mat;
     float integrated_rain_vel;
     vec3 occlusion_dummy; // Fix alignment.
@@ -60,5 +60,5 @@ void main() {
     vec3 f_chunk_pos = vec3(v_pos_norm & 0x3Fu, (v_pos_norm >> 6) & 0x3Fu, float((v_pos_norm >> 12) & 0xFFFFu) - EXTRA_NEG_Z);
     vec3 f_pos = f_chunk_pos + (model_offs - focus_off.xyz);
     
-    gl_Position = rainOcclusionMatrices * vec4(f_pos, 1.0);
+    gl_Position = rain_occlusion_matrices * vec4(f_pos, 1.0);
 }
diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml
index b86d69f315..2d4ff19849 100644
--- a/voxygen/Cargo.toml
+++ b/voxygen/Cargo.toml
@@ -35,7 +35,9 @@ shaderc-from-source = ["shaderc/build-from-source"]
 
 # We don't ship egui with published release builds so a separate feature is required that excludes it.
 default-publish = ["singleplayer", "native-dialog", "plugins", "simd"]
-default = ["default-publish", "egui-ui", "hot-reloading", "shaderc-from-source"]
+# Temp for bug on current wgpu version that has access violation in vulkan when constructing egui pipeline
+default-no-egui = ["default-publish", "hot-reloading", "shaderc-from-source"]
+default = ["default-no-egui", "egui-ui"]
 
 [dependencies]
 client = {package = "veloren-client", path = "../client"}
diff --git a/voxygen/src/audio/ambient.rs b/voxygen/src/audio/ambient.rs
index 9e568cad71..8382a43be7 100644
--- a/voxygen/src/audio/ambient.rs
+++ b/voxygen/src/audio/ambient.rs
@@ -1,9 +1,6 @@
 //! Handles ambient non-positional sounds
 use crate::{
-    audio::{
-        channel::{AmbientChannel, AmbientChannelTag},
-        AudioFrontend,
-    },
+    audio::{channel::AmbientChannelTag, AudioFrontend},
     scene::Camera,
 };
 use client::Client;
@@ -44,97 +41,65 @@ impl AmbientMgr {
         client: &Client,
         camera: &Camera,
     ) {
+        // Checks if the ambience volume is set to zero or audio is disabled
+        // This prevents us from running all the following code unnecessarily
+        if !audio.ambience_enabled() {
+            return;
+        }
         let ambience_volume = audio.get_ambience_volume();
+        let ambience = self.ambience.read();
         // Iterate through each tag
         for tag in AmbientChannelTag::iter() {
             // If the conditions warrant creating a channel of that tag
-            if match tag {
-                AmbientChannelTag::Wind => {
-                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.0
-                },
-                AmbientChannelTag::Rain => {
-                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.1
-                },
-                AmbientChannelTag::Thunder => {
-                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.0
-                },
-                AmbientChannelTag::Leaves => {
-                    AmbientChannelTag::get_tag_volume(tag, client, camera) > 0.1
-                },
-            } && audio.get_ambient_channel(tag).is_none()
+            if AmbientChannelTag::get_tag_volume(tag, client, camera)
+                > match tag {
+                    AmbientChannelTag::Wind => 0.0,
+                    AmbientChannelTag::Rain => 0.1,
+                    AmbientChannelTag::Thunder => 0.0,
+                    AmbientChannelTag::Leaves => 0.1,
+                }
+                && audio.get_ambient_channel(tag).is_none()
             {
-                // Iterate through the supposed number of channels - one for each tag
-                for index in 0..AmbientChannelTag::iter().len() {
-                    // If index would exceed current number of channels, create a new one with
-                    // current tag
-                    if index >= audio.ambient_channels.len() {
-                        audio.new_ambient_channel(tag);
-                        break;
+                audio.new_ambient_channel(tag);
+            }
+            // If a channel exists run volume code
+            if let Some(channel_index) = audio.get_ambient_channel_index(tag) {
+                let channel = &mut audio.ambient_channels[channel_index];
+
+                // Maintain: get the correct multiplier of whatever the tag of the current
+                // channel is
+                let target_volume = get_target_volume(tag, state, client, camera);
+                // Get multiplier of the current channel
+                let initial_volume = channel.multiplier;
+
+                // Lerp multiplier of current channel
+                // TODO: Make this not framerate dependent
+                channel.multiplier = Lerp::lerp(initial_volume, target_volume, 0.02);
+
+                // Update with sfx volume
+                channel.set_volume(ambience_volume);
+
+                // Set the duration of the loop to whatever the current value is (0.0 by
+                // default)
+
+                // If the sound should loop at this point:
+                if channel.began_playing.elapsed().as_secs_f32() > channel.next_track_change {
+                    let track = ambience.tracks.iter().find(|track| track.tag == tag);
+                    // Set the channel's start point at this instant
+                    channel.began_playing = Instant::now();
+                    if let Some(track) = track {
+                        // Set loop duration to the one specified in the ron
+                        channel.next_track_change = track.length;
+                        // Play the file of the current tag at the current multiplier;
+                        let current_multiplier = channel.multiplier;
+                        audio.play_ambient(tag, &track.path, current_multiplier);
                     }
-                }
-                // If the conditions don't warrant the creation of a
-                // channel with that tag, but a channel with
-                // that tag remains nonetheless, run the volume code
-            } else if audio.get_ambient_channel(tag).is_some() {
-                for index in 0..AmbientChannelTag::iter().len() {
-                    // Update with sfx volume
-                    audio.ambient_channels[index].set_volume(ambience_volume);
-                    // If current channel's tag is not the current tag, move on to next channel
-                    if audio.ambient_channels[index].get_tag() == tag {
-                        // Maintain: get the correct multiplier of whatever the tag of the current
-                        // channel is
-                        let target_volume = AmbientChannel::maintain(tag, state, client, camera);
-                        // Get multiplier of the current channel
-                        let initial_volume = audio.ambient_channels[index].get_multiplier();
+                };
 
-                        // Lerp multiplier of current channel
-                        audio.ambient_channels[index].set_multiplier(Lerp::lerp(
-                            initial_volume,
-                            target_volume,
-                            0.02,
-                        ));
-
-                        // Set the duration of the loop to whatever the current value is (0.0 by
-                        // default)
-                        let next_track_change =
-                            audio.ambient_channels[index].get_next_track_change();
-
-                        // If the sound should loop at this point:
-                        if audio.ambient_channels[index]
-                            .get_began_playing()
-                            .elapsed()
-                            .as_secs_f32()
-                            > next_track_change
-                        {
-                            let ambience = self.ambience.read();
-                            let track = ambience.tracks.iter().find(|track| track.tag == tag);
-                            // Set the channel's start point at this instant
-                            audio.ambient_channels[index].set_began_playing(Instant::now());
-                            if let Some(track) = track {
-                                // Set loop duration to the one specified in the ron
-                                audio.ambient_channels[index].set_next_track_change(track.length);
-                                // Play the file of the current tag at the current multiplier
-                                let current_multiplier =
-                                    audio.ambient_channels[index].get_multiplier();
-                                audio.play_ambient(tag, &track.path, current_multiplier);
-                            }
-                        };
-
-                        // Remove channel if not playing
-                        if audio.ambient_channels[index].get_multiplier() == 0.0 {
-                            audio.ambient_channels[index].stop();
-                            audio.ambient_channels.remove(index);
-                        };
-                        // Move on to next tag
-                        break;
-                    } else {
-                        // Channel tag and current tag don't match, move on to next channel
-                        continue;
-                    }
-                }
-            } else {
-                // No need to run code at all, move on to the next tag
-                continue;
+                // Remove channel if not playing
+                if audio.ambient_channels[channel_index].multiplier <= 0.001 {
+                    audio.ambient_channels.remove(channel_index);
+                };
             }
         }
     }
@@ -195,10 +160,12 @@ impl AmbientChannelTag {
                 rain_intensity.min(0.9)
             },
             AmbientChannelTag::Thunder => {
-                if client.weather_at_player().rain * 500.0 < 0.7 {
+                let rain_intensity = client.weather_at_player().rain * 500.0;
+
+                if rain_intensity < 0.7 {
                     0.0
                 } else {
-                    client.weather_at_player().rain * 500.0
+                    rain_intensity
                 }
             },
             AmbientChannelTag::Leaves => {
@@ -229,51 +196,38 @@ impl AmbientChannelTag {
     }
 }
 
-impl AmbientChannel {
-    pub fn maintain(
-        tag: AmbientChannelTag,
-        state: &State,
-        client: &Client,
-        camera: &Camera,
-    ) -> f32 {
-        let focus_off = camera.get_focus_pos().map(f32::trunc);
-        let cam_pos = camera.dependents().cam_pos + focus_off;
+/// Checks various factors to determine the target volume to lerp to
+fn get_target_volume(
+    tag: AmbientChannelTag,
+    state: &State,
+    client: &Client,
+    camera: &Camera,
+) -> f32 {
+    let focus_off = camera.get_focus_pos().map(f32::trunc);
+    let cam_pos = camera.dependents().cam_pos + focus_off;
 
-        let mut target_volume: f32 = AmbientChannelTag::get_tag_volume(tag, client, camera);
+    let mut volume_multiplier: f32 = AmbientChannelTag::get_tag_volume(tag, client, camera);
 
-        target_volume = AmbientChannel::check_camera(state, client, cam_pos, target_volume);
-
-        target_volume
+    let terrain_alt = if let Some(chunk) = client.current_chunk() {
+        chunk.meta().alt()
+    } else {
+        0.0
+    };
+    // Checks if the camera is underwater to diminish ambient sounds
+    if state
+        .terrain()
+        .get((cam_pos).map(|e| e.floor() as i32))
+        .map(|b| b.is_liquid())
+        .unwrap_or(false)
+    {
+        volume_multiplier *= 0.1;
+    }
+    // Is the camera roughly under the terrain?
+    if cam_pos.z < terrain_alt - 20.0 {
+        volume_multiplier = 0.0;
     }
 
-    fn check_camera(
-        state: &State,
-        client: &Client,
-        cam_pos: Vec3<f32>,
-        initial_volume: f32,
-    ) -> f32 {
-        let mut volume_multiplier = initial_volume;
-        let terrain_alt = if let Some(chunk) = client.current_chunk() {
-            chunk.meta().alt()
-        } else {
-            0.0
-        };
-        // Checks if the camera is underwater to diminish ambient sounds
-        if state
-            .terrain()
-            .get((cam_pos).map(|e| e.floor() as i32))
-            .map(|b| b.is_liquid())
-            .unwrap_or(false)
-        {
-            volume_multiplier *= 0.1;
-        }
-        // Is the camera roughly under the terrain?
-        if cam_pos.z < terrain_alt - 20.0 {
-            volume_multiplier = 0.0;
-        }
-
-        volume_multiplier.clamped(0.0, 1.0)
-    }
+    volume_multiplier.clamped(0.0, 1.0)
 }
 
 pub fn load_ambience_items() -> AssetHandle<AmbientCollection> {
diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs
index 0f2b612f71..3d3a5642f1 100644
--- a/voxygen/src/audio/channel.rs
+++ b/voxygen/src/audio/channel.rs
@@ -11,7 +11,7 @@
 //! [`AudioSettings`](../../settings/struct.AudioSettings.html)
 //!
 //! When the AudioFrontend's
-//! [`play_sfx`](../struct.AudioFrontend.html#method.play_sfx)
+//! [`emit_sfx`](../struct.AudioFrontend.html#method.emit_sfx)
 //! methods is called, it attempts to retrieve an SfxChannel for playback. If
 //! the channel capacity has been reached and all channels are occupied, a
 //! warning is logged, and no sound is played.
@@ -171,10 +171,10 @@ pub enum AmbientChannelTag {
 /// which are always heard at the camera's position.
 pub struct AmbientChannel {
     tag: AmbientChannelTag,
-    multiplier: f32,
+    pub multiplier: f32,
     sink: Sink,
-    began_playing: Instant,
-    next_track_change: f32,
+    pub began_playing: Instant,
+    pub next_track_change: f32,
 }
 
 impl AmbientChannel {
@@ -215,27 +215,11 @@ impl AmbientChannel {
 
     pub fn set_volume(&mut self, volume: f32) { self.sink.set_volume(volume * self.multiplier); }
 
-    pub fn set_multiplier(&mut self, multiplier: f32) { self.multiplier = multiplier; }
-
-    pub fn get_volume(&mut self) -> f32 { self.sink.volume() }
-
-    pub fn get_multiplier(&mut self) -> f32 { self.multiplier }
+    // pub fn get_volume(&mut self) -> f32 { self.sink.volume() }
 
     pub fn get_tag(&self) -> AmbientChannelTag { self.tag }
 
-    pub fn set_tag(&mut self, tag: AmbientChannelTag) { self.tag = tag }
-
-    pub fn get_began_playing(&self) -> Instant { self.began_playing }
-
-    pub fn get_next_track_change(&self) -> f32 { self.next_track_change }
-
-    pub fn set_began_playing(&mut self, began_playing: Instant) {
-        self.began_playing = began_playing
-    }
-
-    pub fn set_next_track_change(&mut self, next_track_change: f32) {
-        self.next_track_change = next_track_change
-    }
+    // pub fn set_tag(&mut self, tag: AmbientChannelTag) { self.tag = tag }
 }
 
 /// An SfxChannel uses a positional audio sink, and is designed for short-lived
diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs
index dbf4e590b1..482b8d0d20 100644
--- a/voxygen/src/audio/mod.rs
+++ b/voxygen/src/audio/mod.rs
@@ -81,14 +81,11 @@ impl AudioFrontend {
         };
 
         let mut sfx_channels = Vec::with_capacity(num_sfx_channels);
-        if let Some(audio_stream) = &audio_stream {
-            sfx_channels.resize_with(num_sfx_channels, || SfxChannel::new(audio_stream));
-        };
-
         let mut ui_channels = Vec::with_capacity(num_ui_channels);
         if let Some(audio_stream) = &audio_stream {
-            ui_channels.resize_with(num_ui_channels, || UiChannel::new(audio_stream))
-        }
+            ui_channels.resize_with(num_ui_channels, || UiChannel::new(audio_stream));
+            sfx_channels.resize_with(num_sfx_channels, || SfxChannel::new(audio_stream));
+        };
 
         Self {
             // The following is for the disabled device switcher
@@ -219,39 +216,6 @@ impl AudioFrontend {
         self.music_channels.last_mut()
     }
 
-    /// Function to play sfx from external places. Useful for UI and
-    /// inventory events
-    pub fn emit_sfx_item(
-        &mut self,
-        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
-        vol: Option<f32>,
-    ) {
-        if let Some((event, item)) = trigger_item {
-            let sfx_file = match item.files.len() {
-                0 => {
-                    debug!("Sfx event {:?} is missing audio file.", event);
-                    "voxygen.audio.sfx.placeholder"
-                },
-                1 => item
-                    .files
-                    .last()
-                    .expect("Failed to determine sound file for this trigger item."),
-                _ => {
-                    // If more than one file is listed, choose one at random
-                    let rand_step = rand::random::<usize>() % item.files.len();
-                    &item.files[rand_step]
-                },
-            };
-
-            match self.play_ui_sfx(sfx_file, vol) {
-                Ok(_) => {},
-                Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e),
-            }
-        } else {
-            debug!("Missing sfx trigger config for external sfx event.",);
-        }
-    }
-
     /// Play an sfx file given the position, SfxEvent, and whether it is
     /// underwater or not
     pub fn emit_sfx(
@@ -262,6 +226,9 @@ impl AudioFrontend {
         underwater: bool,
     ) {
         if let Some((event, item)) = trigger_item {
+            // Find sound based on given trigger_item
+            // Randomizes if multiple sounds are found
+            // Errors if no sounds are found
             let sfx_file = match item.files.len() {
                 0 => {
                     debug!("Sfx event {:?} is missing audio file.", event);
@@ -277,10 +244,20 @@ impl AudioFrontend {
                     &item.files[rand_step]
                 },
             };
+            // Play sound in empty channel at given position
+            if self.audio_stream.is_some() {
+                let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
 
-            match self.play_sfx(sfx_file, position, volume, underwater) {
-                Ok(_) => {},
-                Err(e) => warn!("Failed to play sfx '{:?}'. {}", sfx_file, e),
+                let listener = self.listener.clone();
+                if let Some(channel) = self.get_sfx_channel() {
+                    channel.set_pos(position);
+                    channel.update(&listener);
+                    if underwater {
+                        channel.play_with_low_pass_filter(sound.convert_samples());
+                    } else {
+                        channel.play(sound);
+                    }
+                }
             }
         } else {
             debug!(
@@ -290,48 +267,47 @@ impl AudioFrontend {
         }
     }
 
-    /// Play (once) an sfx file by file path at the given position and volume.
-    /// If `underwater` is true, the sound is played with a low pass filter
-    pub fn play_sfx(
+    /// Plays a sfx using a non-spatial sink at the given volume; doesn't need a
+    /// position
+    /// Passing no volume will default to 1.0
+    pub fn emit_ui_sfx(
         &mut self,
-        sound: &str,
-        pos: Vec3<f32>,
-        vol: Option<f32>,
-        underwater: bool,
-    ) -> Result<(), rodio::decoder::DecoderError> {
-        if self.audio_stream.is_some() {
-            let sound = load_ogg(sound).amplify(vol.unwrap_or(1.0));
+        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
+        volume: Option<f32>,
+    ) {
+        // Find sound based on given trigger_item
+        // Randomizes if multiple sounds are found
+        // Errors if no sounds are found
+        if let Some((event, item)) = trigger_item {
+            let sfx_file = match item.files.len() {
+                0 => {
+                    debug!("Sfx event {:?} is missing audio file.", event);
+                    "voxygen.audio.sfx.placeholder"
+                },
+                1 => item
+                    .files
+                    .last()
+                    .expect("Failed to determine sound file for this trigger item."),
+                _ => {
+                    // If more than one file is listed, choose one at random
+                    let rand_step = rand::random::<usize>() % item.files.len();
+                    &item.files[rand_step]
+                },
+            };
+            // Play sound in empty channel
+            if self.audio_stream.is_some() {
+                let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
 
-            let listener = self.listener.clone();
-            if let Some(channel) = self.get_sfx_channel() {
-                channel.set_pos(pos);
-                channel.update(&listener);
-                if underwater {
-                    channel.play_with_low_pass_filter(sound.convert_samples());
-                } else {
+                if let Some(channel) = self.get_ui_channel() {
                     channel.play(sound);
                 }
             }
+        } else {
+            debug!("Missing sfx trigger config for external sfx event.",);
         }
-        Ok(())
     }
 
-    pub fn play_ui_sfx(
-        &mut self,
-        sound: &str,
-        vol: Option<f32>,
-    ) -> Result<(), rodio::decoder::DecoderError> {
-        if self.audio_stream.is_some() {
-            let sound = load_ogg(sound).amplify(vol.unwrap_or(1.0));
-
-            if let Some(channel) = self.get_ui_channel() {
-                channel.play(sound);
-            }
-        }
-        Ok(())
-    }
-
-    // Plays a file at a given volume in the channel with a given tag
+    /// Plays a file at a given volume in the channel with a given tag
     fn play_ambient(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -346,7 +322,7 @@ impl AudioFrontend {
         }
     }
 
-    // Adds a new ambient channel of the given tag at zero volume
+    /// Adds a new ambient channel of the given tag at zero volume
     fn new_ambient_channel(&mut self, channel_tag: AmbientChannelTag) {
         if let Some(audio_stream) = &self.audio_stream {
             let ambient_channel = AmbientChannel::new(audio_stream, channel_tag, 0.0);
@@ -355,6 +331,7 @@ impl AudioFrontend {
     }
 
     /// Retrieves the channel currently having the given tag
+    /// If no channel with the given tag is found, returns None
     fn get_ambient_channel(
         &mut self,
         channel_tag: AmbientChannelTag,
@@ -368,6 +345,19 @@ impl AudioFrontend {
         }
     }
 
+    /// Retrieves the index of the channel having the given tag in the array of
+    /// ambient channels This is used for times when borrowing becomes
+    /// difficult If no channel with the given tag is found, returns None
+    fn get_ambient_channel_index(&self, channel_tag: AmbientChannelTag) -> Option<usize> {
+        if self.audio_stream.is_some() {
+            self.ambient_channels
+                .iter()
+                .position(|channel| channel.get_tag() == channel_tag)
+        } else {
+            None
+        }
+    }
+
     // Unused code that may be useful in the future:
     // Sets the volume of the channel with the given tag to the given volume
     // fn set_ambient_volume(&mut self, channel_tag: AmbientChannelTag,
@@ -470,12 +460,19 @@ impl AudioFrontend {
     pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
         self.sfx_volume = sfx_volume;
 
-        self.update_sfx_volumes();
+        let sfx_volume = self.get_sfx_volume();
+        for channel in self.sfx_channels.iter_mut() {
+            channel.set_volume(sfx_volume);
+        }
+        for channel in self.ui_channels.iter_mut() {
+            channel.set_volume(sfx_volume);
+        }
     }
 
     pub fn set_ambience_volume(&mut self, ambience_volume: f32) {
         self.ambience_volume = ambience_volume;
 
+        let ambience_volume = self.get_ambience_volume();
         for channel in self.ambient_channels.iter_mut() {
             channel.set_volume(ambience_volume)
         }
@@ -490,6 +487,7 @@ impl AudioFrontend {
         }
     }
 
+    /// Updates master volume in all channels
     pub fn set_master_volume(&mut self, master_volume: f32) {
         self.master_volume = master_volume;
 
@@ -497,15 +495,17 @@ impl AudioFrontend {
         for channel in self.music_channels.iter_mut() {
             channel.set_volume(music_volume);
         }
-
-        self.update_sfx_volumes();
-    }
-
-    fn update_sfx_volumes(&mut self) {
         let sfx_volume = self.get_sfx_volume();
         for channel in self.sfx_channels.iter_mut() {
             channel.set_volume(sfx_volume);
         }
+        for channel in self.ui_channels.iter_mut() {
+            channel.set_volume(sfx_volume);
+        }
+        let ambience_volume = self.get_ambience_volume();
+        for channel in self.ambient_channels.iter_mut() {
+            channel.set_volume(ambience_volume)
+        }
     }
 
     pub fn stop_ambient_sounds(&mut self) {
diff --git a/voxygen/src/audio/music.rs b/voxygen/src/audio/music.rs
index e56e83ea07..262e9aa4b8 100644
--- a/voxygen/src/audio/music.rs
+++ b/voxygen/src/audio/music.rs
@@ -228,6 +228,11 @@ impl MusicMgr {
 
         use common::comp::{group::ENEMY, Group, Health, Pos};
         use specs::{Join, WorldExt};
+        // Checks if the music volume is set to zero or audio is disabled
+        // This prevents us from running all the following code unnecessarily
+        if !audio.music_enabled() {
+            return;
+        }
 
         let mut activity_state = MusicActivity::Explore;
 
diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs
index 86378f46bf..1a45c39927 100644
--- a/voxygen/src/audio/sfx/mod.rs
+++ b/voxygen/src/audio/sfx/mod.rs
@@ -412,14 +412,13 @@ impl SfxMgr {
         outcome: &Outcome,
         audio: &mut AudioFrontend,
         client: &Client,
-        state: &State,
         underwater: bool,
     ) {
         if !audio.sfx_enabled() {
             return;
         }
         let triggers = self.triggers.read();
-        let uids = state.ecs().read_storage::<Uid>();
+        let uids = client.state().ecs().read_storage::<Uid>();
 
         // TODO handle underwater
         match outcome {
@@ -495,7 +494,7 @@ impl SfxMgr {
                 if let Some(client_uid) = uids.get(client.entity()) {
                     if uid == client_uid {
                         let sfx_trigger_item = triggers.get_key_value(&SfxEvent::SkillPointGain);
-                        audio.emit_sfx_item(sfx_trigger_item, Some(0.4));
+                        audio.emit_ui_sfx(sfx_trigger_item, Some(0.4));
                     }
                 }
             },
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index 10a21abbab..4379a47a01 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -2386,11 +2386,11 @@ impl Hud {
                 .font_id(self.fonts.cyri.conrod_id)
                 .font_size(self.fonts.cyri.scale(14))
                 .set(self.ids.time, ui_widgets);
-
+            // Weather
             let weather = client.weather_at_player();
             Text::new(&format!(
-                "Weather({kind:.5}): {{cloud: {cloud:.5}, rain: {rain:.5}, wind: <{wind_x:.5}, \
-                 {wind_y:.2}>}}",
+                "Weather({kind}): {{cloud: {cloud:.2}, rain: {rain:.2}, wind: <{wind_x:.0}, \
+                 {wind_y:.0}>}}",
                 kind = weather.get_kind(),
                 cloud = weather.cloud,
                 rain = weather.rain,
@@ -2521,7 +2521,7 @@ impl Hud {
             // Set debug box dimensions, only timings height is dynamic
             // TODO: Make the background box size fully dynamic
 
-            let debug_bg_size = [320.0, 370.0 + timings_height];
+            let debug_bg_size = [320.0, 385.0 + timings_height];
 
             Rectangle::fill(debug_bg_size)
                 .rgba(0.0, 0.0, 0.0, global_state.settings.chat.chat_opacity)
diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs
index 7cabfd4ef7..7a4a757eaf 100644
--- a/voxygen/src/render/pipelines/lod_terrain.rs
+++ b/voxygen/src/render/pipelines/lod_terrain.rs
@@ -65,7 +65,7 @@ impl LodData {
         lod_base: &[u32],
         lod_alt: &[u32],
         lod_horizon: &[u32],
-        clouds_size: Vec2<u32>,
+        weather_size: Vec2<u32>,
         tgt_detail: u32,
         //border_color: gfx::texture::PackedColor,
     ) -> Self {
@@ -139,8 +139,8 @@ impl LodData {
             let texture_info = wgpu::TextureDescriptor {
                 label: None,
                 size: wgpu::Extent3d {
-                    width: clouds_size.x,
-                    height: clouds_size.y,
+                    width: weather_size.x,
+                    height: weather_size.y,
                     depth_or_array_layers: 1,
                 },
                 mip_level_count: 1,
@@ -177,7 +177,7 @@ impl LodData {
                 &texture_info,
                 &view_info,
                 &sampler_info,
-                vec![0; clouds_size.x as usize * clouds_size.y as usize * 4].as_slice(),
+                vec![0; weather_size.x as usize * weather_size.y as usize * 4].as_slice(),
             )
         };
         Self {
diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs
index a6ce260b80..3ddd7252e6 100644
--- a/voxygen/src/render/pipelines/mod.rs
+++ b/voxygen/src/render/pipelines/mod.rs
@@ -70,6 +70,8 @@ pub struct Globals {
     // To keep 16-byte-aligned.
     globals_dummy: [f32; 1],
 }
+/// Make sure Globals is 16-byte-aligned.
+const _: () = assert!(core::mem::size_of::<Globals>() % 16 == 0);
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Zeroable, Pod)]
@@ -110,7 +112,6 @@ impl Globals {
         cam_mode: CameraMode,
         sprite_render_distance: f32,
     ) -> Self {
-        // dbg!(core::mem::size_of::<Self>() % 16);
         Self {
             view_mat: view_mat.into_col_arrays(),
             proj_mat: proj_mat.into_col_arrays(),
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index 2ea313955d..754972f5b6 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -7,25 +7,29 @@ use vek::*;
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Zeroable, Pod, Default)]
 pub struct Locals {
-    shadow_matrices: [[f32; 4]; 4],
-    texture_mats: [[f32; 4]; 4],
+    rain_occlusion_matrices: [[f32; 4]; 4],
+    rain_occlusion_texture_mat: [[f32; 4]; 4],
+    /// A rotation of the direction of the rain, relative to the players
+    /// velocity.
     rel_rain_dir_mat: [[f32; 4]; 4],
+    /// A value to offset the rain, to make it move over time.
     integrated_rain_vel: f32,
     // To keep 16-byte-aligned.
     occlusion_dummy: [f32; 3],
 }
+/// Make sure Locals is 16-byte-aligned.
+const _: () = assert!(core::mem::size_of::<Locals>() % 16 == 0);
 
 impl Locals {
     pub fn new(
-        shadow_mat: Mat4<f32>,
-        texture_mat: Mat4<f32>,
+        rain_occlusion_matrices: Mat4<f32>,
+        rain_occlusion_texture_mat: Mat4<f32>,
         rel_rain_dir_mat: Mat4<f32>,
         integrated_rain_vel: f32,
     ) -> Self {
-        // dbg!(core::mem::size_of::<Self>() % 16);
         Self {
-            shadow_matrices: shadow_mat.into_col_arrays(),
-            texture_mats: texture_mat.into_col_arrays(),
+            rain_occlusion_matrices: rain_occlusion_matrices.into_col_arrays(),
+            rain_occlusion_texture_mat: rain_occlusion_texture_mat.into_col_arrays(),
             rel_rain_dir_mat: rel_rain_dir_mat.into_col_arrays(),
             integrated_rain_vel,
             occlusion_dummy: [0.0; 3],
@@ -91,7 +95,7 @@ impl RainOcclusionFigurePipeline {
 
         let render_pipeline_layout =
             device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                label: Some("Directed figure shadow pipeline layout"),
+                label: Some("Rain occlusion pipeline layout"),
                 push_constant_ranges: &[],
                 bind_group_layouts: &[&global_layout.globals, &figure_layout.locals],
             });
@@ -104,7 +108,7 @@ impl RainOcclusionFigurePipeline {
         };
 
         let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
-            label: Some("Directed shadow figure pipeline"),
+            label: Some("Rain occlusion figure pipeline"),
             layout: Some(&render_pipeline_layout),
             vertex: wgpu::VertexState {
                 module: vs_module,
diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs
index 34a48b5f9f..156cafb83d 100644
--- a/voxygen/src/render/renderer.rs
+++ b/voxygen/src/render/renderer.rs
@@ -49,10 +49,6 @@ pub type ColLightInfo = (Vec<[u8; 4]>, Vec2<u16>);
 const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000;
 const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000;
 
-// For rain occlusion we only need to render the closest chunks.
-/// How many chunks are maximally rendered for rain occlusion.
-pub const RAIN_OCCLUSION_CHUNKS: usize = 9;
-
 /// A type that stores all the layouts associated with this renderer that never
 /// change when the RenderMode is modified.
 struct ImmutableLayouts {
@@ -738,10 +734,8 @@ impl Renderer {
 
                         update_shadow_bind = true;
                     },
-                    shadow => {
-                        if let Err(err) = shadow {
-                            warn!("Could not create shadow map views: {:?}", err);
-                        }
+                    Err(err) => {
+                        warn!("Could not create shadow map views: {:?}", err);
                     },
                 }
             }
@@ -755,10 +749,8 @@ impl Renderer {
 
                         update_shadow_bind = true;
                     },
-                    rain => {
-                        if let Err(err) = rain {
-                            warn!("Could not create rain occlusion map view: {:?}", err);
-                        }
+                    Err(err) => {
+                        warn!("Could not create rain occlusion map view: {:?}", err);
                     },
                 }
             }
diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs
index 7fdedd371d..b1fb5e1e88 100644
--- a/voxygen/src/render/renderer/drawer.rs
+++ b/voxygen/src/render/renderer/drawer.rs
@@ -136,8 +136,8 @@ impl<'frame> Drawer<'frame> {
     /// Get the pipeline modes.
     pub fn pipeline_modes(&self) -> &super::super::PipelineModes { self.borrow.pipeline_modes }
 
-    /// Returns None if the shadow renderer is not enabled at some level or the
-    /// pipelines are not available yet
+    /// Returns None if the rain occlusion renderer is not enabled at some
+    /// level, the pipelines are not available yet or clouds are disabled.
     pub fn rain_occlusion_pass(&mut self) -> Option<RainOcclusionPassDrawer> {
         if !self.borrow.pipeline_modes.cloud.is_enabled() {
             return None;
@@ -663,7 +663,7 @@ impl<'pass> ShadowPassDrawer<'pass> {
     pub fn draw_figure_shadows(&mut self) -> FigureShadowDrawer<'_, 'pass> {
         let mut render_pass = self
             .render_pass
-            .scope("direcred_figure_shadows", self.borrow.device);
+            .scope("directed_figure_shadows", self.borrow.device);
 
         render_pass.set_pipeline(&self.shadow_renderer.figure_directed_pipeline.pipeline);
         set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
@@ -674,7 +674,7 @@ impl<'pass> ShadowPassDrawer<'pass> {
     pub fn draw_terrain_shadows(&mut self) -> TerrainShadowDrawer<'_, 'pass> {
         let mut render_pass = self
             .render_pass
-            .scope("direcred_terrain_shadows", self.borrow.device);
+            .scope("directed_terrain_shadows", self.borrow.device);
 
         render_pass.set_pipeline(&self.shadow_renderer.terrain_directed_pipeline.pipeline);
         set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
@@ -694,7 +694,7 @@ impl<'pass> RainOcclusionPassDrawer<'pass> {
     pub fn draw_figure_shadows(&mut self) -> FigureShadowDrawer<'_, 'pass> {
         let mut render_pass = self
             .render_pass
-            .scope("direcred_figure_rain_occlusion", self.borrow.device);
+            .scope("directed_figure_rain_occlusion", self.borrow.device);
 
         render_pass.set_pipeline(&self.rain_occlusion_renderer.figure_pipeline.pipeline);
         set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
@@ -705,7 +705,7 @@ impl<'pass> RainOcclusionPassDrawer<'pass> {
     pub fn draw_terrain_shadows(&mut self) -> TerrainShadowDrawer<'_, 'pass> {
         let mut render_pass = self
             .render_pass
-            .scope("direcred_terrain_rain_occlusion", self.borrow.device);
+            .scope("directed_terrain_rain_occlusion", self.borrow.device);
 
         render_pass.set_pipeline(&self.rain_occlusion_renderer.terrain_pipeline.pipeline);
         set_quad_index_buffer::<terrain::Vertex>(&mut render_pass, self.borrow);
diff --git a/voxygen/src/render/renderer/rain_occlusion_map.rs b/voxygen/src/render/renderer/rain_occlusion_map.rs
index 3fb37b2a5e..124deb4a72 100644
--- a/voxygen/src/render/renderer/rain_occlusion_map.rs
+++ b/voxygen/src/render/renderer/rain_occlusion_map.rs
@@ -1,4 +1,4 @@
-use crate::render::{pipelines::rain_occlusion, renderer::RAIN_OCCLUSION_CHUNKS};
+use crate::{render::pipelines::rain_occlusion, scene::terrain::RAIN_OCCLUSION_CHUNKS};
 
 use super::{
     super::{texture::Texture, RenderError, ShadowMapMode},
@@ -7,8 +7,8 @@ use super::{
 use common::{terrain::TerrainChunkSize, vol::RectVolSize};
 use vek::*;
 
-/// A type that holds shadow map data.  Since shadow mapping may not be
-/// supported on all platforms, we try to keep it separate.
+/// A type that holds rain occlusion map data.  Since rain occlusion mapping may
+/// not be supported on all platforms, we try to keep it separate.
 pub struct RainOcclusionMapRenderer {
     pub depth: Texture,
 
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 01fd3e66aa..9e870e28a6 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -420,7 +420,7 @@ impl Scene {
             .unwrap_or(false);
         self.particle_mgr.handle_outcome(outcome, scene_data);
         self.sfx_mgr
-            .handle_outcome(outcome, audio, scene_data.client, state, underwater);
+            .handle_outcome(outcome, audio, scene_data.client, underwater);
 
         match outcome {
             Outcome::Explosion {
@@ -1022,6 +1022,8 @@ impl Scene {
             );
 
             renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
+        } else {
+            self.integrated_rain_vel = 0.0;
         }
 
         let sun_dir = scene_data.get_sun_dir();
@@ -1106,25 +1108,19 @@ impl Scene {
         self.figure_mgr.clean(scene_data.tick);
 
         // Maintain audio
-        if audio.sfx_enabled() {
-            self.sfx_mgr.maintain(
-                audio,
-                scene_data.state,
-                scene_data.player_entity,
-                &self.camera,
-                &self.terrain,
-                client,
-            );
-        }
+        self.sfx_mgr.maintain(
+            audio,
+            scene_data.state,
+            scene_data.player_entity,
+            &self.camera,
+            &self.terrain,
+            client,
+        );
 
-        if audio.ambience_enabled() {
-            self.ambient_mgr
-                .maintain(audio, scene_data.state, client, &self.camera);
-        }
+        self.ambient_mgr
+            .maintain(audio, scene_data.state, client, &self.camera);
 
-        if audio.music_enabled() {
-            self.music_mgr.maintain(audio, scene_data.state, client);
-        }
+        self.music_mgr.maintain(audio, scene_data.state, client);
     }
 
     pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
@@ -1180,7 +1176,7 @@ impl Scene {
             prof_span!("rain occlusion");
             if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
                 self.terrain
-                    .render_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
+                    .render_rain_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
 
                 self.figure_mgr.render_shadows(
                     &mut occlusion_pass.draw_figure_shadows(),
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 5d61660dee..5c24f924ab 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -10,7 +10,6 @@ use crate::{
     },
     render::{
         pipelines::{self, ColLights},
-        renderer::RAIN_OCCLUSION_CHUNKS,
         ColLightInfo, FirstPassDrawer, FluidVertex, GlobalModel, Instances, LodData, Mesh, Model,
         RenderError, Renderer, SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts,
         TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
@@ -47,6 +46,10 @@ use vek::*;
 const SPRITE_SCALE: Vec3<f32> = Vec3::new(1.0 / 11.0, 1.0 / 11.0, 1.0 / 11.0);
 const SPRITE_LOD_LEVELS: usize = 5;
 
+// For rain occlusion we only need to render the closest chunks.
+/// How many chunks are maximally rendered for rain occlusion.
+pub const RAIN_OCCLUSION_CHUNKS: usize = 9;
+
 #[derive(Clone, Copy, Debug)]
 struct Visibility {
     in_range: bool,
@@ -1405,10 +1408,9 @@ impl<V: RectRasterableVol> Terrain<V> {
         // Check if there is rain near the camera
         let max_weather = scene_data.state.max_weather_near(focus_pos.xy());
         let visible_occlusion_volume = if max_weather.rain > 0.0 {
-            let occlusion_box = visible_bounding_box;
             let visible_bounding_box = math::Aabb::<f32> {
-                min: math::Vec3::from(occlusion_box.min - focus_off),
-                max: math::Vec3::from(occlusion_box.max - focus_off),
+                min: math::Vec3::from(visible_bounding_box.min - focus_off),
+                max: math::Vec3::from(visible_bounding_box.max - focus_off),
             };
             let visible_bounds_fine = math::Aabb {
                 min: visible_bounding_box.min.as_::<f64>(),
@@ -1490,7 +1492,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             .for_each(|(model, locals)| drawer.draw(model, locals));
     }
 
-    pub fn render_occlusion<'a>(
+    pub fn render_rain_occlusion<'a>(
         &'a self,
         drawer: &mut TerrainShadowDrawer<'_, 'a>,
         focus_pos: Vec3<f32>,
diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs
index 6a521c7d67..7a3ccf5add 100644
--- a/voxygen/src/session/mod.rs
+++ b/voxygen/src/session/mod.rs
@@ -269,9 +269,7 @@ impl SessionState {
                         | InventoryUpdateEvent::EntityCollectFailed { .. }
                         | InventoryUpdateEvent::BlockCollectFailed { .. }
                         | InventoryUpdateEvent::Craft => {
-                            global_state
-                                .audio
-                                .emit_sfx_item(sfx_trigger_item, Some(1.0));
+                            global_state.audio.emit_ui_sfx(sfx_trigger_item, Some(1.0));
                         },
                         _ => global_state.audio.emit_sfx(
                             sfx_trigger_item,
@@ -1207,6 +1205,9 @@ impl PlayState for SessionState {
                     },
                     HudEvent::Logout => {
                         self.client.borrow_mut().logout();
+                        // Stop all sounds
+                        // TODO: Abstract this behavior to all instances of PlayStateResult::Pop
+                        // somehow
                         global_state.audio.stop_ambient_sounds();
                         global_state.audio.stop_all_sfx();
                         return PlayStateResult::Pop;

From a7c724a46d0d1bfcf841c757aa44e621fdc3ab2b Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Wed, 8 Jun 2022 15:58:56 +0200
Subject: [PATCH 58/71] Limit figures drawn for rain occlusion

---
 assets/voxygen/shaders/clouds-frag.glsl       |   5 +
 common/state/src/state.rs                     |   3 +-
 .../src/render/pipelines/rain_occlusion.rs    |   2 +-
 voxygen/src/scene/figure/mod.rs               | 118 +++++++++++++-----
 voxygen/src/scene/lod.rs                      |   2 +
 voxygen/src/scene/mod.rs                      |  31 +++--
 voxygen/src/scene/terrain.rs                  |  26 ++--
 7 files changed, 136 insertions(+), 51 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 75998d2cd4..6ec61eb9ee 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -108,6 +108,11 @@ void main() {
     vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
     vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+    // Rain density is now only based on the cameras current position. 
+    // This could be affected by a setting where rain_density_at is instead
+    // called each iteration of the loop. With the current implementation
+    // of rain_dir this has issues with being in a place where it doesn't rain
+    // and seeing rain. 
     float rain = rain_density_at(cam_wpos.xy);
     if (rain > 0.0) {
         float rain_dist = 50.0;
diff --git a/common/state/src/state.rs b/common/state/src/state.rs
index 6f3e8de8a7..d088bc84d3 100644
--- a/common/state/src/state.rs
+++ b/common/state/src/state.rs
@@ -354,11 +354,12 @@ impl State {
     /// Get a mutable reference the current in-game weather grid.
     pub fn weather_grid_mut(&mut self) -> FetchMut<WeatherGrid> { self.ecs.write_resource() }
 
-    /// Get the current weather at a position.
+    /// Get the current weather at a position in worldspace.
     pub fn weather_at(&self, pos: Vec2<f32>) -> Weather {
         self.weather_grid().get_interpolated(pos)
     }
 
+    /// Get the max weather near a position in worldspace.
     pub fn max_weather_near(&self, pos: Vec2<f32>) -> Weather {
         self.weather_grid().get_max_near(pos)
     }
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index 754972f5b6..8e44ef0a53 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -95,7 +95,7 @@ impl RainOcclusionFigurePipeline {
 
         let render_pipeline_layout =
             device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
-                label: Some("Rain occlusion pipeline layout"),
+                label: Some("Rain occlusion figure pipeline layout"),
                 push_constant_ranges: &[],
                 bind_group_layouts: &[&global_layout.globals, &figure_layout.locals],
             });
diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs
index 42d2017765..f0b417949c 100644
--- a/voxygen/src/scene/figure/mod.rs
+++ b/voxygen/src/scene/figure/mod.rs
@@ -17,7 +17,7 @@ use crate::{
         camera::{Camera, CameraMode, Dependents},
         math,
         terrain::Terrain,
-        SceneData, TrailMgr,
+        SceneData, TrailMgr, RAIN_THRESHOLD,
     },
 };
 use anim::{
@@ -620,6 +620,7 @@ impl FigureMgr {
         scene_data: &SceneData,
         // Visible chunk data.
         visible_psr_bounds: math::Aabr<f32>,
+        visible_por_bounds: math::Aabr<f32>,
         camera: &Camera,
         terrain: Option<&Terrain>,
     ) -> anim::vek::Aabb<f32> {
@@ -637,25 +638,27 @@ impl FigureMgr {
         // of the image rendered from the light).  If the position projected
         // with the ray_mat matrix is valid, and shadows are otherwise enabled,
         // we mark can_shadow.
-        let can_shadow_sun = {
-            let ray_direction = scene_data.get_sun_dir();
-            let is_daylight = ray_direction.z < 0.0/*0.6*/;
-            // Are shadows enabled at all?
-            let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight;
+        // Rain occlusion is very similar to sun shadows, but using a different ray_mat,
+        // and only if it's raining.
+        let (can_shadow_sun, can_occlude_rain) = {
             let Dependents {
                 proj_mat: _,
                 view_mat: _,
                 cam_pos,
                 ..
             } = camera.dependents();
-            let cam_pos = math::Vec3::from(cam_pos);
-            let ray_direction = math::Vec3::from(ray_direction);
 
-            // Transform (semi) world space to light space.
-            let ray_mat: math::Mat4<f32> =
-                math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
+            let sun_dir = scene_data.get_sun_dir();
+            let is_daylight = sun_dir.z < 0.0/*0.6*/;
+            // Are shadows enabled at all?
+            let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight;
+
+            let weather = scene_data.state.weather_at(cam_pos.xy());
+
+            let cam_pos = math::Vec3::from(cam_pos);
+
             let focus_off = math::Vec3::from(camera.get_focus_pos().map(f32::trunc));
-            let ray_mat = ray_mat * math::Mat4::translation_3d(-focus_off);
+            let focus_off_mat = math::Mat4::translation_3d(-focus_off);
 
             let collides_with_aabr = |a: math::Aabr<f32>, b: math::Aabr<f32>| {
                 let min = math::Vec4::new(a.min.x, a.min.y, b.min.x, b.min.y);
@@ -665,22 +668,40 @@ impl FigureMgr {
                 #[cfg(not(feature = "simd"))]
                 return min.partial_cmple(&max).reduce_and();
             };
-            move |pos: (anim::vek::Vec3<f32>,), radius: f32| {
-                // Short circuit when there are no shadows to cast.
-                if !can_shadow_sun {
-                    return false;
+
+            let can_shadow = |ray_direction: Vec3<f32>,
+                              enabled: bool,
+                              visible_bounds: math::Aabr<f32>| {
+                let ray_direction = math::Vec3::from(ray_direction);
+                // Transform (semi) world space to light space.
+                let ray_mat: math::Mat4<f32> =
+                    math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
+                let ray_mat = ray_mat * focus_off_mat;
+                move |pos: (anim::vek::Vec3<f32>,), radius: f32| {
+                    // Short circuit when there are no shadows to cast.
+                    if !enabled {
+                        return false;
+                    }
+                    // First project center onto shadow map.
+                    let center = (ray_mat * math::Vec4::new(pos.0.x, pos.0.y, pos.0.z, 1.0)).xy();
+                    // Then, create an approximate bounding box (± radius).
+                    let figure_box = math::Aabr {
+                        min: center - radius,
+                        max: center + radius,
+                    };
+                    // Quick intersection test for membership in the PSC (potential shader caster)
+                    // list.
+                    collides_with_aabr(figure_box, visible_bounds)
                 }
-                // First project center onto shadow map.
-                let center = (ray_mat * math::Vec4::new(pos.0.x, pos.0.y, pos.0.z, 1.0)).xy();
-                // Then, create an approximate bounding box (± radius).
-                let figure_box = math::Aabr {
-                    min: center - radius,
-                    max: center + radius,
-                };
-                // Quick intersection test for membership in the PSC (potential shader caster)
-                // list.
-                collides_with_aabr(figure_box, visible_psr_bounds)
-            }
+            };
+            (
+                can_shadow(sun_dir, can_shadow_sun, visible_psr_bounds),
+                can_shadow(
+                    weather.rain_vel(),
+                    weather.rain > RAIN_THRESHOLD,
+                    visible_por_bounds,
+                ),
+            )
         };
 
         // Get player position.
@@ -812,6 +833,8 @@ impl FigureMgr {
             } else if vd_frac > 1.0 {
                 state.as_mut().map(|state| state.visible = false);
                 // Keep processing if this might be a shadow caster.
+                // NOTE: Not worth to do for rain_occlusion, since that only happens in closeby
+                // chunks.
                 if !can_shadow_prev {
                     continue;
                 }
@@ -841,6 +864,7 @@ impl FigureMgr {
                 } else {
                     // Check whether we can shadow.
                     meta.can_shadow_sun = can_shadow_sun(pos, radius);
+                    meta.can_occlude_rain = can_occlude_rain(pos, radius);
                 }
                 (in_frustum, lpindex)
             } else {
@@ -5551,17 +5575,16 @@ impl FigureMgr {
         visible_aabb
     }
 
-    pub fn render_shadows<'a>(
+    fn render_shadow_mapping<'a>(
         &'a self,
         drawer: &mut FigureShadowDrawer<'_, 'a>,
         state: &State,
         tick: u64,
         (camera, figure_lod_render_distance): CameraData,
+        filter_state: impl Fn(&FigureStateMeta) -> bool,
     ) {
-        span!(_guard, "render_shadows", "FigureManager::render_shadows");
         let ecs = state.ecs();
         let items = ecs.read_storage::<Item>();
-
         (
                 &ecs.entities(),
                 &ecs.read_storage::<Pos>(),
@@ -5590,7 +5613,7 @@ impl FigureMgr {
                         Some(Collider::Volume(vol)) => vol.mut_count,
                         _ => 0,
                     },
-                    |state| state.can_shadow_sun(),
+                    &filter_state,
                     if matches!(body, Body::ItemDrop(_)) { items.get(entity).map(ItemKey::from) } else { None },
                 ) {
                     drawer.draw(model, bound);
@@ -5598,6 +5621,32 @@ impl FigureMgr {
             });
     }
 
+    pub fn render_shadows<'a>(
+        &'a self,
+        drawer: &mut FigureShadowDrawer<'_, 'a>,
+        state: &State,
+        tick: u64,
+        camera_data: CameraData,
+    ) {
+        span!(_guard, "render_shadows", "FigureManager::render_shadows");
+        self.render_shadow_mapping(drawer, state, tick, camera_data, |state| {
+            state.can_shadow_sun()
+        })
+    }
+
+    pub fn render_rain_occlusion<'a>(
+        &'a self,
+        drawer: &mut FigureShadowDrawer<'_, 'a>,
+        state: &State,
+        tick: u64,
+        camera_data: CameraData,
+    ) {
+        span!(_guard, "render_rain_occlusion", "FigureManager::render_rain_occlusion");
+        self.render_shadow_mapping(drawer, state, tick, camera_data, |state| {
+            state.can_occlude_rain()
+        })
+    }
+
     pub fn render<'a>(
         &'a self,
         drawer: &mut FigureDrawer<'_, 'a>,
@@ -6237,6 +6286,7 @@ pub struct FigureStateMeta {
     last_ori: anim::vek::Quaternion<f32>,
     lpindex: u8,
     can_shadow_sun: bool,
+    can_occlude_rain: bool,
     visible: bool,
     last_pos: Option<anim::vek::Vec3<f32>>,
     avg_vel: anim::vek::Vec3<f32>,
@@ -6253,6 +6303,11 @@ impl FigureStateMeta {
         // Either visible, or explicitly a shadow caster.
         self.visible || self.can_shadow_sun
     }
+
+    pub fn can_occlude_rain(&self) -> bool {
+        // Either visible, or explicitly a rain occluder.
+        self.visible || self.can_occlude_rain
+    }
 }
 
 pub struct FigureState<S> {
@@ -6311,6 +6366,7 @@ impl<S: Skeleton> FigureState<S> {
                 lpindex: 0,
                 visible: false,
                 can_shadow_sun: false,
+                can_occlude_rain: false,
                 last_pos: None,
                 avg_vel: anim::vek::Vec3::zero(),
                 last_light: 1.0,
diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs
index 93a11fe4d7..dddfb0bcfd 100644
--- a/voxygen/src/scene/lod.rs
+++ b/voxygen/src/scene/lod.rs
@@ -183,6 +183,8 @@ impl Lod {
             }
         }
         // Update weather texture
+        // NOTE: consider moving the lerping to a shader if the overhead of uploading to
+        // the gpu each frame becomes an issue.
         let weather = client.state().weather_grid();
         let size = weather.size().as_::<u32>();
         renderer.update_texture(
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 9e870e28a6..09eef58402 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -65,6 +65,9 @@ const SHADOW_FAR: f32 = 128.0; // Far plane for shadow map point light rendering
 /// Used for first person camera effects
 const RUNNING_THRESHOLD: f32 = 0.7;
 
+/// The threashold for starting calculations with rain.
+const RAIN_THRESHOLD: f32 = 0.0;
+
 /// is_daylight, array of active lights.
 pub type LightData<'a> = (bool, &'a [Light]);
 
@@ -705,14 +708,19 @@ impl Scene {
         self.debug.maintain(renderer);
 
         // Maintain the terrain.
-        let (_visible_bounds, visible_light_volume, visible_psr_bounds, visible_occlusion_volume) =
-            self.terrain.maintain(
-                renderer,
-                scene_data,
-                focus_pos,
-                self.loaded_distance,
-                &self.camera,
-            );
+        let (
+            _visible_bounds,
+            visible_light_volume,
+            visible_psr_bounds,
+            visible_occlusion_volume,
+            visible_por_bounds,
+        ) = self.terrain.maintain(
+            renderer,
+            scene_data,
+            focus_pos,
+            self.loaded_distance,
+            &self.camera,
+        );
 
         // Maintain the figures.
         let _figure_bounds = self.figure_mgr.maintain(
@@ -720,6 +728,7 @@ impl Scene {
             &mut self.trail_mgr,
             scene_data,
             visible_psr_bounds,
+            visible_por_bounds,
             &self.camera,
             Some(&self.terrain),
         );
@@ -1003,7 +1012,7 @@ impl Scene {
         let weather = client
             .state()
             .max_weather_near(focus_off.xy() + cam_pos.xy());
-        if weather.rain > 0.0 {
+        if weather.rain > RAIN_THRESHOLD {
             let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
             let rain_vel = weather.rain_vel();
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
@@ -1139,7 +1148,7 @@ impl Scene {
         let is_daylight = sun_dir.z < 0.0;
         let focus_pos = self.camera.get_focus_pos();
         let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
-        let is_rain = state.max_weather_near(cam_pos.xy()).rain > 0.0;
+        let is_rain = state.max_weather_near(cam_pos.xy()).rain > RAIN_THRESHOLD;
 
         let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
 
@@ -1178,7 +1187,7 @@ impl Scene {
                 self.terrain
                     .render_rain_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
 
-                self.figure_mgr.render_shadows(
+                self.figure_mgr.render_rain_occlusion(
                     &mut occlusion_pass.draw_figure_shadows(),
                     state,
                     tick,
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 5c24f924ab..a34567cad4 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -18,7 +18,7 @@ use crate::{
 
 use super::{
     camera::{self, Camera},
-    math, SceneData,
+    math, SceneData, RAIN_THRESHOLD,
 };
 use common::{
     assets::{self, AssetExt, DotVoxAsset},
@@ -821,6 +821,7 @@ impl<V: RectRasterableVol> Terrain<V> {
         Vec<math::Vec3<f32>>,
         math::Aabr<f32>,
         Vec<math::Vec3<f32>>,
+        math::Aabr<f32>,
     ) {
         let camera::Dependents {
             view_mat,
@@ -1313,6 +1314,9 @@ impl<V: RectRasterableVol> Terrain<V> {
             #[cfg(not(feature = "simd"))]
             return min.partial_cmple(&max).reduce_and();
         };
+
+        let cam_pos = math::Vec4::from(view_mat.inverted() * Vec4::unit_w()).xyz();
+
         let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0
             && renderer.pipeline_modes().shadow.is_map()
         {
@@ -1335,9 +1339,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             .map(|v| v.as_::<f32>())
             .collect::<Vec<_>>();
 
-            let cam_pos = math::Vec4::from(view_mat.inverted() * Vec4::unit_w()).xyz();
             let up: math::Vec3<f32> = { math::Vec3::unit_y() };
-
             let ray_mat = math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, up);
             let visible_bounds = math::Aabr::from(math::fit_psr(
                 ray_mat,
@@ -1407,7 +1409,7 @@ impl<V: RectRasterableVol> Terrain<V> {
         span!(guard, "Rain occlusion magic");
         // Check if there is rain near the camera
         let max_weather = scene_data.state.max_weather_near(focus_pos.xy());
-        let visible_occlusion_volume = if max_weather.rain > 0.0 {
+        let (visible_occlusion_volume, visible_por_bounds) = if max_weather.rain > RAIN_THRESHOLD {
             let visible_bounding_box = math::Aabb::<f32> {
                 min: math::Vec3::from(visible_bounding_box.min - focus_off),
                 max: math::Vec3::from(visible_bounding_box.max - focus_off),
@@ -1422,16 +1424,25 @@ impl<V: RectRasterableVol> Terrain<V> {
             // NOTE: We use proj_mat_treeculler here because
             // calc_focused_light_volume_points makes the assumption that the
             // near plane lies before the far plane.
-            math::calc_focused_light_volume_points(
+            let visible_volume = math::calc_focused_light_volume_points(
                 inv_proj_view,
                 ray_direction.as_::<f64>(),
                 visible_bounds_fine,
                 1e-6,
             )
             .map(|v| v.as_::<f32>())
-            .collect::<Vec<_>>()
+            .collect::<Vec<_>>();
+            let ray_mat =
+                math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
+            let visible_bounds = math::Aabr::from(math::fit_psr(
+                ray_mat,
+                visible_volume.iter().copied(),
+                |p| p,
+            ));
+
+            (visible_volume, visible_bounds)
         } else {
-            Vec::new()
+            (Vec::new(), math::Aabr::default())
         };
 
         drop(guard);
@@ -1440,6 +1451,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             visible_light_volume,
             visible_psr_bounds,
             visible_occlusion_volume,
+            visible_por_bounds,
         )
     }
 

From ab707b6df183f90b5a10c6cdf90d0ad3ccc0a4ba Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Thu, 9 Jun 2022 11:45:38 +0200
Subject: [PATCH 59/71] sample weather at correct position for occlusion

---
 voxygen/src/scene/terrain.rs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index a34567cad4..4e915f5689 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -826,6 +826,7 @@ impl<V: RectRasterableVol> Terrain<V> {
         let camera::Dependents {
             view_mat,
             proj_mat_treeculler,
+            cam_pos,
             ..
         } = camera.dependents();
 
@@ -1315,7 +1316,6 @@ impl<V: RectRasterableVol> Terrain<V> {
             return min.partial_cmple(&max).reduce_and();
         };
 
-        let cam_pos = math::Vec4::from(view_mat.inverted() * Vec4::unit_w()).xyz();
 
         let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0
             && renderer.pipeline_modes().shadow.is_map()
@@ -1340,6 +1340,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             .collect::<Vec<_>>();
 
             let up: math::Vec3<f32> = { math::Vec3::unit_y() };
+            let cam_pos = math::Vec3::from(cam_pos);
             let ray_mat = math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, up);
             let visible_bounds = math::Aabr::from(math::fit_psr(
                 ray_mat,
@@ -1408,7 +1409,7 @@ impl<V: RectRasterableVol> Terrain<V> {
         drop(guard);
         span!(guard, "Rain occlusion magic");
         // Check if there is rain near the camera
-        let max_weather = scene_data.state.max_weather_near(focus_pos.xy());
+        let max_weather = scene_data.state.max_weather_near(focus_off.xy() + cam_pos.xy());
         let (visible_occlusion_volume, visible_por_bounds) = if max_weather.rain > RAIN_THRESHOLD {
             let visible_bounding_box = math::Aabb::<f32> {
                 min: math::Vec3::from(visible_bounding_box.min - focus_off),
@@ -1418,7 +1419,7 @@ impl<V: RectRasterableVol> Terrain<V> {
                 min: visible_bounding_box.min.as_::<f64>(),
                 max: visible_bounding_box.max.as_::<f64>(),
             };
-            let weather = scene_data.state.weather_at(focus_pos.xy());
+            let weather = scene_data.state.weather_at(focus_off.xy() + cam_pos.xy());
             let ray_direction = math::Vec3::<f32>::from(weather.rain_vel().normalized());
 
             // NOTE: We use proj_mat_treeculler here because
@@ -1432,6 +1433,7 @@ impl<V: RectRasterableVol> Terrain<V> {
             )
             .map(|v| v.as_::<f32>())
             .collect::<Vec<_>>();
+            let cam_pos = math::Vec3::from(cam_pos);
             let ray_mat =
                 math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y());
             let visible_bounds = math::Aabr::from(math::fit_psr(

From 5b7b13adcee1944fbea7a6452a1819cda812b683 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Thu, 9 Jun 2022 12:16:40 +0200
Subject: [PATCH 60/71] Add medium check to rain

---
 assets/voxygen/shaders/clouds-frag.glsl | 2 +-
 voxygen/src/scene/figure/mod.rs         | 6 +++++-
 voxygen/src/scene/terrain.rs            | 5 +++--
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 6ec61eb9ee..b3412d558f 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -114,7 +114,7 @@ void main() {
     // of rain_dir this has issues with being in a place where it doesn't rain
     // and seeing rain. 
     float rain = rain_density_at(cam_wpos.xy);
-    if (rain > 0.0) {
+    if (medium.x == MEDIUM_AIR && rain > 0.0) {
         float rain_dist = 50.0;
         for (int i = 0; i < 4; i ++) {
             float old_rain_dist = rain_dist;
diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs
index f0b417949c..b7a0ec5f5e 100644
--- a/voxygen/src/scene/figure/mod.rs
+++ b/voxygen/src/scene/figure/mod.rs
@@ -5641,7 +5641,11 @@ impl FigureMgr {
         tick: u64,
         camera_data: CameraData,
     ) {
-        span!(_guard, "render_rain_occlusion", "FigureManager::render_rain_occlusion");
+        span!(
+            _guard,
+            "render_rain_occlusion",
+            "FigureManager::render_rain_occlusion"
+        );
         self.render_shadow_mapping(drawer, state, tick, camera_data, |state| {
             state.can_occlude_rain()
         })
diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 4e915f5689..28a744cb4c 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -1316,7 +1316,6 @@ impl<V: RectRasterableVol> Terrain<V> {
             return min.partial_cmple(&max).reduce_and();
         };
 
-
         let (visible_light_volume, visible_psr_bounds) = if ray_direction.z < 0.0
             && renderer.pipeline_modes().shadow.is_map()
         {
@@ -1409,7 +1408,9 @@ impl<V: RectRasterableVol> Terrain<V> {
         drop(guard);
         span!(guard, "Rain occlusion magic");
         // Check if there is rain near the camera
-        let max_weather = scene_data.state.max_weather_near(focus_off.xy() + cam_pos.xy());
+        let max_weather = scene_data
+            .state
+            .max_weather_near(focus_off.xy() + cam_pos.xy());
         let (visible_occlusion_volume, visible_por_bounds) = if max_weather.rain > RAIN_THRESHOLD {
             let visible_bounding_box = math::Aabb::<f32> {
                 min: math::Vec3::from(visible_bounding_box.min - focus_off),

From 80e29e2c205d7277d04f854d7e39b1927552384e Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sat, 11 Jun 2022 22:57:07 +0200
Subject: [PATCH 61/71] put rain_density in uniform

---
 assets/voxygen/shaders/clouds-frag.glsl                  | 5 ++---
 assets/voxygen/shaders/fluid-frag/shiny.glsl             | 3 +--
 assets/voxygen/shaders/include/rain_occlusion.glsl       | 3 ++-
 assets/voxygen/shaders/rain-occlusion-directed-vert.glsl | 3 ++-
 assets/voxygen/shaders/rain-occlusion-figure-vert.glsl   | 3 ++-
 assets/voxygen/shaders/terrain-frag.glsl                 | 5 ++---
 server/src/cmd.rs                                        | 2 +-
 voxygen/src/render/pipelines/rain_occlusion.rs           | 7 +++++--
 voxygen/src/scene/mod.rs                                 | 6 +++++-
 9 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index b3412d558f..0242ccc824 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -113,8 +113,7 @@ void main() {
     // called each iteration of the loop. With the current implementation
     // of rain_dir this has issues with being in a place where it doesn't rain
     // and seeing rain. 
-    float rain = rain_density_at(cam_wpos.xy);
-    if (medium.x == MEDIUM_AIR && rain > 0.0) {
+    if (medium.x == MEDIUM_AIR && rain_density > 0.0) {
         float rain_dist = 50.0;
         for (int i = 0; i < 4; i ++) {
             float old_rain_dist = rain_dist;
@@ -141,7 +140,7 @@ void main() {
             if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
                 break;
             }
-            float rain_density = rain * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+            float rain_density = rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
 
             if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                 continue;
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index 20c6b7160e..f99d30bf60 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -149,7 +149,6 @@ void main() {
         wave_sample_dist / slope
     );
 
-    float rain_density = rain_density_at(f_pos.xy + focus_off.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
     if (rain_density > 0 && surf_norm.z > 0.5) {
         vec3 drop_density = vec3(2, 2, 2);
         vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
@@ -158,7 +157,7 @@ void main() {
         drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
         vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
+        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
             vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
             vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index 78d5c7d8b7..ceef0c8bf3 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -14,7 +14,8 @@ uniform u_rain_occlusion {
     mat4 rain_occlusion_texture_mat;
     mat4 rel_rain_dir_mat;
     float integrated_rain_vel;
-    vec3 occlusion_dummy; // Fix alignment.
+    float rain_density;
+    vec2 occlusion_dummy; // Fix alignment.
 };
 
 float rain_occlusion_at(in vec3 fragPos)
diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
index f0feb6891c..ba3889d2ae 100644
--- a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
@@ -30,7 +30,8 @@ uniform u_rain_occlusion {
     mat4 rain_occlusion_texture_mat;
     mat4 rel_rain_dir_mat;
     float integrated_rain_vel;
-    vec3 occlusion_dummy; // Fix alignment.
+    float rain_density;
+    vec2 occlusion_dummy; // Fix alignment.
 };
 
 /* Accurate packed shadow maps for many lights at once!
diff --git a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
index 800a74b6b3..fe64700e79 100644
--- a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
@@ -32,7 +32,8 @@ uniform u_rain_occlusion {
     mat4 texture_mat;
     mat4 rel_rain_dir_mat;
     float integrated_rain_vel;
-    vec3 occlusion_dummy; // Fix alignment.
+    float rain_density;
+    vec2 occlusion_dummy; // Fix alignment.
 };
 
 /* Accurate packed shadow maps for many lights at once!
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index e37eac610f..d495fbd1b3 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -231,12 +231,11 @@ void main() {
     vec3 k_d = vec3(1.0);
     vec3 k_s = vec3(R_s);
 
-    vec3 pos = f_pos + focus_off.xyz;
-    float rain_density = rain_density_at(pos.xy) * rain_occlusion_at(f_pos.xyz) * 50.0;
     // Toggle to see rain_occlusion
     // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
     // return;
     if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
+        vec3 pos = f_pos + focus_off.xyz;
         vec3 drop_density = vec3(2, 2, 2);
         vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
         drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
@@ -244,7 +243,7 @@ void main() {
         drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
         vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density) {
+        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
             vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
             vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
index 5795d6727f..ab65b6d843 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -3656,7 +3656,7 @@ fn handle_weather_zone(
                 add_zone(weather::Weather {
                     cloud: 0.3,
                     rain: 0.3,
-                    wind: Vec2::new(15.0,20.0),
+                    wind: Vec2::new(15.0, 20.0),
                 });
                 Ok(())
             },
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index 8e44ef0a53..86e30255f4 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -14,8 +14,9 @@ pub struct Locals {
     rel_rain_dir_mat: [[f32; 4]; 4],
     /// A value to offset the rain, to make it move over time.
     integrated_rain_vel: f32,
+    rain_density: f32,
     // To keep 16-byte-aligned.
-    occlusion_dummy: [f32; 3],
+    occlusion_dummy: [f32; 2],
 }
 /// Make sure Locals is 16-byte-aligned.
 const _: () = assert!(core::mem::size_of::<Locals>() % 16 == 0);
@@ -25,6 +26,7 @@ impl Locals {
         rain_occlusion_matrices: Mat4<f32>,
         rain_occlusion_texture_mat: Mat4<f32>,
         rel_rain_dir_mat: Mat4<f32>,
+        rain_density: f32,
         integrated_rain_vel: f32,
     ) -> Self {
         Self {
@@ -32,7 +34,8 @@ impl Locals {
             rain_occlusion_texture_mat: rain_occlusion_texture_mat.into_col_arrays(),
             rel_rain_dir_mat: rel_rain_dir_mat.into_col_arrays(),
             integrated_rain_vel,
-            occlusion_dummy: [0.0; 3],
+            rain_density,
+            occlusion_dummy: [0.0; 2],
         }
     }
 }
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 09eef58402..3014a45eca 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1027,12 +1027,16 @@ impl Scene {
                 shadow_mat,
                 texture_mat,
                 rel_rain_dir_mat,
+                weather.rain,
                 self.integrated_rain_vel,
             );
 
             renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
-        } else {
+        } else if self.integrated_rain_vel > 0.0 {
             self.integrated_rain_vel = 0.0;
+            // Need to set rain to zero
+            let rain_occlusion_locals = RainOcclusionLocals::default();
+            renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
         }
 
         let sun_dir = scene_data.get_sun_dir();

From e2969dc5f98f10512739008c61c121856e651c7a Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 12 Jun 2022 01:42:29 +0200
Subject: [PATCH 62/71] turn off rain for CLOUD_MODE_NONE and adjust iterations

---
 assets/voxygen/shaders/clouds-frag.glsl      | 102 ++++++++++---------
 assets/voxygen/shaders/fluid-frag/shiny.glsl |  38 +++----
 assets/voxygen/shaders/terrain-frag.glsl     |  50 ++++-----
 3 files changed, 101 insertions(+), 89 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 0242ccc824..19cc6e9b74 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -97,63 +97,71 @@ void main() {
 
     #if (CLOUD_MODE == CLOUD_MODE_NONE)
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
-    #endif
+    #else
+        vec3 old_color = color.rgb;
 
-    vec3 old_color = color.rgb;
+        dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
-    dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
+        float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+        vec2 dir_2d = normalize(dir.xy);
+        vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
-    float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
-    vec2 dir_2d = normalize(dir.xy);
-    vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
+        vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+        // Rain density is now only based on the cameras current position. 
+        // This could be affected by a setting where rain_density_at is instead
+        // called each iteration of the loop. With the current implementation
+        // of rain_dir this has issues with being in a place where it doesn't rain
+        // and seeing rain. 
+        if (medium.x == MEDIUM_AIR && rain_density > 0.0) {
+            float rain_dist = 50.0;
+            #if (CLOUD_MODE <= CLOUD_MODE_LOW)
+                int iterations = 2;
+            #elif (CLOUD_MODE == CLOUD_MODE_MEDIUM)
+                int iterations = 3;
+            #else
+                int iterations = 4;
+            #endif
 
-    vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
-    // Rain density is now only based on the cameras current position. 
-    // This could be affected by a setting where rain_density_at is instead
-    // called each iteration of the loop. With the current implementation
-    // of rain_dir this has issues with being in a place where it doesn't rain
-    // and seeing rain. 
-    if (medium.x == MEDIUM_AIR && rain_density > 0.0) {
-        float rain_dist = 50.0;
-        for (int i = 0; i < 4; i ++) {
-            float old_rain_dist = rain_dist;
-            rain_dist *= 0.3;
+            for (int i = 0; i < iterations; i ++) {
+                float old_rain_dist = rain_dist;
+                rain_dist *= 0.3;
 
-            vec2 drop_density = vec2(30, 1);
+                vec2 drop_density = vec2(30, 1);
 
-            vec2 rain_pos = (view_pos * rain_dist);
-            rain_pos.y += integrated_rain_vel;
+                vec2 rain_pos = (view_pos * rain_dist);
+                rain_pos.y += integrated_rain_vel;
 
-            vec2 cell = floor(rain_pos * drop_density) / drop_density;
+                vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
-            float drop_depth = mix(
-                old_rain_dist,
-                rain_dist,
-                fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
-            );
-            vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
-            float dist_to_rain = length(rpos);
-            if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
-                continue;
+                float drop_depth = mix(
+                    old_rain_dist,
+                    rain_dist,
+                    fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
+                );
+                vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
+                float dist_to_rain = length(rpos);
+                if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
+                    continue;
+                }
+
+                if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+                    break;
+                }
+                float rain_density = rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+
+                if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
+                    continue;
+                }
+                vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
+
+                vec2 drop_size = vec2(0.0008, 0.03);
+                float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
+                float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
+                float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
+                color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
             }
-
-            if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-                break;
-            }
-            float rain_density = rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
-
-            if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
-                continue;
-            }
-            vec2 near_drop = cell + (vec2(0.5) + (vec2(hash(vec4(cell, 0, 0)), 0.5) - 0.5) * vec2(2, 0)) / drop_density;
-
-            vec2 drop_size = vec2(0.0008, 0.03);
-            float avg_alpha = (drop_size.x * drop_size.y) * 10 / 1;
-            float alpha = sign(max(1 - length((rain_pos - near_drop) / drop_size * 0.1), 0));
-            float light = sqrt(dot(old_color, vec3(1))) + (get_sun_brightness() + get_moon_brightness()) * 0.01;
-            color.rgb = mix(color.rgb, vec3(0.3, 0.4, 0.5) * light, mix(avg_alpha, alpha, min(1000 / dist_to_rain, 1)) * 0.25);
         }
-    }
+    #endif
 
     tgt_color = vec4(color.rgb, 1);
 }
diff --git a/assets/voxygen/shaders/fluid-frag/shiny.glsl b/assets/voxygen/shaders/fluid-frag/shiny.glsl
index f99d30bf60..413e9809ea 100644
--- a/assets/voxygen/shaders/fluid-frag/shiny.glsl
+++ b/assets/voxygen/shaders/fluid-frag/shiny.glsl
@@ -149,27 +149,29 @@ void main() {
         wave_sample_dist / slope
     );
 
-    if (rain_density > 0 && surf_norm.z > 0.5) {
-        vec3 drop_density = vec3(2, 2, 2);
-        vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
-        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-        drop_pos.z += noise_2d(cell2d * 13.1) * 10;
-        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+    #if (CLOUD_MODE != CLOUD_MODE_NONE)
+        if (rain_density > 0 && surf_norm.z > 0.5) {
+            vec3 drop_density = vec3(2, 2, 2);
+            vec3 drop_pos = wave_pos + vec3(0, 0, -time_of_day.x * 0.025);
+            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+            drop_pos.z += noise_2d(cell2d * 13.1) * 10;
+            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
-            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
+                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-            float drop_rad = 0.125;
-            nmap.xy += (drop_pos - near_cell).xy
-                * max(1.0 - abs(dist - drop_rad) * 50, 0)
-                * 2500
-                * sign(dist - drop_rad)
-                * max(drop_pos.z - near_cell.z, 0);
+                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+                float drop_rad = 0.125;
+                nmap.xy += (drop_pos - near_cell).xy
+                    * max(1.0 - abs(dist - drop_rad) * 50, 0)
+                    * 2500
+                    * sign(dist - drop_rad)
+                    * max(drop_pos.z - near_cell.z, 0);
+            }
         }
-    }
+    #endif
 
     nmap = mix(f_norm, normalize(nmap), min(1.0 / pow(frag_dist, 0.75), 1));
 
diff --git a/assets/voxygen/shaders/terrain-frag.glsl b/assets/voxygen/shaders/terrain-frag.glsl
index d495fbd1b3..2c821f9cf4 100644
--- a/assets/voxygen/shaders/terrain-frag.glsl
+++ b/assets/voxygen/shaders/terrain-frag.glsl
@@ -234,33 +234,35 @@ void main() {
     // Toggle to see rain_occlusion
     // tgt_color = vec4(rain_occlusion_at(f_pos.xyz), 0.0, 0.0, 1.0);
     // return;
-    if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
-        vec3 pos = f_pos + focus_off.xyz;
-        vec3 drop_density = vec3(2, 2, 2);
-        vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
-        drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
-        vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
-        drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
-        vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
+    #if (CLOUD_MODE != CLOUD_MODE_NONE)
+        if (rain_density > 0 && !faces_fluid && f_norm.z > 0.5) {
+            vec3 pos = f_pos + focus_off.xyz;
+            vec3 drop_density = vec3(2, 2, 2);
+            vec3 drop_pos = pos + vec3(pos.zz, 0) + vec3(0, 0, -tick.x * 1.0);
+            drop_pos.z += noise_2d(floor(drop_pos.xy * drop_density.xy) * 13.1) * 10;
+            vec2 cell2d = floor(drop_pos.xy * drop_density.xy);
+            drop_pos.z *= 0.5 + hash_fast(uvec3(cell2d, 0));
+            vec3 cell = vec3(cell2d, floor(drop_pos.z * drop_density.z));
 
-        if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
-            vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
-            vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
+            if (fract(hash(fract(vec4(cell, 0) * 0.01))) < rain_density * rain_occlusion_at(f_pos.xyz) * 50.0) {
+                vec3 off = vec3(hash_fast(uvec3(cell * 13)), hash_fast(uvec3(cell * 5)), 0);
+                vec3 near_cell = (cell + 0.5 + (off - 0.5) * 0.5) / drop_density;
 
-            float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
-            float drop_rad = 0.1;
-            float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
-            k_a += distort;
-            k_d += distort;
-            k_s += distort;
-            f_norm.xy += (drop_pos - near_cell).xy
-                * max(1.0 - abs(dist - drop_rad) * 30, 0)
-                * 500.0
-                * max(drop_pos.z - near_cell.z, 0)
-                * sign(dist - drop_rad)
-                * max(drop_pos.z - near_cell.z, 0);
+                float dist = length((drop_pos - near_cell) / vec3(1, 1, 2));
+                float drop_rad = 0.1;
+                float distort = max(1.0 - abs(dist - drop_rad) * 100, 0) * 1.5 * max(drop_pos.z - near_cell.z, 0);
+                k_a += distort;
+                k_d += distort;
+                k_s += distort;
+                f_norm.xy += (drop_pos - near_cell).xy
+                    * max(1.0 - abs(dist - drop_rad) * 30, 0)
+                    * 500.0
+                    * max(drop_pos.z - near_cell.z, 0)
+                    * sign(dist - drop_rad)
+                    * max(drop_pos.z - near_cell.z, 0);
+            }
         }
-    }
+    #endif
 
     // float sun_light = get_sun_brightness(sun_dir);
     // float moon_light = get_moon_brightness(moon_dir);

From 5866e23e3208fcd2c59e573cdb7e810fc4d8cbfa Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Sat, 11 Jun 2022 19:06:31 -0400
Subject: [PATCH 63/71] Pre-compute view_mat_inv * proj_mat_inv on the CPU
 before sending to the cloud shader

---
 assets/voxygen/shaders/clouds-frag.glsl | 54 +++++++++++++++++++++----
 assets/voxygen/shaders/include/lod.glsl |  1 +
 voxygen/src/render/pipelines/clouds.rs  | 10 +++--
 3 files changed, 54 insertions(+), 11 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 19cc6e9b74..0c40dc067f 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -42,17 +42,31 @@ layout(location = 0) in vec2 uv;
 
 layout (std140, set = 2, binding = 4)
 uniform u_locals {
-    mat4 proj_mat_inv;
-    mat4 view_mat_inv;
+    //mat4 proj_mat_inv;
+    //mat4 view_mat_inv;
+    mat4 all_mat_inv;
 };
 
 layout(location = 0) out vec4 tgt_color;
 
+// start
+// 777 instructions with rain commented out
+// 0.55 - 0.58 ms staring at high time area in sky in rain
+// 0.48 ms staring at roughly open sky in rain 45 degree
+// 0.35 ms staring at feet in rain
+
+// precombine inversion matrix
+// 683 instructions 
+// 0.55 ms starting at high time arena in sky in rain
+// 0.46 ms staring roughly open sky roughly 45 degree in rain
+// 0.33 ms staring at feet in rain
+
+
 vec3 wpos_at(vec2 uv) {
     float buf_depth = texture(sampler2D(t_src_depth, s_src_depth), uv).x;
-    mat4 inv = view_mat_inv * proj_mat_inv;//inverse(all_mat);
+    //mat4 inv = view_mat_inv * proj_mat_inv;//inverse(all_mat);
     vec4 clip_space = vec4((uv * 2.0 - 1.0) * vec2(1, -1), buf_depth, 1.0);
-    vec4 view_space = inv * clip_space;
+    vec4 view_space = all_mat_inv * clip_space;
     view_space /= view_space.w;
     if (buf_depth == 0.0) {
         vec3 direction = normalize(view_space.xyz);
@@ -62,7 +76,7 @@ vec3 wpos_at(vec2 uv) {
     }
 }
 
-mat4 spin_in_axis(vec3 axis, float angle)
+/*mat4 spin_in_axis(vec3 axis, float angle)
 {
     axis = normalize(axis);
     float s = sin(angle);
@@ -73,10 +87,11 @@ mat4 spin_in_axis(vec3 axis, float angle)
         oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c,          oc * axis.y * axis.z - axis.x * s, 0,
         oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c,          0,
         0,                                 0,                                 0,                                 1);
-}
+}*/
 
 void main() {
     vec4 color = texture(sampler2D(t_src_color, s_src_color), uv);
+    color.rgb *= 0.25;
 
     #ifdef EXPERIMENTAL_BAREMINIMUM
         tgt_color = vec4(color.rgb, 1);
@@ -93,25 +108,43 @@ void main() {
         cloud_blend = 1.0 - color.a;
         dist = DIST_CAP;
     }
-    color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
+   //color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
 
     #if (CLOUD_MODE == CLOUD_MODE_NONE)
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
     #else
         vec3 old_color = color.rgb;
 
+        // 0.43 ms extra without this
+        // 0.01 ms spent on rain_density_at?
+        // 0.49 -> 0.13 (0.36 ms with full occupancy)
+        //tgt_color = vec4(color.rgb, 1);
+        //return;
+
+        // normalized direction from the camera position to the fragment in world, transformed by the relative rain direction
         dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
+        // stretch z values far from 0
         float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+        // normalize xy to get a 2d direction
         vec2 dir_2d = normalize(dir.xy);
+        // view_pos is the angle from x axis (except x and y are flipped, so the
+        // angle 0 is looking along the y-axis)
+        //
+        // combined with stretched z position essentially we unroll a cylinder
+        // around the z axis while stretching it to make the sections near the
+        // origin larger in the Z direction
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
+        // compute camera position in the world
         vec3 cam_wpos = cam_pos.xyz + focus_off.xyz;
+        
         // Rain density is now only based on the cameras current position. 
         // This could be affected by a setting where rain_density_at is instead
         // called each iteration of the loop. With the current implementation
         // of rain_dir this has issues with being in a place where it doesn't rain
         // and seeing rain. 
+        float rain_density = rain_density * 1.0;
         if (medium.x == MEDIUM_AIR && rain_density > 0.0) {
             float rain_dist = 50.0;
             #if (CLOUD_MODE <= CLOUD_MODE_LOW)
@@ -133,6 +166,13 @@ void main() {
 
                 vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
+                // For reference:
+                //
+                // float hash(vec4 p) {
+                //     p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121);
+                //     p *= 17.0;
+                //     return (fract(p.x * p.y * (1.0 - p.z) * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
+                // }
                 float drop_depth = mix(
                     old_rain_dist,
                     rain_dist,
diff --git a/assets/voxygen/shaders/include/lod.glsl b/assets/voxygen/shaders/include/lod.glsl
index e56e2f5010..d2efde0737 100644
--- a/assets/voxygen/shaders/include/lod.glsl
+++ b/assets/voxygen/shaders/include/lod.glsl
@@ -28,6 +28,7 @@ vec4 cubic(float v) {
     return vec4(x, y, z, w) * (1.0/6.0);
 }
 
+// Computes atan(y, x), except with more stability when x is near 0.
 float atan2(in float y, in float x) {
     bool s = (abs(x) > abs(y));
     return mix(PI/2.0 - atan(x,y), atan(y,x), s);
diff --git a/voxygen/src/render/pipelines/clouds.rs b/voxygen/src/render/pipelines/clouds.rs
index f0531bdf85..0360d296d1 100644
--- a/voxygen/src/render/pipelines/clouds.rs
+++ b/voxygen/src/render/pipelines/clouds.rs
@@ -8,8 +8,9 @@ use vek::*;
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Zeroable, Pod)]
 pub struct Locals {
-    proj_mat_inv: [[f32; 4]; 4],
-    view_mat_inv: [[f32; 4]; 4],
+    //proj_mat_inv: [[f32; 4]; 4],
+    //view_mat_inv: [[f32; 4]; 4],
+    all_mat_inv: [[f32; 4]; 4],
 }
 
 impl Default for Locals {
@@ -19,8 +20,9 @@ impl Default for Locals {
 impl Locals {
     pub fn new(proj_mat_inv: Mat4<f32>, view_mat_inv: Mat4<f32>) -> Self {
         Self {
-            proj_mat_inv: proj_mat_inv.into_col_arrays(),
-            view_mat_inv: view_mat_inv.into_col_arrays(),
+            //proj_mat_inv: proj_mat_inv.into_col_arrays(),
+            //view_mat_inv: view_mat_inv.into_col_arrays(),
+            all_mat_inv: (view_mat_inv * proj_mat_inv).into_col_arrays(),
         }
     }
 }

From b29bad015cb51d8403c43796fcdc1adf8d6bba48 Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Sat, 11 Jun 2022 23:11:37 -0400
Subject: [PATCH 64/71] Misc notes in cloud-frag.glsl

---
 assets/voxygen/shaders/clouds-frag.glsl | 30 ++++++++++++++++++++-----
 1 file changed, 24 insertions(+), 6 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 0c40dc067f..197115e1d3 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -50,17 +50,22 @@ uniform u_locals {
 layout(location = 0) out vec4 tgt_color;
 
 // start
-// 777 instructions with rain commented out
+// 777 instructions with low clouds commented out
 // 0.55 - 0.58 ms staring at high time area in sky in rain
 // 0.48 ms staring at roughly open sky in rain 45 degree
 // 0.35 ms staring at feet in rain
 
 // precombine inversion matrix
-// 683 instructions 
-// 0.55 ms starting at high time arena in sky in rain
+// 683 instructions (-94) 
+// 0.55 ms starting at high time area in sky in rain
 // 0.46 ms staring roughly open sky roughly 45 degree in rain
 // 0.33 ms staring at feet in rain
 
+// rebase on moving rain_density to a uniform
+// 664 instructions (-19)
+// 0.54 ms staring at high time area in sky in rain
+// 0.51 ms staring rougly open sky roughly 45 degree in rain
+// 0.276 ms staring at feet in rain
 
 vec3 wpos_at(vec2 uv) {
     float buf_depth = texture(sampler2D(t_src_depth, s_src_depth), uv).x;
@@ -108,7 +113,7 @@ void main() {
         cloud_blend = 1.0 - color.a;
         dist = DIST_CAP;
     }
-   //color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
+   color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
 
     #if (CLOUD_MODE == CLOUD_MODE_NONE)
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
@@ -118,13 +123,18 @@ void main() {
         // 0.43 ms extra without this
         // 0.01 ms spent on rain_density_at?
         // 0.49 -> 0.13 (0.36 ms with full occupancy)
+        // 1.118 -> 0.689 (0.43 ms extra with low clouds)
+        // 0.264 -> 0.073 (0.191 ms commented clouds, 2 iterations, break -> continue)
+        // 2.08  -> 1.60 (0.48 ms low clouds, break -> continue)
         //tgt_color = vec4(color.rgb, 1);
+        //tgt_color = vec4(vec3(hash(vec4(0.0, uv * 300.0, 0.0))), 1);
         //return;
 
+        // 1.118 -> 1.114 (cost 0.004 ms, roughly 1% of the 0.43 ms total)
         // normalized direction from the camera position to the fragment in world, transformed by the relative rain direction
         dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
-        // stretch z values far from 0
+        // stretch z values as they move away from 0
         float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
         // normalize xy to get a 2d direction
         vec2 dir_2d = normalize(dir.xy);
@@ -185,9 +195,17 @@ void main() {
                 }
 
                 if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
+                    // replacing this break with continue saves ~0.04 ms, roughly 10% of time (commenting out the break saves the same amount of time)
+                    // this also causes the loop to be unrolled
+                    // seems to only save 0.02 ms with clouds uncommented
+                    // note: testing on integrated intel doesn't seem to show any observable difference here
                     break;
+                    // 0.467 0.487 0.467
+                    // 0.425 0.445 0.425
+                    //continue;
                 }
-                float rain_density = rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz) * 10.0;
+                // costs ~0.04 ms, roughly 10% of rain time (37 instructions)
+                float rain_density = 10.0 * rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz);
 
                 if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                     continue;

From b2d3759105a1619e6da754025c4828159075b24a Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Sat, 11 Jun 2022 23:27:38 -0400
Subject: [PATCH 65/71] Cleanup profiling comments and commented code

---
 assets/voxygen/shaders/clouds-frag.glsl | 67 +------------------------
 voxygen/src/render/pipelines/clouds.rs  |  4 --
 2 files changed, 1 insertion(+), 70 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 197115e1d3..8c20434b5e 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -42,34 +42,13 @@ layout(location = 0) in vec2 uv;
 
 layout (std140, set = 2, binding = 4)
 uniform u_locals {
-    //mat4 proj_mat_inv;
-    //mat4 view_mat_inv;
     mat4 all_mat_inv;
 };
 
 layout(location = 0) out vec4 tgt_color;
 
-// start
-// 777 instructions with low clouds commented out
-// 0.55 - 0.58 ms staring at high time area in sky in rain
-// 0.48 ms staring at roughly open sky in rain 45 degree
-// 0.35 ms staring at feet in rain
-
-// precombine inversion matrix
-// 683 instructions (-94) 
-// 0.55 ms starting at high time area in sky in rain
-// 0.46 ms staring roughly open sky roughly 45 degree in rain
-// 0.33 ms staring at feet in rain
-
-// rebase on moving rain_density to a uniform
-// 664 instructions (-19)
-// 0.54 ms staring at high time area in sky in rain
-// 0.51 ms staring rougly open sky roughly 45 degree in rain
-// 0.276 ms staring at feet in rain
-
 vec3 wpos_at(vec2 uv) {
     float buf_depth = texture(sampler2D(t_src_depth, s_src_depth), uv).x;
-    //mat4 inv = view_mat_inv * proj_mat_inv;//inverse(all_mat);
     vec4 clip_space = vec4((uv * 2.0 - 1.0) * vec2(1, -1), buf_depth, 1.0);
     vec4 view_space = all_mat_inv * clip_space;
     view_space /= view_space.w;
@@ -81,19 +60,6 @@ vec3 wpos_at(vec2 uv) {
     }
 }
 
-/*mat4 spin_in_axis(vec3 axis, float angle)
-{
-    axis = normalize(axis);
-    float s = sin(angle);
-    float c = cos(angle);
-    float oc = 1.0 - c;
-
-    return mat4(oc * axis.x * axis.x + c,  oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0,
-        oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c,          oc * axis.y * axis.z - axis.x * s, 0,
-        oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c,          0,
-        0,                                 0,                                 0,                                 1);
-}*/
-
 void main() {
     vec4 color = texture(sampler2D(t_src_color, s_src_color), uv);
     color.rgb *= 0.25;
@@ -120,17 +86,6 @@ void main() {
     #else
         vec3 old_color = color.rgb;
 
-        // 0.43 ms extra without this
-        // 0.01 ms spent on rain_density_at?
-        // 0.49 -> 0.13 (0.36 ms with full occupancy)
-        // 1.118 -> 0.689 (0.43 ms extra with low clouds)
-        // 0.264 -> 0.073 (0.191 ms commented clouds, 2 iterations, break -> continue)
-        // 2.08  -> 1.60 (0.48 ms low clouds, break -> continue)
-        //tgt_color = vec4(color.rgb, 1);
-        //tgt_color = vec4(vec3(hash(vec4(0.0, uv * 300.0, 0.0))), 1);
-        //return;
-
-        // 1.118 -> 1.114 (cost 0.004 ms, roughly 1% of the 0.43 ms total)
         // normalized direction from the camera position to the fragment in world, transformed by the relative rain direction
         dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
@@ -138,12 +93,7 @@ void main() {
         float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
         // normalize xy to get a 2d direction
         vec2 dir_2d = normalize(dir.xy);
-        // view_pos is the angle from x axis (except x and y are flipped, so the
-        // angle 0 is looking along the y-axis)
-        //
-        // combined with stretched z position essentially we unroll a cylinder
-        // around the z axis while stretching it to make the sections near the
-        // origin larger in the Z direction
+        // sort of map cylinder around the camera to 2d grid 
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
         // compute camera position in the world
@@ -176,13 +126,6 @@ void main() {
 
                 vec2 cell = floor(rain_pos * drop_density) / drop_density;
 
-                // For reference:
-                //
-                // float hash(vec4 p) {
-                //     p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121);
-                //     p *= 17.0;
-                //     return (fract(p.x * p.y * (1.0 - p.z) * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0;
-                // }
                 float drop_depth = mix(
                     old_rain_dist,
                     rain_dist,
@@ -195,16 +138,8 @@ void main() {
                 }
 
                 if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
-                    // replacing this break with continue saves ~0.04 ms, roughly 10% of time (commenting out the break saves the same amount of time)
-                    // this also causes the loop to be unrolled
-                    // seems to only save 0.02 ms with clouds uncommented
-                    // note: testing on integrated intel doesn't seem to show any observable difference here
                     break;
-                    // 0.467 0.487 0.467
-                    // 0.425 0.445 0.425
-                    //continue;
                 }
-                // costs ~0.04 ms, roughly 10% of rain time (37 instructions)
                 float rain_density = 10.0 * rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz);
 
                 if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
diff --git a/voxygen/src/render/pipelines/clouds.rs b/voxygen/src/render/pipelines/clouds.rs
index 0360d296d1..52773d3c0c 100644
--- a/voxygen/src/render/pipelines/clouds.rs
+++ b/voxygen/src/render/pipelines/clouds.rs
@@ -8,8 +8,6 @@ use vek::*;
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Zeroable, Pod)]
 pub struct Locals {
-    //proj_mat_inv: [[f32; 4]; 4],
-    //view_mat_inv: [[f32; 4]; 4],
     all_mat_inv: [[f32; 4]; 4],
 }
 
@@ -20,8 +18,6 @@ impl Default for Locals {
 impl Locals {
     pub fn new(proj_mat_inv: Mat4<f32>, view_mat_inv: Mat4<f32>) -> Self {
         Self {
-            //proj_mat_inv: proj_mat_inv.into_col_arrays(),
-            //view_mat_inv: view_mat_inv.into_col_arrays(),
             all_mat_inv: (view_mat_inv * proj_mat_inv).into_col_arrays(),
         }
     }

From a5a082cf4110999af6d0c2f98ed550cdc713dfee Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Sat, 11 Jun 2022 23:51:39 -0400
Subject: [PATCH 66/71] Also use 4 iterations for medium clouds and adjust the
 factor multiplied by rain_dist each iteration based on the number of
 iterations so that the last iteration is always the same.

---
 assets/voxygen/shaders/clouds-frag.glsl | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 8c20434b5e..a930489e3c 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -62,7 +62,6 @@ vec3 wpos_at(vec2 uv) {
 
 void main() {
     vec4 color = texture(sampler2D(t_src_color, s_src_color), uv);
-    color.rgb *= 0.25;
 
     #ifdef EXPERIMENTAL_BAREMINIMUM
         tgt_color = vec4(color.rgb, 1);
@@ -79,7 +78,7 @@ void main() {
         cloud_blend = 1.0 - color.a;
         dist = DIST_CAP;
     }
-   color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
+    color.rgb = mix(color.rgb, get_cloud_color(color.rgb, dir, cam_pos.xyz, time_of_day.x, dist, 1.0), cloud_blend);
 
     #if (CLOUD_MODE == CLOUD_MODE_NONE)
         color.rgb = apply_point_glow(cam_pos.xyz + focus_off.xyz, dir, dist, color.rgb);
@@ -109,15 +108,13 @@ void main() {
             float rain_dist = 50.0;
             #if (CLOUD_MODE <= CLOUD_MODE_LOW)
                 int iterations = 2;
-            #elif (CLOUD_MODE == CLOUD_MODE_MEDIUM)
-                int iterations = 3;
             #else
                 int iterations = 4;
             #endif
 
             for (int i = 0; i < iterations; i ++) {
                 float old_rain_dist = rain_dist;
-                rain_dist *= 0.3;
+                rain_dist *= 0.3 / 4.0 * iterations;
 
                 vec2 drop_density = vec2(30, 1);
 

From e7208c9537ab68be5b1230f3b9dcdf2679686d50 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sun, 12 Jun 2022 09:51:53 +0200
Subject: [PATCH 67/71] make iterations const

---
 assets/voxygen/shaders/clouds-frag.glsl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index a930489e3c..8e2ed106a6 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -107,9 +107,9 @@ void main() {
         if (medium.x == MEDIUM_AIR && rain_density > 0.0) {
             float rain_dist = 50.0;
             #if (CLOUD_MODE <= CLOUD_MODE_LOW)
-                int iterations = 2;
+                const int iterations = 2;
             #else
-                int iterations = 4;
+                const int iterations = 4;
             #endif
 
             for (int i = 0; i < iterations; i ++) {

From e4dfc7729f21708f30625426f556918fb64929db Mon Sep 17 00:00:00 2001
From: Imbris <imbrisf@gmail.com>
Date: Tue, 14 Jun 2022 00:17:10 -0400
Subject: [PATCH 68/71] Fix rain being visible indoors by making rpos properly
 line up with where the rain is being drawn and suppressing any leaks of rain
 occlusion map via flooring the sampled output

---
 assets/voxygen/shaders/clouds-frag.glsl | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 8e2ed106a6..5cef758dd6 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -86,12 +86,12 @@ void main() {
         vec3 old_color = color.rgb;
 
         // normalized direction from the camera position to the fragment in world, transformed by the relative rain direction
-        dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
+        vec3 adjusted_dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
 
         // stretch z values as they move away from 0
-        float z = (-1 / (abs(dir.z) - 1) - 1) * sign(dir.z);
+        float z = (-1 / (abs(adjusted_dir.z) - 1) - 1) * sign(adjusted_dir.z);
         // normalize xy to get a 2d direction
-        vec2 dir_2d = normalize(dir.xy);
+        vec2 dir_2d = normalize(adjusted_dir.xy);
         // sort of map cylinder around the camera to 2d grid 
         vec2 view_pos = vec2(atan2(dir_2d.x, dir_2d.y), z);
 
@@ -128,8 +128,9 @@ void main() {
                     rain_dist,
                     fract(hash(fract(vec4(cell, rain_dist, 0) * 0.1)))
                 );
-                vec3 rpos = vec3(vec2(dir_2d), view_pos.y) * drop_depth;
-                float dist_to_rain = length(rpos);
+
+                float dist_to_rain = drop_depth / length(dir.xy);
+                vec3 rpos = dir * dist_to_rain; 
                 if (dist < dist_to_rain || cam_wpos.z + rpos.z > CLOUD_AVG_ALT) {
                     continue;
                 }
@@ -137,7 +138,7 @@ void main() {
                 if (dot(rpos * vec3(1, 1, 0.5), rpos) < 1.0) {
                     break;
                 }
-                float rain_density = 10.0 * rain_density * rain_occlusion_at(cam_pos.xyz + rpos.xyz);
+                float rain_density = 10.0 * rain_density * floor(rain_occlusion_at(cam_pos.xyz + rpos.xyz));
 
                 if (rain_density < 0.001 || fract(hash(fract(vec4(cell, rain_dist, 0) * 0.01))) > rain_density) {
                     continue;

From 6562c7076f113bd3d255e0e45d592bde5de07695 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Fri, 17 Jun 2022 13:39:08 +0200
Subject: [PATCH 69/71] remove fluid model todo

---
 voxygen/src/scene/terrain.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs
index 28a744cb4c..ee5d729783 100644
--- a/voxygen/src/scene/terrain.rs
+++ b/voxygen/src/scene/terrain.rs
@@ -1527,7 +1527,6 @@ impl<V: RectRasterableVol> Terrain<V> {
             // Find a way to keep this?
             // .filter(|chunk| chunk.can_shadow_sun())
             .filter_map(|chunk| {
-                // TODO: Should the fluid model also be considered here?
                 chunk
                     .opaque_model
                     .as_ref()

From 6d8cbe6f0f9adf45b633be084c5f77f82a7f8374 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sat, 2 Jul 2022 12:00:02 +0200
Subject: [PATCH 70/71] remove relative rain direction and increase FALL_RATE

---
 assets/voxygen/shaders/clouds-frag.glsl             |  2 +-
 assets/voxygen/shaders/include/rain_occlusion.glsl  |  2 +-
 .../shaders/rain-occlusion-directed-vert.glsl       |  2 +-
 .../voxygen/shaders/rain-occlusion-figure-vert.glsl |  2 +-
 common/src/weather.rs                               |  2 +-
 voxygen/src/render/pipelines/rain_occlusion.rs      |  6 +++---
 voxygen/src/scene/mod.rs                            | 13 ++++---------
 7 files changed, 12 insertions(+), 17 deletions(-)

diff --git a/assets/voxygen/shaders/clouds-frag.glsl b/assets/voxygen/shaders/clouds-frag.glsl
index 5cef758dd6..01faedb424 100644
--- a/assets/voxygen/shaders/clouds-frag.glsl
+++ b/assets/voxygen/shaders/clouds-frag.glsl
@@ -86,7 +86,7 @@ void main() {
         vec3 old_color = color.rgb;
 
         // normalized direction from the camera position to the fragment in world, transformed by the relative rain direction
-        vec3 adjusted_dir = (vec4(dir, 0) * rel_rain_dir_mat).xyz;
+        vec3 adjusted_dir = (vec4(dir, 0) * rain_dir_mat).xyz;
 
         // stretch z values as they move away from 0
         float z = (-1 / (abs(adjusted_dir.z) - 1) - 1) * sign(adjusted_dir.z);
diff --git a/assets/voxygen/shaders/include/rain_occlusion.glsl b/assets/voxygen/shaders/include/rain_occlusion.glsl
index ceef0c8bf3..5f525c282f 100644
--- a/assets/voxygen/shaders/include/rain_occlusion.glsl
+++ b/assets/voxygen/shaders/include/rain_occlusion.glsl
@@ -12,7 +12,7 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 rain_occlusion_matrices;
     mat4 rain_occlusion_texture_mat;
-    mat4 rel_rain_dir_mat;
+    mat4 rain_dir_mat;
     float integrated_rain_vel;
     float rain_density;
     vec2 occlusion_dummy; // Fix alignment.
diff --git a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
index ba3889d2ae..c8bfd187e4 100644
--- a/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-directed-vert.glsl
@@ -28,7 +28,7 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 rain_occlusion_matrices;
     mat4 rain_occlusion_texture_mat;
-    mat4 rel_rain_dir_mat;
+    mat4 rain_dir_mat;
     float integrated_rain_vel;
     float rain_density;
     vec2 occlusion_dummy; // Fix alignment.
diff --git a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
index fe64700e79..b7f940ea2b 100644
--- a/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
+++ b/assets/voxygen/shaders/rain-occlusion-figure-vert.glsl
@@ -30,7 +30,7 @@ layout (std140, set = 0, binding = 14)
 uniform u_rain_occlusion {
     mat4 rainOcclusionMatrices;
     mat4 texture_mat;
-    mat4 rel_rain_dir_mat;
+    mat4 rain_dir_mat;
     float integrated_rain_vel;
     float rain_density;
     vec2 occlusion_dummy; // Fix alignment.
diff --git a/common/src/weather.rs b/common/src/weather.rs
index be39dc73a3..b2e163be3a 100644
--- a/common/src/weather.rs
+++ b/common/src/weather.rs
@@ -42,7 +42,7 @@ impl Weather {
 
     // Get the rain velocity for this weather
     pub fn rain_vel(&self) -> Vec3<f32> {
-        const FALL_RATE: f32 = 20.0;
+        const FALL_RATE: f32 = 50.0;
         Vec3::new(self.wind.x, self.wind.y, -FALL_RATE)
     }
 }
diff --git a/voxygen/src/render/pipelines/rain_occlusion.rs b/voxygen/src/render/pipelines/rain_occlusion.rs
index 86e30255f4..9963b4ccae 100644
--- a/voxygen/src/render/pipelines/rain_occlusion.rs
+++ b/voxygen/src/render/pipelines/rain_occlusion.rs
@@ -11,7 +11,7 @@ pub struct Locals {
     rain_occlusion_texture_mat: [[f32; 4]; 4],
     /// A rotation of the direction of the rain, relative to the players
     /// velocity.
-    rel_rain_dir_mat: [[f32; 4]; 4],
+    rain_dir_mat: [[f32; 4]; 4],
     /// A value to offset the rain, to make it move over time.
     integrated_rain_vel: f32,
     rain_density: f32,
@@ -25,14 +25,14 @@ impl Locals {
     pub fn new(
         rain_occlusion_matrices: Mat4<f32>,
         rain_occlusion_texture_mat: Mat4<f32>,
-        rel_rain_dir_mat: Mat4<f32>,
+        rain_dir_mat: Mat4<f32>,
         rain_density: f32,
         integrated_rain_vel: f32,
     ) -> Self {
         Self {
             rain_occlusion_matrices: rain_occlusion_matrices.into_col_arrays(),
             rain_occlusion_texture_mat: rain_occlusion_texture_mat.into_col_arrays(),
-            rel_rain_dir_mat: rel_rain_dir_mat.into_col_arrays(),
+            rain_dir_mat: rain_dir_mat.into_col_arrays(),
             integrated_rain_vel,
             rain_density,
             occlusion_dummy: [0.0; 2],
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 3014a45eca..46fb837a76 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -488,11 +488,6 @@ impl Scene {
             .get(scene_data.player_entity)
             .map_or(Vec3::zero(), |pos| pos.0);
 
-        let player_vel = ecs
-            .read_storage::<comp::Vel>()
-            .get(scene_data.player_entity)
-            .map_or(Vec3::zero(), |vel| vel.0);
-
         let player_rolling = ecs
             .read_storage::<comp::CharacterState>()
             .get(scene_data.player_entity)
@@ -1016,9 +1011,9 @@ impl Scene {
             let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
             let rain_vel = weather.rain_vel();
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
-            let relative_rain_vel = rain_vel - player_vel;
-            self.integrated_rain_vel += relative_rain_vel.magnitude() * dt;
-            let rel_rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), relative_rain_vel);
+            
+            self.integrated_rain_vel += rain_vel.magnitude() * dt;
+            let rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
 
             let (shadow_mat, texture_mat) =
                 directed_mats(rain_view_mat, rain_vel.into(), &visible_occlusion_volume);
@@ -1026,7 +1021,7 @@ impl Scene {
             let rain_occlusion_locals = RainOcclusionLocals::new(
                 shadow_mat,
                 texture_mat,
-                rel_rain_dir_mat,
+                rain_dir_mat,
                 weather.rain,
                 self.integrated_rain_vel,
             );

From 5a0cdc2528263446a20fb6ca8eb509f529ca6d84 Mon Sep 17 00:00:00 2001
From: IsseW <isidornie@gmail.com>
Date: Sat, 2 Jul 2022 20:23:15 +0200
Subject: [PATCH 71/71] add rain threshold that depends on chunk moisture

---
 server/src/weather/sim.rs | 34 +++++++++++++++++++++++++++++++---
 voxygen/src/scene/mod.rs  |  2 +-
 2 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/server/src/weather/sim.rs b/server/src/weather/sim.rs
index bf82ce006f..50612bcbc8 100644
--- a/server/src/weather/sim.rs
+++ b/server/src/weather/sim.rs
@@ -1,7 +1,7 @@
 use common::{
     grid::Grid,
     resources::TimeOfDay,
-    weather::{Weather, WeatherGrid, CELL_SIZE},
+    weather::{Weather, WeatherGrid, CELL_SIZE, CHUNKS_PER_CELL},
 };
 use noise::{NoiseFn, SuperSimplex, Turbulence};
 use vek::*;
@@ -18,15 +18,42 @@ struct WeatherZone {
     time_to_live: f32,
 }
 
+struct CellConsts {
+    rain_factor: f32,
+}
+
 pub struct WeatherSim {
     size: Vec2<u32>,
+    consts: Grid<CellConsts>,
     zones: Grid<Option<WeatherZone>>,
 }
 
 impl WeatherSim {
-    pub fn new(size: Vec2<u32>, _world: &World) -> Self {
+    pub fn new(size: Vec2<u32>, world: &World) -> Self {
         Self {
             size,
+            consts: Grid::from_raw(
+                size.as_(),
+                (0..size.x * size.y)
+                    .map(|i| Vec2::new(i % size.x, i / size.x))
+                    .map(|p| {
+                        let mut humid_sum = 0.0;
+
+                        for y in 0..CHUNKS_PER_CELL {
+                            for x in 0..CHUNKS_PER_CELL {
+                                let chunk_pos = p * CHUNKS_PER_CELL + Vec2::new(x, y);
+                                if let Some(chunk) = world.sim().get(chunk_pos.as_()) {
+                                    let env = chunk.get_environment();
+                                    humid_sum += env.humid;
+                                }
+                            }
+                        }
+                        let average_humid = humid_sum / (CHUNKS_PER_CELL * CHUNKS_PER_CELL) as f32;
+                        let rain_factor = (2.0 * average_humid.powf(0.2)).min(1.0);
+                        CellConsts { rain_factor }
+                    })
+                    .collect::<Vec<_>>(),
+            ),
             zones: Grid::new(size.as_(), None),
         }
     }
@@ -89,7 +116,8 @@ impl WeatherSim {
 
                 const RAIN_CLOUD_THRESHOLD: f32 = 0.26;
                 cell.cloud = (1.0 - pressure) * 0.5;
-                cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0).powf(1.0);
+                cell.rain = (1.0 - pressure - RAIN_CLOUD_THRESHOLD).max(0.0)
+                    * self.consts[point].rain_factor;
                 cell.wind = Vec2::new(
                     rain_nz.get(spos.into_array()).powi(3) as f32,
                     rain_nz.get((spos + 1.0).into_array()).powi(3) as f32,
diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs
index 46fb837a76..76ff9aaeaf 100644
--- a/voxygen/src/scene/mod.rs
+++ b/voxygen/src/scene/mod.rs
@@ -1011,7 +1011,7 @@ impl Scene {
             let weather = client.state().weather_at(focus_off.xy() + cam_pos.xy());
             let rain_vel = weather.rain_vel();
             let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
-            
+
             self.integrated_rain_vel += rain_vel.magnitude() * dt;
             let rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);