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, Instant), - new: (Grid, Instant), - current: Grid, + old: (WeatherGrid, Instant), + new: (WeatherGrid, Instant), } impl WeatherLerp { - fn weather_update(&mut self, weather: Grid) { + 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 { &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) -> 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_::(); - 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> { 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), + 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::::lerp(from.wind, to.wind, t), } } + + // Get the rain direction for this weather + pub fn rain_dir(&self) -> Vec3 { + // 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, +} + +impl WeatherGrid { + pub fn new(size: Vec2) -> Self { + Self { + weather: Grid::new(size.as_(), Weather::default()), + } + } + + pub fn iter(&self) -> impl Iterator, &Weather)> { self.weather.iter() } + + pub fn iter_mut(&mut self) -> impl Iterator, &mut Weather)> { + self.weather.iter_mut() + } + + pub fn size(&self) -> Vec2 { 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) -> 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_::(); + 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 { self.ecs.read_resource() } + /// Get a reference the current in-game weather grid. + pub fn weather_grid(&self) -> Fetch { self.ecs.read_resource() } + + /// Get a mutable reference the current in-game weather grid. + pub fn weather_grid_mut(&mut self) -> FetchMut { self.ecs.write_resource() } + + /// Get the current weather at a position. + pub fn weather_at(&self, pos: Vec2) -> 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::().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::::every(Duration::from_secs_f32( - sim::DT, + WEATHER_DT, ))); state .ecs_mut() .insert(SysScheduler::::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, // The variables used for simulation consts: Grid, // The constants from the world used for simulation - weather: Grid, // The current weather. info: Grid, } -/* -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]) -> Option> { 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 { &self.weather } - /* fn get_cell(&self, p: Vec2, 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_::() + time as f64 * 0.1; @@ -391,4 +379,6 @@ impl WeatherSim { } */ } + + pub fn size(&self) -> Vec2 { 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>, 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, (sim, mut scheduler, clients): Self::SystemData) { + fn run( + _job: &mut common_ecs::Job, + (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>, ); @@ -22,10 +23,13 @@ impl<'a> System<'a> for Sys { fn run( _job: &mut common_ecs::Job, - (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_::(); 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, - d_dir: math::Vec3| + d_dir: math::Vec3, + volume: &Vec>| -> (Mat4, Mat4) { // 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 Terrain { focus_pos: Vec3, loaded_distance: f32, camera: &Camera, - ) -> (Aabb, Vec>, math::Aabr) { + ) -> ( + // TODO: Better return type? + Aabb, + Vec>, + math::Aabr, + Vec>, + ) { let camera::Dependents { view_mat, proj_mat_treeculler, @@ -1395,11 +1401,56 @@ impl Terrain { }) }; 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:: { + 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_::()), + max: math::Vec3::from(visible_bounding_box.max.as_::()), + }; + // TODO: move out shared calculations + let inv_proj_view = + math::Mat4::from_col_arrays((proj_mat_treeculler * view_mat).into_col_arrays()) + .as_::() + .inverted(); + let ray_direction = math::Vec3::::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_::(), + visible_bounds_fine, + 1e-6, + ) + .map(|v| v.as_::()) + .collect::>(); + + 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 Terrain { .for_each(|(model, locals)| drawer.draw(model, locals)); } + pub fn render_occlusion<'a>( + &'a self, + drawer: &mut TerrainShadowDrawer<'_, 'a>, + focus_pos: Vec3, + ) { + 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,