Server weather sim

This commit is contained in:
IsseW 2021-11-17 18:09:51 +01:00
parent eb0b5998ab
commit b7c0196129
18 changed files with 579 additions and 16 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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[];
};

View File

@ -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(())

View File

@ -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

View File

@ -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;

34
common/src/weather.rs Normal file
View File

@ -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,
}

View File

@ -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

View File

@ -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,
);

34
server/src/weather/mod.rs Normal file
View File

@ -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,
)));
}

296
server/src/weather/sim.rs Normal file
View File

@ -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 cells 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) = (100m) / 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;
});
}
}

View File

@ -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));
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -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,
}
}
}

View File

@ -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),
},
]
}

View File

@ -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(),
},
]);

View File

@ -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(), */

View File

@ -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(),
);
},
}
}