mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Server weather sim
This commit is contained in:
parent
eb0b5998ab
commit
b7c0196129
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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[];
|
||||
};
|
||||
|
||||
|
@ -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(())
|
||||
|
@ -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
|
||||
|
@ -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
34
common/src/weather.rs
Normal 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,
|
||||
}
|
@ -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
|
||||
|
@ -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
34
server/src/weather/mod.rs
Normal 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
296
server/src/weather/sim.rs
Normal 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 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;
|
||||
});
|
||||
}
|
||||
}
|
36
server/src/weather/sync.rs
Normal file
36
server/src/weather/sync.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
server/src/weather/tick.rs
Normal file
34
server/src/weather/tick.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
},
|
||||
]);
|
||||
|
@ -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(), */
|
||||
|
@ -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(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user