mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
better sim
This commit is contained in:
parent
0c804af773
commit
9c84a20cef
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user