From 761953513b30c2674b8566167c3b789a521c33fd Mon Sep 17 00:00:00 2001 From: laundmo Date: Thu, 8 Jun 2023 17:28:07 +0200 Subject: [PATCH 01/22] Initial work for gliding lift, updraft, thermals --- .cargo/config | 3 + common/src/terrain/mod.rs | 26 ++++++ common/systems/src/phys.rs | 168 +++++++++++++++++++++++++++++++++++-- voxygen/egui/src/lib.rs | 3 +- world/src/lib.rs | 19 ++++- 5 files changed, 208 insertions(+), 11 deletions(-) diff --git a/.cargo/config b/.cargo/config index 87d684cbbf..7409c9c2cf 100644 --- a/.cargo/config +++ b/.cargo/config @@ -32,3 +32,6 @@ dbg-voxygen = "run --bin veloren-voxygen --profile debuginfo" swarm = "run --bin swarm --features client/bin_bot,client/tick_network --" ci-clippy = "clippy --all-targets --locked --features=bin_cmd_doc_gen,bin_compression,bin_csv,bin_graphviz,bin_bot,bin_asset_migrate,asset_tweak,bin,stat" ci-clippy2 = "clippy -p veloren-voxygen --locked --no-default-features --features=default-publish" + +[target.x86_64-pc-windows-msvc] +linker = "rust-lld.exe" diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 5e6e0fd8e6..3c9f57dfcf 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -141,6 +141,10 @@ pub struct TerrainChunkMeta { contains_cave: bool, contains_river: bool, river_velocity: Vec3, + downhill_chunk: Option>, + gradient: Option, + rockiness: f32, + cliff_height: f32, temp: f32, humidity: f32, site: Option, @@ -162,6 +166,10 @@ impl TerrainChunkMeta { temp: f32, humidity: f32, site: Option, + downhill_chunk: Option>, + gradient: Option, + rockiness: f32, + cliff_height: f32, ) -> Self { Self { name, @@ -178,6 +186,10 @@ impl TerrainChunkMeta { debug_points: Vec::new(), debug_lines: Vec::new(), sprite_cfgs: HashMap::default(), + downhill_chunk, + rockiness, + cliff_height, + gradient, } } @@ -197,6 +209,10 @@ impl TerrainChunkMeta { debug_points: Vec::new(), debug_lines: Vec::new(), sprite_cfgs: HashMap::default(), + downhill_chunk: None, + gradient: None, + rockiness: 0.0, + cliff_height: 0.0, } } @@ -204,6 +220,7 @@ impl TerrainChunkMeta { pub fn biome(&self) -> BiomeKind { self.biome } + /// Altitude in blocks pub fn alt(&self) -> f32 { self.alt } pub fn tree_density(&self) -> f32 { self.tree_density } @@ -216,6 +233,7 @@ impl TerrainChunkMeta { pub fn site(&self) -> Option { self.site } + /// Temperature from 0 to 1 (possibly -1 to 1) pub fn temp(&self) -> f32 { self.temp } pub fn humidity(&self) -> f32 { self.humidity } @@ -239,6 +257,14 @@ impl TerrainChunkMeta { pub fn set_sprite_cfg_at(&mut self, rpos: Vec3, sprite_cfg: SpriteCfg) { self.sprite_cfgs.insert(rpos, sprite_cfg); } + + pub fn downhill_chunk(&self) -> Option> { self.downhill_chunk } + + pub fn gradient(&self) -> Option { self.gradient } + + pub fn rockiness(&self) -> f32 { self.rockiness } + + pub fn cliff_height(&self) -> f32 { self.cliff_height } } // Terrain type aliases diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 89377b3f6a..975e153fb9 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -12,22 +12,25 @@ use common::{ link::Is, mounting::{Rider, VolumeRider}, outcome::Outcome, - resources::{DeltaTime, GameMode}, + resources::{DeltaTime, GameMode, TimeOfDay}, states, - terrain::{Block, BlockKind, TerrainGrid}, + terrain::{Block, BlockKind, CoordinateConversions, TerrainGrid, NEIGHBOR_DELTA}, + time::DayPeriod, uid::Uid, util::{Projection, SpatialGrid}, vol::{BaseVol, ReadVol}, + weather::WeatherGrid, }; use common_base::{prof_span, span}; use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System}; +use itertools::Itertools; use rayon::iter::ParallelIterator; use specs::{ shred, Entities, Entity, Join, LendJoin, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect, WriteStorage, }; use std::ops::Range; -use vek::*; +use vek::{num_traits::Signed, *}; /// The density of the fluid as a function of submersion ratio in given fluid /// where it is assumed that any unsubmersed part is is air. @@ -150,11 +153,12 @@ pub struct PhysicsRead<'a> { is_ridings: ReadStorage<'a, Is>, is_volume_ridings: ReadStorage<'a, Is>, projectiles: ReadStorage<'a, Projectile>, - char_states: ReadStorage<'a, CharacterState>, - bodies: ReadStorage<'a, Body>, character_states: ReadStorage<'a, CharacterState>, + bodies: ReadStorage<'a, Body>, densities: ReadStorage<'a, Density>, stats: ReadStorage<'a, Stats>, + weather: Option>, + time_of_day: Read<'a, TimeOfDay>, } #[derive(SystemData)] @@ -236,7 +240,7 @@ impl<'a> PhysicsData<'a> { &mut self.write.previous_phys_cache, &self.read.colliders, self.read.scales.maybe(), - self.read.char_states.maybe(), + self.read.character_states.maybe(), ) .join() { @@ -369,7 +373,7 @@ impl<'a> PhysicsData<'a> { // moving whether it should interact into the collider component // or into a separate component. read.projectiles.maybe(), - read.char_states.maybe(), + read.character_states.maybe(), ) .par_join() .map_init( @@ -438,7 +442,7 @@ impl<'a> PhysicsData<'a> { previous_cache, mass, collider, - read.char_states.get(entity), + read.character_states.get(entity), read.is_ridings.get(entity), )) }) @@ -601,6 +605,154 @@ impl<'a> PhysicsData<'a> { ref mut write, } = self; + prof_span!(guard, "Apply Weather"); + if let Some(weather) = &read.weather { + let day_period: DayPeriod = read.time_of_day.0.into(); + for (_, state, pos, phys) in ( + &read.entities, + &read.character_states, + &write.positions, + &mut write.physics_states, + ) + .join() + { + if !state.is_glide() { + continue; + } + prof_span!(guard, "Apply Weather INIT"); + let chunk_pos: Vec2 = pos.0.xy().wpos_to_cpos().as_(); + let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { continue; }; + + let meta = current_chunk.meta(); + let interp_weather = weather.get_interpolated(pos.0.xy()); + // TODO: use tree density to scale down surface wind + // TODO: use temperature to estimate updraft + // TODO: use slope to estimate updraft (wind direction goes up along slope) + // TODO: use biome to refine + + // Weather sim wind + let above_ground = pos.0.z - meta.alt(); + let wind_velocity = interp_weather.wind_vel(); + + let surrounding_chunks_metas = { + NEIGHBOR_DELTA + .iter() + .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) + .filter(|pos| read.terrain.contains_key(*pos)) + .filter_map(|cpos| read.terrain.get_key(cpos).map(|c| c.meta())) + }; + + drop(guard); + + prof_span!(guard, "Apply Weather THERMALS"); + + // === THERMALS === + let mut lift = dbg!(dbg!(meta.temp()) * 4.0); + + let temperatures = surrounding_chunks_metas.clone().map(|m| m.temp()).minmax(); + + // more thermals if hot chunks border cold chunks + lift *= match temperatures { + itertools::MinMaxResult::NoElements + | itertools::MinMaxResult::OneElement(_) => 1.0, + itertools::MinMaxResult::MinMax(a, b) => 0.8 + ((a - b).abs() * 1.1), + } + .clamp(0.1, 2.0); + + // way less thermals at after/night - long term heat. + lift *= match day_period { + DayPeriod::Night => 0.0, + DayPeriod::Morning => 0.9, + DayPeriod::Noon => 1.8, + DayPeriod::Evening => 1.2, + }; + + // way more thermals in strong rain (its often caused by strong thermals). less + // in weak rain or cloudy.. + lift *= if interp_weather.rain.is_between(0.5, 1.0) + && interp_weather.cloud.is_between(0.6, 1.0) + { + 1.5 + } else if interp_weather.rain.is_between(0.0, 0.5) + && interp_weather.cloud.is_between(0.01, 1.0) + { + 0.1 + } else { + 1.0 + }; + lift *= (above_ground / 60.).max(1.); // the first 60 blocks are weaker. starting from the ground should be difficult. + + // biome thermals modifiers + use common::terrain::BiomeKind::*; + lift *= match meta.biome() { + Void => 0.0, + Lake => 2.0, + Ocean => 1.5, + Swamp => 0.8, // swamps are usually not hot + Taiga | Grassland | Savannah => 1.0, + Mountain => 0.8, + Snowland => 0.9, + Desert => 0.2, // deserts are too uniformly hot for nice thermal columns + Jungle | Forest => 0.5, // trees cool the air by evaporation + }; + drop(guard); + + prof_span!(guard, "Apply Weather RIDGE_WAVE"); + + // === Ridge/Wave lift === + + let mut ridge_lift = meta + .downhill_chunk() + .map(|cpos| { + let upwards: Vec2 = -(chunk_pos.cpos_to_wpos_center().as_() + - cpos.cpos_to_wpos_center().as_()); + match read.terrain.get_key(cpos).map(|c| c.meta()) { + Some(other_meta) => { + let wind = dbg!( + (upwards + wind_velocity) + .clamped(Vec2::zero(), Vec2::broadcast(1.0)) + ); + let vertical_distance = + ((meta.alt() - other_meta.alt()) / 20.0).clamp(0.0, 14.0); // just guessing, 50 blocks seems like a decent max + if wind.magnitude_squared().is_positive() { + 0.5 + dbg!(wind.magnitude()).clamp(0.0, 1.0) + + dbg!(vertical_distance) + } else { + 1.0 + } + }, + None => 0.0, + } + }) + .unwrap_or(0.0); + dbg!(ridge_lift); + + // Cliffs mean more lift + ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + + // trees mean less lift + ridge_lift *= (1.0 - meta.tree_density()).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons + drop(guard); + // see: https://en.wikipedia.org/wiki/Lift_(soaring) + // ridge/wave lift: + // - along the ridge of a mountain if wind is blowing against and up it + // - around the tops of large mountains + // convergence zones: + // - where hot and cold meet. + // - could use biomes/temp difference between chunks to model this + let wind_vel = (wind_velocity, dbg!(lift) + dbg!(ridge_lift)).into(); + phys.in_fluid = phys.in_fluid.map(|f| match f { + Fluid::Air { elevation, .. } => Fluid::Air { + vel: Vel(wind_vel), + elevation, + }, + fluid => fluid, + }); + } + } + + drop(guard); + prof_span!(guard, "insert PosVelOriDefer"); // NOTE: keep in sync with join below ( diff --git a/voxygen/egui/src/lib.rs b/voxygen/egui/src/lib.rs index 9ac3d7642b..5a8a279e38 100644 --- a/voxygen/egui/src/lib.rs +++ b/voxygen/egui/src/lib.rs @@ -754,8 +754,7 @@ fn selected_entity_window( two_col_row(ui, "On Wall", physics_state.on_wall.map_or("-".to_owned(), |x| format!("{:.1},{:.1},{:.1}", x.x, x.y, x.z ))); two_col_row(ui, "Touching Entities", physics_state.touch_entities.len().to_string()); two_col_row(ui, "In Fluid", match physics_state.in_fluid { - - Some(Fluid::Air { elevation, .. }) => format!("Air (Elevation: {:.1})", elevation), + Some(Fluid::Air { elevation, vel, .. }) => format!("Air (Elevation: {:.1}), vel: ({:.1},{:.1},{:.1}) ({:.1} u/s)", elevation, vel.0.x, vel.0.y, vel.0.z, vel.0.magnitude()), Some(Fluid::Liquid { depth, kind, .. }) => format!("{:?} (Depth: {:.1})", kind, depth), _ => "None".to_owned() }); }); diff --git a/world/src/lib.rs b/world/src/lib.rs index 95a7fb9b3f..f38fa19fe4 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -390,7 +390,24 @@ impl World { .get_origin() .distance_squared(chunk_center_wpos2d) }) - .map(|id| index.sites[*id].kind.convert_to_meta().unwrap_or_default()), + .map(|id| index.sites[*id].kind.convert_to_meta().unwrap_or_default()) + .or_else(|| sim_chunk.poi.map(|poi| self.civs.pois[poi].name.clone())), + sim_chunk.get_biome(), + sim_chunk.alt, + sim_chunk.tree_density, + sim_chunk.cave.1.alt != 0.0, + sim_chunk.river.is_river(), + sim_chunk.river.velocity, + sim_chunk.temp, + sim_chunk.humidity, + sim_chunk + .sites + .iter() + .find_map(|site| index.sites[*site].kind.convert_to_meta()), + sim_chunk.downhill.map(|e| e.wpos_to_cpos()), + self.sim.get_gradient_approx(chunk_pos), + sim_chunk.rockiness, + sim_chunk.cliff_height, ); let mut chunk = TerrainChunk::new(base_z, stone, air, meta); From 4c97f3f62432dd4b525eaef47d9a25b81e74fdf4 Mon Sep 17 00:00:00 2001 From: laundmo Date: Sat, 10 Jun 2023 01:33:27 +0200 Subject: [PATCH 02/22] use formula for time of day dependency of thermals --- common/systems/src/phys.rs | 41 +++++++++++++++----------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 975e153fb9..76ed84fd8c 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -607,7 +607,11 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "Apply Weather"); if let Some(weather) = &read.weather { - let day_period: DayPeriod = read.time_of_day.0.into(); + // 0.0..1.0, 0.25 morning, 0.45 midday, 0.66 evening, 0.79 night, 0.0/1.0 + // midnight + const SECONDS_PER_DAY: f64 = 60.0 * 60.0 * 24.0; + let fraction_of_day: f32 = + (read.time_of_day.0.rem_euclid(SECONDS_PER_DAY) / SECONDS_PER_DAY) as f32; for (_, state, pos, phys) in ( &read.entities, &read.character_states, @@ -625,31 +629,25 @@ impl<'a> PhysicsData<'a> { let meta = current_chunk.meta(); let interp_weather = weather.get_interpolated(pos.0.xy()); - // TODO: use tree density to scale down surface wind - // TODO: use temperature to estimate updraft - // TODO: use slope to estimate updraft (wind direction goes up along slope) - // TODO: use biome to refine - // Weather sim wind let above_ground = pos.0.z - meta.alt(); let wind_velocity = interp_weather.wind_vel(); - let surrounding_chunks_metas = { - NEIGHBOR_DELTA - .iter() - .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) - .filter(|pos| read.terrain.contains_key(*pos)) - .filter_map(|cpos| read.terrain.get_key(cpos).map(|c| c.meta())) - }; + let surrounding_chunks_metas = NEIGHBOR_DELTA + .iter() + .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) + .filter(|pos| read.terrain.contains_key(*pos)) + .filter_map(|cpos| read.terrain.get_key(cpos).map(|c| c.meta())) + .collect::>(); drop(guard); prof_span!(guard, "Apply Weather THERMALS"); // === THERMALS === - let mut lift = dbg!(dbg!(meta.temp()) * 4.0); + let mut lift = meta.temp() * 5.0; - let temperatures = surrounding_chunks_metas.clone().map(|m| m.temp()).minmax(); + let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); // more thermals if hot chunks border cold chunks lift *= match temperatures { @@ -659,13 +657,8 @@ impl<'a> PhysicsData<'a> { } .clamp(0.1, 2.0); - // way less thermals at after/night - long term heat. - lift *= match day_period { - DayPeriod::Night => 0.0, - DayPeriod::Morning => 0.9, - DayPeriod::Noon => 1.8, - DayPeriod::Evening => 1.2, - }; + // Estimate warmth over day based on time of day. https://www.desmos.com/calculator/kw0ba0i7mf + lift *= f32::exp(-((fraction_of_day - 0.5).powf(6.0)) / 0.0001); // way more thermals in strong rain (its often caused by strong thermals). less // in weak rain or cloudy.. @@ -686,9 +679,7 @@ impl<'a> PhysicsData<'a> { use common::terrain::BiomeKind::*; lift *= match meta.biome() { Void => 0.0, - Lake => 2.0, - Ocean => 1.5, - Swamp => 0.8, // swamps are usually not hot + Lake | Ocean | Swamp => 0.1, Taiga | Grassland | Savannah => 1.0, Mountain => 0.8, Snowland => 0.9, From 59bf62a4533be0084ac3467fa0856bbbbfa2e7aa Mon Sep 17 00:00:00 2001 From: laundmo Date: Sat, 10 Jun 2023 01:50:55 +0200 Subject: [PATCH 03/22] Add approximate chunk terrain normals into sim --- common/src/terrain/mod.rs | 18 +++++-------- common/systems/src/phys.rs | 54 ++++++++++++++++++++------------------ world/src/lib.rs | 3 +-- world/src/sim/mod.rs | 32 ++++++++++++++++++++++ 4 files changed, 69 insertions(+), 38 deletions(-) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 3c9f57dfcf..be027ebd35 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -141,8 +141,7 @@ pub struct TerrainChunkMeta { contains_cave: bool, contains_river: bool, river_velocity: Vec3, - downhill_chunk: Option>, - gradient: Option, + approx_chunk_terrain_normal: Option>, rockiness: f32, cliff_height: f32, temp: f32, @@ -166,8 +165,7 @@ impl TerrainChunkMeta { temp: f32, humidity: f32, site: Option, - downhill_chunk: Option>, - gradient: Option, + approx_chunk_terrain_normal: Option>, rockiness: f32, cliff_height: f32, ) -> Self { @@ -186,10 +184,9 @@ impl TerrainChunkMeta { debug_points: Vec::new(), debug_lines: Vec::new(), sprite_cfgs: HashMap::default(), - downhill_chunk, + approx_chunk_terrain_normal, rockiness, cliff_height, - gradient, } } @@ -209,8 +206,7 @@ impl TerrainChunkMeta { debug_points: Vec::new(), debug_lines: Vec::new(), sprite_cfgs: HashMap::default(), - downhill_chunk: None, - gradient: None, + approx_chunk_terrain_normal: None, rockiness: 0.0, cliff_height: 0.0, } @@ -258,9 +254,9 @@ impl TerrainChunkMeta { self.sprite_cfgs.insert(rpos, sprite_cfg); } - pub fn downhill_chunk(&self) -> Option> { self.downhill_chunk } - - pub fn gradient(&self) -> Option { self.gradient } + pub fn approx_chunk_terrain_normal(&self) -> Option> { + self.approx_chunk_terrain_normal + } pub fn rockiness(&self) -> f32 { self.rockiness } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 76ed84fd8c..a89af9beb0 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -692,31 +692,35 @@ impl<'a> PhysicsData<'a> { // === Ridge/Wave lift === - let mut ridge_lift = meta - .downhill_chunk() - .map(|cpos| { - let upwards: Vec2 = -(chunk_pos.cpos_to_wpos_center().as_() - - cpos.cpos_to_wpos_center().as_()); - match read.terrain.get_key(cpos).map(|c| c.meta()) { - Some(other_meta) => { - let wind = dbg!( - (upwards + wind_velocity) - .clamped(Vec2::zero(), Vec2::broadcast(1.0)) - ); - let vertical_distance = - ((meta.alt() - other_meta.alt()) / 20.0).clamp(0.0, 14.0); // just guessing, 50 blocks seems like a decent max - if wind.magnitude_squared().is_positive() { - 0.5 + dbg!(wind.magnitude()).clamp(0.0, 1.0) - + dbg!(vertical_distance) - } else { - 1.0 - } - }, - None => 0.0, - } - }) - .unwrap_or(0.0); - dbg!(ridge_lift); + // TODO: reimplement with chunk terrain normals + let mut ridge_lift = 1.; + + // let mut ridge_lift = meta + // .downhill_chunk() + // .map(|cpos| { + // let upwards: Vec2 = -(chunk_pos.cpos_to_wpos_center().as_() + // - cpos.cpos_to_wpos_center().as_()); + // match read.terrain.get_key(cpos).map(|c| c.meta()) { + // Some(other_meta) => { + // let wind = dbg!( + // (upwards + wind_velocity) + // .clamped(Vec2::zero(), Vec2::broadcast(1.0)) + // ); + // let vertical_distance = + // ((meta.alt() - other_meta.alt()) / 20.0).clamp(0.0, + // 14.0); // just guessing, 50 blocks seems like a decent max + // if wind.magnitude_squared().is_positive() { + // 0.5 + dbg!(wind.magnitude()).clamp(0.0, 1.0) + // + dbg!(vertical_distance) + // } else { + // 1.0 + // } + // }, + // None => 0.0, + // } + // }) + // .unwrap_or(0.0); + // dbg!(ridge_lift); // Cliffs mean more lift ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs diff --git a/world/src/lib.rs b/world/src/lib.rs index f38fa19fe4..f786105d05 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -404,8 +404,7 @@ impl World { .sites .iter() .find_map(|site| index.sites[*site].kind.convert_to_meta()), - sim_chunk.downhill.map(|e| e.wpos_to_cpos()), - self.sim.get_gradient_approx(chunk_pos), + self.sim.approx_chunk_terrain_normal(chunk_pos), sim_chunk.rockiness, sim_chunk.cliff_height, ); diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index d8bdc52a12..afdfa67898 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1636,6 +1636,38 @@ impl WorldSim { TerrainChunk::water(CONFIG.sea_level as i32) } + pub fn approx_chunk_terrain_normal(&self, chunk_pos: Vec2) -> Option> { + let curr_chunk = self.get(chunk_pos)?; + let downhill_chunk_pos = curr_chunk.downhill?.wpos_to_cpos(); + let downhill_chunk = self.get(downhill_chunk_pos)?; + // special case if chunks are flat + if (curr_chunk.alt - downhill_chunk.alt) == 0. { + return Some(Vec3::unit_z()); + } + + let calc_zero = chunk_pos + .cpos_to_wpos_center() + .as_() + .with_z(downhill_chunk.alt); + let downhill = calc_zero + - Vec3::::from(( + downhill_chunk_pos.cpos_to_wpos_center().as_(), + downhill_chunk.alt, + )); + let curr = Vec3::zero().with_z(curr_chunk.alt); + let flat_dist = downhill.magnitude(); + + let f = curr.z.powi(2) / flat_dist; + let neg_normal = Vec3::new( + (-downhill.x / flat_dist) * f, + (-downhill.y / flat_dist) * f, + 0., + ); + let normal = neg_normal - curr; + + Some(normal.normalized()) + } + /// Draw a map of the world based on chunk information. Returns a buffer of /// u32s. pub fn get_map(&self, index: IndexRef, calendar: Option<&Calendar>) -> WorldMapMsg { From 91c718b93126d684d66a02265c32060e09c139e0 Mon Sep 17 00:00:00 2001 From: laundmo Date: Wed, 14 Jun 2023 17:55:42 +0200 Subject: [PATCH 04/22] easier chunk normal using double cross product --- world/src/sim/mod.rs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index afdfa67898..ff2acb27b9 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1644,28 +1644,16 @@ impl WorldSim { if (curr_chunk.alt - downhill_chunk.alt) == 0. { return Some(Vec3::unit_z()); } - - let calc_zero = chunk_pos + let curr = chunk_pos.cpos_to_wpos_center().as_().with_z(curr_chunk.alt); + let down = downhill_chunk_pos .cpos_to_wpos_center() .as_() .with_z(downhill_chunk.alt); - let downhill = calc_zero - - Vec3::::from(( - downhill_chunk_pos.cpos_to_wpos_center().as_(), - downhill_chunk.alt, - )); - let curr = Vec3::zero().with_z(curr_chunk.alt); - let flat_dist = downhill.magnitude(); - - let f = curr.z.powi(2) / flat_dist; - let neg_normal = Vec3::new( - (-downhill.x / flat_dist) * f, - (-downhill.y / flat_dist) * f, - 0., - ); - let normal = neg_normal - curr; - - Some(normal.normalized()) + let downwards = curr - down; + let flat = downwards.with_z(down.z); + let mut res = downwards.cross(flat).cross(downwards); + res.normalize(); + Some(res) } /// Draw a map of the world based on chunk information. Returns a buffer of From 432be97a974f671db3894972f0d975f2aabe35af Mon Sep 17 00:00:00 2001 From: laundmo Date: Sat, 17 Jun 2023 17:30:05 +0200 Subject: [PATCH 05/22] copy interpolation function to terrain --- common/src/terrain/mod.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index be027ebd35..91220837a4 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -6,6 +6,8 @@ pub mod site; pub mod sprite; pub mod structure; +use std::ops::{Add, Mul}; + // Reexports pub use self::{ biome::BiomeKind, @@ -301,6 +303,39 @@ impl TerrainGrid { && self.is_space(*pos) }) } + + pub fn get_interpolated(&self, pos: Vec2, mut f: F) -> Option + where + T: Copy + Default + Add + Mul, + F: FnMut(&TerrainChunk) -> T, + { + let pos = pos.as_::().wpos_to_cpos(); + + let cubic = |a: T, b: T, c: T, d: T, x: f32| -> T { + let x2 = x * x; + + // Catmull-Rom splines + let co0 = a * -0.5 + b * 1.5 + c * -1.5 + d * 0.5; + let co1 = a + b * -2.5 + c * 2.0 + d * -0.5; + let co2 = a * -0.5 + c * 0.5; + let co3 = b; + + co0 * x2 * x + co1 * x2 + co2 * x + co3 + }; + + let mut x = [T::default(); 4]; + + for (x_idx, j) in (-1..3).enumerate() { + let y0 = f(self.get_key(pos.map2(Vec2::new(j, -1), |e, q| e.max(0.0) as i32 + q))?); + let y1 = f(self.get_key(pos.map2(Vec2::new(j, 0), |e, q| e.max(0.0) as i32 + q))?); + let y2 = f(self.get_key(pos.map2(Vec2::new(j, 1), |e, q| e.max(0.0) as i32 + q))?); + let y3 = f(self.get_key(pos.map2(Vec2::new(j, 2), |e, q| e.max(0.0) as i32 + q))?); + + x[x_idx] = cubic(y0, y1, y2, y3, pos.y.fract() as f32); + } + + Some(cubic(x[0], x[1], x[2], x[3], pos.x.fract() as f32)) + } } impl TerrainChunk { From fbd2a09c66bb2c39e7d143217eba90a76ad5b094 Mon Sep 17 00:00:00 2001 From: laundmo Date: Sat, 17 Jun 2023 17:30:47 +0200 Subject: [PATCH 06/22] massively improve ridge lift calculation --- common/systems/src/phys.rs | 68 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index a89af9beb0..e7f5afb68f 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -29,7 +29,7 @@ use specs::{ shred, Entities, Entity, Join, LendJoin, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect, WriteStorage, }; -use std::ops::Range; +use std::{f32::consts::PI, ops::Range}; use vek::{num_traits::Signed, *}; /// The density of the fluid as a function of submersion ratio in given fluid @@ -655,7 +655,7 @@ impl<'a> PhysicsData<'a> { | itertools::MinMaxResult::OneElement(_) => 1.0, itertools::MinMaxResult::MinMax(a, b) => 0.8 + ((a - b).abs() * 1.1), } - .clamp(0.1, 2.0); + .min(2.0); // Estimate warmth over day based on time of day. https://www.desmos.com/calculator/kw0ba0i7mf lift *= f32::exp(-((fraction_of_day - 0.5).powf(6.0)) / 0.0001); @@ -673,7 +673,7 @@ impl<'a> PhysicsData<'a> { } else { 1.0 }; - lift *= (above_ground / 60.).max(1.); // the first 60 blocks are weaker. starting from the ground should be difficult. + lift *= (above_ground / 60.).min(1.); // the first 60 blocks are weaker. starting from the ground should be difficult. // biome thermals modifiers use common::terrain::BiomeKind::*; @@ -692,41 +692,41 @@ impl<'a> PhysicsData<'a> { // === Ridge/Wave lift === - // TODO: reimplement with chunk terrain normals - let mut ridge_lift = 1.; + // WORKING! + // TODO: average out over 4 surrounding chunks to make chunk borders less harsh. + // theres sometimes a noticeable bump while gliding whereas it should feel + // smooth. + let mut ridge_wind = { + let normal = read + .terrain + .get_interpolated(pos.0.as_().xy(), |c| { + c.meta() + .approx_chunk_terrain_normal() + .unwrap_or(Vec3::unit_z()) + }) + .unwrap_or(Vec3::unit_z()); + // the the line along the surface of the chunk (perpendicular to normal) + let mut up = normal.cross(normal.with_z(0.)).cross(normal) * -1.; + up.normalize(); + // a bit further up than just flat along the surface + up = up.with_z(up.z + 0.2); - // let mut ridge_lift = meta - // .downhill_chunk() - // .map(|cpos| { - // let upwards: Vec2 = -(chunk_pos.cpos_to_wpos_center().as_() - // - cpos.cpos_to_wpos_center().as_()); - // match read.terrain.get_key(cpos).map(|c| c.meta()) { - // Some(other_meta) => { - // let wind = dbg!( - // (upwards + wind_velocity) - // .clamped(Vec2::zero(), Vec2::broadcast(1.0)) - // ); - // let vertical_distance = - // ((meta.alt() - other_meta.alt()) / 20.0).clamp(0.0, - // 14.0); // just guessing, 50 blocks seems like a decent max - // if wind.magnitude_squared().is_positive() { - // 0.5 + dbg!(wind.magnitude()).clamp(0.0, 1.0) - // + dbg!(vertical_distance) - // } else { - // 1.0 - // } - // }, - // None => 0.0, - // } - // }) - // .unwrap_or(0.0); - // dbg!(ridge_lift); + // angle between normal and wind + let mut angle = wind_velocity.angle_between(normal.xy()); // 1.4 radians of zero + // a deadzone of +-1.5 radians if wind is blowing away from + // the mountainside. + angle = (angle - 1.3).max(0.0); + // multiply the upwards vector by angle of wind incidence. less incidence means + // less lift. This feels better ingame than actually + // calculating how the wind would be redirected. + (up * angle) * wind_velocity.magnitude_squared() * 7. + }; // Cliffs mean more lift - ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + ridge_wind *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs // trees mean less lift - ridge_lift *= (1.0 - meta.tree_density()).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons + ridge_wind *= (1.0 - meta.tree_density()).min(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons drop(guard); // see: https://en.wikipedia.org/wiki/Lift_(soaring) // ridge/wave lift: @@ -735,7 +735,7 @@ impl<'a> PhysicsData<'a> { // convergence zones: // - where hot and cold meet. // - could use biomes/temp difference between chunks to model this - let wind_vel = (wind_velocity, dbg!(lift) + dbg!(ridge_lift)).into(); + let wind_vel = ridge_wind.with_z(ridge_wind.z + lift); phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { vel: Vel(wind_vel), From 4e3743018958fdab911de8295a14f30ce7407023 Mon Sep 17 00:00:00 2001 From: laundmo Date: Sat, 17 Jun 2023 18:10:57 +0200 Subject: [PATCH 07/22] draw debug vectors, and toggle them in egui --- client/src/lib.rs | 3 ++ voxygen/egui/src/lib.rs | 11 ++++++ voxygen/src/scene/debug.rs | 9 +++-- voxygen/src/scene/mod.rs | 79 +++++++++++++++++++++++++++++++++++++- voxygen/src/session/mod.rs | 11 ++++++ voxygen/src/ui/egui/mod.rs | 3 ++ 6 files changed, 110 insertions(+), 6 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 112a04c2db..fbc95e1905 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -311,6 +311,8 @@ pub struct Client { flashing_lights_enabled: bool, + pub debug_vectors_enabled: bool, + /// Terrrain view distance server_view_distance_limit: Option, view_distance: Option, @@ -987,6 +989,7 @@ impl Client { dt_adjustment: 1.0, connected_server_constants: server_constants, + debug_vectors_enabled: false, }) } diff --git a/voxygen/egui/src/lib.rs b/voxygen/egui/src/lib.rs index 5a8a279e38..330d1c2647 100644 --- a/voxygen/egui/src/lib.rs +++ b/voxygen/egui/src/lib.rs @@ -96,6 +96,7 @@ pub struct EguiInnerState { selected_entity_cylinder_height: f32, frame_times: Vec, windows: EguiWindows, + debug_vectors_enabled: bool, } #[derive(Clone, Default)] @@ -118,6 +119,7 @@ impl Default for EguiInnerState { selected_entity_cylinder_height: 10.0, frame_times: Vec::new(), windows: EguiWindows::default(), + debug_vectors_enabled: false, } } } @@ -142,6 +144,7 @@ pub enum EguiAction { }, DebugShape(EguiDebugShapeAction), SetExperimentalShader(String, bool), + SetShowDebugVector(bool), } #[derive(Default)] @@ -225,6 +228,7 @@ pub fn maintain_egui_inner( let mut max_entity_distance = egui_state.max_entity_distance; let mut selected_entity_cylinder_height = egui_state.selected_entity_cylinder_height; let mut windows = egui_state.windows.clone(); + let mut debug_vectors_enabled_mut = egui_state.debug_vectors_enabled; // If a debug cylinder was added in the last frame, store it against the // selected entity @@ -261,6 +265,7 @@ pub fn maintain_egui_inner( ui.checkbox(&mut windows.ecs_entities, "ECS Entities"); ui.checkbox(&mut windows.frame_time, "Frame Time"); ui.checkbox(&mut windows.experimental_shaders, "Experimental Shaders"); + ui.checkbox(&mut debug_vectors_enabled_mut, "Show Debug Vectors"); }); }); @@ -510,6 +515,12 @@ pub fn maintain_egui_inner( } } }; + if debug_vectors_enabled_mut != egui_state.debug_vectors_enabled { + egui_actions + .actions + .push(EguiAction::SetShowDebugVector(debug_vectors_enabled_mut)); + egui_state.debug_vectors_enabled = debug_vectors_enabled_mut; + } egui_state.max_entity_distance = max_entity_distance; egui_state.selected_entity_cylinder_height = selected_entity_cylinder_height; diff --git a/voxygen/src/scene/debug.rs b/voxygen/src/scene/debug.rs index bfb94015bb..412ef5e48b 100644 --- a/voxygen/src/scene/debug.rs +++ b/voxygen/src/scene/debug.rs @@ -9,7 +9,8 @@ use vek::*; #[derive(Debug, PartialEq)] pub enum DebugShape { - Line([Vec3; 2]), + /// [Start, End], width + Line([Vec3; 2], f32), Cylinder { radius: f32, height: f32, @@ -96,13 +97,13 @@ impl DebugShape { }; match self { - DebugShape::Line([a, b]) => { + DebugShape::Line([a, b], width) => { //let h = Vec3::new(0.0, 1.0, 0.0); //mesh.push_quad(quad(*a, a + h, b + h, *b)); box_along_line( LineSegment3 { start: *a, end: *b }, - 0.1, - 0.1, + *width, + *width, [1.0; 4], &mut mesh, ); diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index c87821d971..af2eb453b2 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -27,6 +27,7 @@ use crate::{ GlobalsBindGroup, Light, Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals, Renderer, Shadow, ShadowLocals, SkyboxVertex, }, + session::PlayerDebugLines, settings::Settings, window::{AnalogGameInput, Event}, }; @@ -39,8 +40,9 @@ use common::{ }, outcome::Outcome, resources::{DeltaTime, TimeScale}, - terrain::{BlockKind, TerrainChunk, TerrainGrid}, + terrain::{BlockKind, CoordinateConversions, TerrainChunk, TerrainGrid, NEIGHBOR_DELTA}, vol::ReadVol, + weather::WeatherGrid, }; use common_base::{prof_span, span}; use common_state::State; @@ -1541,7 +1543,7 @@ impl Scene { for line in chunk.meta().debug_lines().iter() { let shape_id = self .debug - .add_shape(DebugShape::Line([line.start, line.end])); + .add_shape(DebugShape::Line([line.start, line.end], 0.1)); ret.push(shape_id); self.debug .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]); @@ -1638,4 +1640,77 @@ impl Scene { keep }); } + + pub fn maintain_debug_vectors(&mut self, client: &Client, lines: &mut PlayerDebugLines) { + lines + .chunk_normal + .take() + .map(|id| self.debug.remove_shape(id)); + lines.fluid_vel.take().map(|id| self.debug.remove_shape(id)); + lines.wind.take().map(|id| self.debug.remove_shape(id)); + lines.vel.take().map(|id| self.debug.remove_shape(id)); + if client.debug_vectors_enabled { + let ecs = client.state().ecs(); + + let vels = &ecs.read_component::(); + let Some(vel) = vels.get(client.entity()) else { return; }; + + let phys_states = &ecs.read_component::(); + let Some(phys) = phys_states.get(client.entity()) else {return;}; + + let positions = &ecs.read_component::(); + let Some(pos) = positions.get(client.entity()) else {return;}; + + let weather = ecs.read_resource::(); + // take id and remove to delete the previous lines. + + const LINE_WIDTH: f32 = 0.05; + // Fluid Velocity + { + let Some(fluid) = phys.in_fluid else { return;}; + let shape = DebugShape::Line([pos.0, pos.0 + fluid.flow_vel().0 / 2.], LINE_WIDTH); + let id = self.debug.add_shape(shape); + lines.fluid_vel = Some(id); + self.debug + .set_context(id, [0.0; 4], [0.18, 0.72, 0.87, 0.8], [0.0, 0.0, 0.0, 1.0]); + } + // Chunk Terrain Normal Vector + { + let Some(chunk) = client.current_chunk() else { return;}; + let shape = DebugShape::Line( + [ + pos.0, + pos.0 + + chunk + .meta() + .approx_chunk_terrain_normal() + .unwrap_or(Vec3::unit_z()) + * 2.5, + ], + LINE_WIDTH, + ); + let id = self.debug.add_shape(shape); + lines.chunk_normal = Some(id); + self.debug + .set_context(id, [0.0; 4], [0.22, 0.63, 0.1, 0.8], [0.0, 0.0, 0.0, 1.0]); + } + // Wind + { + let wind = weather.get_interpolated(pos.0.xy()).wind_vel(); + let shape = DebugShape::Line([pos.0, pos.0 + wind * 5.0], LINE_WIDTH); + let id = self.debug.add_shape(shape); + lines.wind = Some(id); + self.debug + .set_context(id, [0.0; 4], [0.76, 0.76, 0.76, 0.8], [0.0, 0.0, 0.0, 1.0]); + } + // Player Vel + { + let shape = DebugShape::Line([pos.0, pos.0 + vel.0 / 2.0], LINE_WIDTH); + let id = self.debug.add_shape(shape); + lines.vel = Some(id); + self.debug + .set_context(id, [0.0; 4], [0.98, 0.76, 0.01, 0.8], [0.0, 0.0, 0.0, 1.0]); + } + } + } } diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index 8b1295e86d..b8d3c01d39 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -89,6 +89,14 @@ enum TickAction { Disconnect, } +#[derive(Default)] +pub struct PlayerDebugLines { + pub chunk_normal: Option, + pub wind: Option, + pub fluid_vel: Option, + pub vel: Option, +} + pub struct SessionState { scene: Scene, pub(crate) client: Rc>, @@ -113,6 +121,7 @@ pub struct SessionState { #[cfg(not(target_os = "macos"))] mumble_link: SharedLink, hitboxes: HashMap, + lines: PlayerDebugLines, tracks: HashMap, Vec>, } @@ -186,6 +195,7 @@ impl SessionState { hitboxes: HashMap::new(), metadata, tracks: HashMap::new(), + lines: Default::default(), } } @@ -237,6 +247,7 @@ impl SessionState { &mut self.hitboxes, &mut self.tracks, ); + self.scene.maintain_debug_vectors(&client, &mut self.lines); // All this camera code is just to determine if it's underwater for the sfx // filter diff --git a/voxygen/src/ui/egui/mod.rs b/voxygen/src/ui/egui/mod.rs index efdd9b18f4..2e440b12a9 100644 --- a/voxygen/src/ui/egui/mod.rs +++ b/voxygen/src/ui/egui/mod.rs @@ -102,6 +102,9 @@ impl EguiState { } } }, + EguiAction::SetShowDebugVector(enabled) => { + client.debug_vectors_enabled = enabled; + }, }); new_render_mode.map(|rm| SettingsChange::Graphics(Graphics::ChangeRenderMode(Box::new(rm)))) From 2ae7c68dde05dd5992ff07a060b9ec1664b52e0e Mon Sep 17 00:00:00 2001 From: laundmo Date: Sun, 18 Jun 2023 15:18:51 +0200 Subject: [PATCH 08/22] move sun/moon direction to common --- common/src/resources.rs | 21 +++++++++++++++++++++ voxygen/src/render/pipelines/mod.rs | 23 +++-------------------- voxygen/src/scene/mod.rs | 6 +++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/common/src/resources.rs b/common/src/resources.rs index 007f34d0bd..1d3c968e89 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -2,10 +2,31 @@ use crate::comp::Pos; use serde::{Deserialize, Serialize}; use specs::Entity; use std::ops::{Mul, MulAssign}; +use vek::Vec3; /// A resource that stores the time of day. #[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)] pub struct TimeOfDay(pub f64); +impl TimeOfDay { + pub fn new(t: f64) -> Self { TimeOfDay(t) } + + fn get_angle_rad(self) -> f32 { + const TIME_FACTOR: f64 = (std::f64::consts::PI * 2.0) / (3600.0 * 24.0); + ((self.0 as f32 * TIME_FACTOR) % (std::f64::consts::PI * 2.0)) as f32 + } + + /// Computes the direction of light from the sun based on the time of day. + pub fn get_sun_dir(self) -> Vec3 { + let angle_rad = self.get_angle_rad(); + Vec3::new(-angle_rad.sin(), 0.0, angle_rad.cos()) + } + + /// Computes the direction of light from the moon based on the time of day. + pub fn get_moon_dir(self) -> Vec3 { + let angle_rad = self.get_angle_rad(); + -Vec3::new(-angle_rad.sin(), 0.0, angle_rad.cos() - 0.5).normalized() + } +} impl TimeOfDay { pub fn day(&self) -> f64 { self.0.rem_euclid(24.0 * 3600.0) } diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index fc87440c25..da20cd7d0c 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -20,7 +20,7 @@ pub mod ui; use super::{Consts, Renderer, Texture}; use crate::scene::camera::CameraMode; use bytemuck::{Pod, Zeroable}; -use common::{terrain::BlockKind, util::srgb_to_linear}; +use common::{resources::TimeOfDay, terrain::BlockKind, util::srgb_to_linear}; use std::marker::PhantomData; use vek::*; @@ -144,8 +144,8 @@ impl Globals { 0.0, 0.0, ], - sun_dir: Vec4::from_direction(Self::get_sun_dir(time_of_day)).into_array(), - moon_dir: Vec4::from_direction(Self::get_moon_dir(time_of_day)).into_array(), + sun_dir: Vec4::from_direction(TimeOfDay::new(time_of_day).get_sun_dir()).into_array(), + moon_dir: Vec4::from_direction(TimeOfDay::new(time_of_day).get_moon_dir()).into_array(), tick: [ (tick % TIME_OVERFLOW) as f32, (tick / TIME_OVERFLOW).floor() as f32, @@ -195,23 +195,6 @@ impl Globals { globals_dummy: [0.0; 3], } } - - fn get_angle_rad(time_of_day: f64) -> f32 { - const TIME_FACTOR: f64 = (std::f64::consts::PI * 2.0) / (3600.0 * 24.0); - ((time_of_day * TIME_FACTOR) % (std::f64::consts::PI * 2.0)) as f32 - } - - /// Computes the direction of light from the sun based on the time of day. - pub fn get_sun_dir(time_of_day: f64) -> Vec3 { - let angle_rad = Self::get_angle_rad(time_of_day); - Vec3::new(-angle_rad.sin(), 0.0, angle_rad.cos()) - } - - /// Computes the direction of light from the moon based on the time of day. - pub fn get_moon_dir(time_of_day: f64) -> Vec3 { - let angle_rad = Self::get_angle_rad(time_of_day); - -Vec3::new(-angle_rad.sin(), 0.0, angle_rad.cos() - 0.5).normalized() - } } impl Default for Globals { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index af2eb453b2..5d79ce5c58 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -39,7 +39,7 @@ use common::{ tool::ToolKind, }, outcome::Outcome, - resources::{DeltaTime, TimeScale}, + resources::{DeltaTime, TimeScale, TimeOfDay}, terrain::{BlockKind, CoordinateConversions, TerrainChunk, TerrainGrid, NEIGHBOR_DELTA}, vol::ReadVol, weather::WeatherGrid, @@ -150,11 +150,11 @@ pub struct SceneData<'a> { impl<'a> SceneData<'a> { pub fn get_sun_dir(&self) -> Vec3 { - Globals::get_sun_dir(self.interpolated_time_of_day.unwrap_or(0.0)) + TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_sun_dir() } pub fn get_moon_dir(&self) -> Vec3 { - Globals::get_moon_dir(self.interpolated_time_of_day.unwrap_or(0.0)) + TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_moon_dir() } } From 6e5bd898189995fb4a2000801f1947e8bf96725e Mon Sep 17 00:00:00 2001 From: laundmo Date: Sun, 18 Jun 2023 23:43:25 +0200 Subject: [PATCH 09/22] Add near_water boolean to TerrainChunkMeta + lots of smaller tweaks and changes + tuning to make more fun (its a bit strong though) --- common/src/terrain/mod.rs | 6 ++ common/systems/src/phys.rs | 121 +++++++++++++++++++++++-------------- world/src/lib.rs | 1 + 3 files changed, 83 insertions(+), 45 deletions(-) diff --git a/common/src/terrain/mod.rs b/common/src/terrain/mod.rs index 91220837a4..fa01cf3e53 100644 --- a/common/src/terrain/mod.rs +++ b/common/src/terrain/mod.rs @@ -142,6 +142,7 @@ pub struct TerrainChunkMeta { tree_density: f32, contains_cave: bool, contains_river: bool, + near_water: bool, river_velocity: Vec3, approx_chunk_terrain_normal: Option>, rockiness: f32, @@ -163,6 +164,7 @@ impl TerrainChunkMeta { tree_density: f32, contains_cave: bool, contains_river: bool, + near_water: bool, river_velocity: Vec3, temp: f32, humidity: f32, @@ -178,6 +180,7 @@ impl TerrainChunkMeta { tree_density, contains_cave, contains_river, + near_water, river_velocity, temp, humidity, @@ -200,6 +203,7 @@ impl TerrainChunkMeta { tree_density: 0.0, contains_cave: false, contains_river: false, + near_water: false, river_velocity: Vec3::zero(), temp: 0.0, humidity: 0.0, @@ -227,6 +231,8 @@ impl TerrainChunkMeta { pub fn contains_river(&self) -> bool { self.contains_river } + pub fn near_water(&self) -> bool { self.near_water } + pub fn river_velocity(&self) -> Vec3 { self.river_velocity } pub fn site(&self) -> Option { self.site } diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index e7f5afb68f..1f0aa9368b 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -14,7 +14,7 @@ use common::{ outcome::Outcome, resources::{DeltaTime, GameMode, TimeOfDay}, states, - terrain::{Block, BlockKind, CoordinateConversions, TerrainGrid, NEIGHBOR_DELTA}, + terrain::{Block, BlockKind, CoordinateConversions, SiteKindMeta, TerrainGrid, NEIGHBOR_DELTA}, time::DayPeriod, uid::Uid, util::{Projection, SpatialGrid}, @@ -621,16 +621,40 @@ impl<'a> PhysicsData<'a> { .join() { if !state.is_glide() { + phys.in_fluid = phys.in_fluid.map(|f| match f { + Fluid::Air { elevation, .. } => Fluid::Air { + vel: Vel::zero(), + elevation, + }, + fluid => fluid, + }); continue; } prof_span!(guard, "Apply Weather INIT"); - let chunk_pos: Vec2 = pos.0.xy().wpos_to_cpos().as_(); + let pos_2d = pos.0.as_().xy(); + let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { continue; }; let meta = current_chunk.meta(); let interp_weather = weather.get_interpolated(pos.0.xy()); // Weather sim wind - let above_ground = pos.0.z - meta.alt(); + let interp_alt = read + .terrain + .get_interpolated(pos_2d, |c| c.meta().alt()) + .unwrap_or(0.); + let interp_tree_density = read + .terrain + .get_interpolated(pos_2d, |c| c.meta().tree_density()) + .unwrap_or(0.); + let normal = read + .terrain + .get_interpolated(pos_2d, |c| { + c.meta() + .approx_chunk_terrain_normal() + .unwrap_or(Vec3::unit_z()) + }) + .unwrap_or(Vec3::unit_z()); + let above_ground = pos.0.z - interp_alt; let wind_velocity = interp_weather.wind_vel(); let surrounding_chunks_metas = NEIGHBOR_DELTA @@ -642,11 +666,16 @@ impl<'a> PhysicsData<'a> { drop(guard); - prof_span!(guard, "Apply Weather THERMALS"); + prof_span!(guard, "thermals"); // === THERMALS === + // TODO: doesnt work well let mut lift = meta.temp() * 5.0; + // sun angle of incidence. + let sun_dir = read.time_of_day.get_sun_dir().normalized(); + lift += ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 5.; + let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); // more thermals if hot chunks border cold chunks @@ -657,38 +686,42 @@ impl<'a> PhysicsData<'a> { } .min(2.0); - // Estimate warmth over day based on time of day. https://www.desmos.com/calculator/kw0ba0i7mf - lift *= f32::exp(-((fraction_of_day - 0.5).powf(6.0)) / 0.0001); - - // way more thermals in strong rain (its often caused by strong thermals). less - // in weak rain or cloudy.. + // way more thermals in strong rain as its often caused by strong thermals. + // less in weak rain or cloudy.. lift *= if interp_weather.rain.is_between(0.5, 1.0) && interp_weather.cloud.is_between(0.6, 1.0) { 1.5 - } else if interp_weather.rain.is_between(0.0, 0.5) - && interp_weather.cloud.is_between(0.01, 1.0) + } else if interp_weather.rain.is_between(0.2, 0.5) + && interp_weather.cloud.is_between(0.3, 0.6) { - 0.1 + 0.8 } else { 1.0 }; - lift *= (above_ground / 60.).min(1.); // the first 60 blocks are weaker. starting from the ground should be difficult. + // the first 15 blocks are weaker. starting from the ground should be difficult. + lift *= (above_ground / 15.).min(1.); + lift *= (280. - above_ground / 80.).clamp(0.0, 1.0); - // biome thermals modifiers - use common::terrain::BiomeKind::*; - lift *= match meta.biome() { - Void => 0.0, - Lake | Ocean | Swamp => 0.1, - Taiga | Grassland | Savannah => 1.0, - Mountain => 0.8, - Snowland => 0.9, - Desert => 0.2, // deserts are too uniformly hot for nice thermal columns - Jungle | Forest => 0.5, // trees cool the air by evaporation + if interp_alt > 500.0 { + lift *= 0.8; + } + + // more thermals above towns, the materials tend to heat up more. + lift *= match meta.site() { + Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 4.0, + _ => 1.0, }; + + // bodies of water cool the air, causing less thermals + lift *= read + .terrain + .get_interpolated(pos_2d, |c| 1. - c.meta().near_water() as i32 as f32) + .unwrap_or(1.); + drop(guard); - prof_span!(guard, "Apply Weather RIDGE_WAVE"); + prof_span!(guard, "ridge lift"); // === Ridge/Wave lift === @@ -697,45 +730,43 @@ impl<'a> PhysicsData<'a> { // theres sometimes a noticeable bump while gliding whereas it should feel // smooth. let mut ridge_wind = { - let normal = read - .terrain - .get_interpolated(pos.0.as_().xy(), |c| { - c.meta() - .approx_chunk_terrain_normal() - .unwrap_or(Vec3::unit_z()) - }) - .unwrap_or(Vec3::unit_z()); // the the line along the surface of the chunk (perpendicular to normal) let mut up = normal.cross(normal.with_z(0.)).cross(normal) * -1.; + // sometimes the cross product thing might be (0,0,0) which causes normalize to + // spit out NaN. so this just says "yeah actually, that means up". + if up.magnitude().is_between(-0.0001, 0.0001) { + up = Vec3::unit_z(); + } up.normalize(); + // a bit further up than just flat along the surface - up = up.with_z(up.z + 0.2); + up = up.with_z(up.z + 0.4); // angle between normal and wind let mut angle = wind_velocity.angle_between(normal.xy()); // 1.4 radians of zero // a deadzone of +-1.5 radians if wind is blowing away from // the mountainside. angle = (angle - 1.3).max(0.0); + // multiply the upwards vector by angle of wind incidence. less incidence means // less lift. This feels better ingame than actually // calculating how the wind would be redirected. - (up * angle) * wind_velocity.magnitude_squared() * 7. + (up * angle) * wind_velocity.magnitude() * 1.5 }; // Cliffs mean more lift - ridge_wind *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + ridge_wind *= (0.9 + (meta.cliff_height() / 44.0) * 1.2); // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs - // trees mean less lift - ridge_wind *= (1.0 - meta.tree_density()).min(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons + // height based fall-off + ridge_wind *= ((150. - above_ground) / 40.).clamp(0.0, 1.0); drop(guard); - // see: https://en.wikipedia.org/wiki/Lift_(soaring) - // ridge/wave lift: - // - along the ridge of a mountain if wind is blowing against and up it - // - around the tops of large mountains - // convergence zones: - // - where hot and cold meet. - // - could use biomes/temp difference between chunks to model this - let wind_vel = ridge_wind.with_z(ridge_wind.z + lift); + + let mut wind_vel = (wind_velocity / 4.).with_z(lift) + ridge_wind; + wind_vel *= (1.0 - interp_tree_density).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons + + // clamp magnitude, we never want to throw players around way too fast. + let magn = wind_vel.magnitude_squared() + 0.0001; // Hack: adding a tiny value so that its never div by zero. + wind_vel *= magn.min(600.) / magn; // 600 here is compared to squared ~ 25. this limits the magnitude of the wind. phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { vel: Vel(wind_vel), diff --git a/world/src/lib.rs b/world/src/lib.rs index f786105d05..bdb9c95771 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -397,6 +397,7 @@ impl World { sim_chunk.tree_density, sim_chunk.cave.1.alt != 0.0, sim_chunk.river.is_river(), + sim_chunk.river.near_water(), sim_chunk.river.velocity, sim_chunk.temp, sim_chunk.humidity, From 04805167ff0b3717a6e4b762a679348fcb600ae1 Mon Sep 17 00:00:00 2001 From: laundmo Date: Mon, 19 Jun 2023 23:04:27 +0200 Subject: [PATCH 10/22] undo linker --- .cargo/config | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.cargo/config b/.cargo/config index 7409c9c2cf..5dbe6321db 100644 --- a/.cargo/config +++ b/.cargo/config @@ -31,7 +31,4 @@ dbg-voxygen = "run --bin veloren-voxygen --profile debuginfo" # misc swarm = "run --bin swarm --features client/bin_bot,client/tick_network --" ci-clippy = "clippy --all-targets --locked --features=bin_cmd_doc_gen,bin_compression,bin_csv,bin_graphviz,bin_bot,bin_asset_migrate,asset_tweak,bin,stat" -ci-clippy2 = "clippy -p veloren-voxygen --locked --no-default-features --features=default-publish" - -[target.x86_64-pc-windows-msvc] -linker = "rust-lld.exe" +ci-clippy2 = "clippy -p veloren-voxygen --locked --no-default-features --features=default-publish" \ No newline at end of file From 2add795109f87f6b407a82f6150c0b54aacf4d78 Mon Sep 17 00:00:00 2001 From: laundmo Date: Sun, 25 Jun 2023 17:21:24 +0200 Subject: [PATCH 11/22] change ridge lift to just angles, other tweaks --- common/systems/src/phys.rs | 72 +++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 1f0aa9368b..b1d59cb24c 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -30,7 +30,10 @@ use specs::{ Write, WriteExpect, WriteStorage, }; use std::{f32::consts::PI, ops::Range}; -use vek::{num_traits::Signed, *}; +use vek::{ + num_traits::{Pow, Signed}, + *, +}; /// The density of the fluid as a function of submersion ratio in given fluid /// where it is assumed that any unsubmersed part is is air. @@ -620,7 +623,13 @@ impl<'a> PhysicsData<'a> { ) .join() { - if !state.is_glide() { + prof_span!(guard, "Apply Weather INIT"); + let pos_2d = pos.0.as_().xy(); + let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); + let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { continue; }; + + let meta = current_chunk.meta(); + if !state.is_glide() || meta.alt() - 25. > pos.0.z { phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { vel: Vel::zero(), @@ -630,12 +639,6 @@ impl<'a> PhysicsData<'a> { }); continue; } - prof_span!(guard, "Apply Weather INIT"); - let pos_2d = pos.0.as_().xy(); - let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); - let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { continue; }; - - let meta = current_chunk.meta(); let interp_weather = weather.get_interpolated(pos.0.xy()); // Weather sim wind let interp_alt = read @@ -646,6 +649,13 @@ impl<'a> PhysicsData<'a> { .terrain .get_interpolated(pos_2d, |c| c.meta().tree_density()) .unwrap_or(0.); + let interp_town = read + .terrain + .get_interpolated(pos_2d, |c| match c.meta().site() { + Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 3.5, + _ => 1.0, + }) + .unwrap_or(0.); let normal = read .terrain .get_interpolated(pos_2d, |c| { @@ -669,12 +679,10 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "thermals"); // === THERMALS === - // TODO: doesnt work well - let mut lift = meta.temp() * 5.0; // sun angle of incidence. let sun_dir = read.time_of_day.get_sun_dir().normalized(); - lift += ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 5.; + let mut lift = ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 3.; let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); @@ -701,17 +709,14 @@ impl<'a> PhysicsData<'a> { }; // the first 15 blocks are weaker. starting from the ground should be difficult. lift *= (above_ground / 15.).min(1.); - lift *= (280. - above_ground / 80.).clamp(0.0, 1.0); + lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); if interp_alt > 500.0 { lift *= 0.8; } // more thermals above towns, the materials tend to heat up more. - lift *= match meta.site() { - Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 4.0, - _ => 1.0, - }; + lift *= interp_town; // bodies of water cool the air, causing less thermals lift *= read @@ -726,42 +731,29 @@ impl<'a> PhysicsData<'a> { // === Ridge/Wave lift === // WORKING! - // TODO: average out over 4 surrounding chunks to make chunk borders less harsh. - // theres sometimes a noticeable bump while gliding whereas it should feel - // smooth. - let mut ridge_wind = { - // the the line along the surface of the chunk (perpendicular to normal) - let mut up = normal.cross(normal.with_z(0.)).cross(normal) * -1.; - // sometimes the cross product thing might be (0,0,0) which causes normalize to - // spit out NaN. so this just says "yeah actually, that means up". - if up.magnitude().is_between(-0.0001, 0.0001) { - up = Vec3::unit_z(); - } - up.normalize(); - - // a bit further up than just flat along the surface - up = up.with_z(up.z + 0.4); - + let mut ridge_lift = { + let steepness = normal.angle_between(normal.with_z(0.)).max(0.5); // angle between normal and wind let mut angle = wind_velocity.angle_between(normal.xy()); // 1.4 radians of zero // a deadzone of +-1.5 radians if wind is blowing away from // the mountainside. angle = (angle - 1.3).max(0.0); - // multiply the upwards vector by angle of wind incidence. less incidence means - // less lift. This feels better ingame than actually - // calculating how the wind would be redirected. - (up * angle) * wind_velocity.magnitude() * 1.5 + // the ridge lift is based on the angle and the velocity of the wind + angle * steepness * wind_velocity.magnitude() * 2.5 }; // Cliffs mean more lift - ridge_wind *= (0.9 + (meta.cliff_height() / 44.0) * 1.2); // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + ridge_lift *= (0.9 + (meta.cliff_height() / 44.0) * 1.2); // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs - // height based fall-off - ridge_wind *= ((150. - above_ground) / 40.).clamp(0.0, 1.0); + // height based fall-off https://www.desmos.com/calculator/jijqfunchg + ridge_lift *= 1. / (1. + (1.3f32.powf(0.1 * above_ground - 15.))); drop(guard); - let mut wind_vel = (wind_velocity / 4.).with_z(lift) + ridge_wind; + // more flat wind above ground https://www.desmos.com/calculator/jryiyqsdnx + let wind_factor = 1. / (0.25 + (0.96f32.powf(0.1 * above_ground - 15.))); + + let mut wind_vel = (wind_velocity * wind_factor).with_z(lift + ridge_lift); wind_vel *= (1.0 - interp_tree_density).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons // clamp magnitude, we never want to throw players around way too fast. From 283b40472452dbfff4929a7d05050f9999690628 Mon Sep 17 00:00:00 2001 From: laundmo Date: Thu, 10 Aug 2023 19:18:04 +0200 Subject: [PATCH 12/22] Working wind/lift particles + clippy fixes --- assets/voxygen/shaders/particle-vert.glsl | 8 ++++++ common/systems/src/phys.rs | 17 +++++------- voxygen/src/render/pipelines/particle.rs | 1 + voxygen/src/scene/mod.rs | 4 +-- voxygen/src/scene/particle.rs | 33 +++++++++++++++++++++-- world/src/lib.rs | 3 +-- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index b59f41a2ec..5128043cd2 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -95,6 +95,7 @@ const int ENERGY_PHOENIX = 55; const int PHOENIX_BEAM = 56; const int PHOENIX_BUILD_UP_AIM = 57; const int CLAY_SHRAPNEL = 58; +const int AIRFLOW = 47; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -982,6 +983,13 @@ void main() { vec3(5 * (1 - percent())), vec4(vec3(clay_color * 3, clay_color * 2, clay_color), 1), spin_in_axis(vec3(1,0,0),0) + case AIRFLOW: + perp_axis = normalize(cross(inst_dir, vec3(0.0, 0.0, 1.0))); + attr = Attr( + inst_dir * 0.2 * length(inst_dir) * percent() + inst_dir * percent() * 0.08, + vec3(0.03 * length(inst_dir), 0.03 * length(inst_dir), 20.0 * length(inst_dir) * percent() * (1 - percent())), + vec4(1.1, 1.1, 1.1, 0.3), + spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0) ); break; default: diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index b1d59cb24c..796dd212d5 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -15,7 +15,6 @@ use common::{ resources::{DeltaTime, GameMode, TimeOfDay}, states, terrain::{Block, BlockKind, CoordinateConversions, SiteKindMeta, TerrainGrid, NEIGHBOR_DELTA}, - time::DayPeriod, uid::Uid, util::{Projection, SpatialGrid}, vol::{BaseVol, ReadVol}, @@ -29,11 +28,8 @@ use specs::{ shred, Entities, Entity, Join, LendJoin, ParJoin, Read, ReadExpect, ReadStorage, SystemData, Write, WriteExpect, WriteStorage, }; -use std::{f32::consts::PI, ops::Range}; -use vek::{ - num_traits::{Pow, Signed}, - *, -}; +use std::ops::Range; +use vek::*; /// The density of the fluid as a function of submersion ratio in given fluid /// where it is assumed that any unsubmersed part is is air. @@ -612,9 +608,6 @@ impl<'a> PhysicsData<'a> { if let Some(weather) = &read.weather { // 0.0..1.0, 0.25 morning, 0.45 midday, 0.66 evening, 0.79 night, 0.0/1.0 // midnight - const SECONDS_PER_DAY: f64 = 60.0 * 60.0 * 24.0; - let fraction_of_day: f32 = - (read.time_of_day.0.rem_euclid(SECONDS_PER_DAY) / SECONDS_PER_DAY) as f32; for (_, state, pos, phys) in ( &read.entities, &read.character_states, @@ -684,6 +677,7 @@ impl<'a> PhysicsData<'a> { let sun_dir = read.time_of_day.get_sun_dir().normalized(); let mut lift = ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 3.; + // TODO: potential source of harsh edges in wind speed. let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); // more thermals if hot chunks border cold chunks @@ -694,6 +688,7 @@ impl<'a> PhysicsData<'a> { } .min(2.0); + // TODO: potential source of harsh edges in wind speed. // way more thermals in strong rain as its often caused by strong thermals. // less in weak rain or cloudy.. lift *= if interp_weather.rain.is_between(0.5, 1.0) @@ -711,6 +706,8 @@ impl<'a> PhysicsData<'a> { lift *= (above_ground / 15.).min(1.); lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); + // smooth this, and increase height some more (500 isnt that much higher than + // the spires) if interp_alt > 500.0 { lift *= 0.8; } @@ -744,7 +741,7 @@ impl<'a> PhysicsData<'a> { }; // Cliffs mean more lift - ridge_lift *= (0.9 + (meta.cliff_height() / 44.0) * 1.2); // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs // height based fall-off https://www.desmos.com/calculator/jijqfunchg ridge_lift *= 1. / (1. + (1.3f32.powf(0.1 * above_ground - 15.))); diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 34a156c755..3bef36fb38 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -110,6 +110,7 @@ pub enum ParticleMode { PhoenixBeam = 56, PhoenixBuildUpAim = 57, ClayShrapnel = 58, + Airflow = 47, } impl ParticleMode { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 5d79ce5c58..d1d94242b9 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -39,8 +39,8 @@ use common::{ tool::ToolKind, }, outcome::Outcome, - resources::{DeltaTime, TimeScale, TimeOfDay}, - terrain::{BlockKind, CoordinateConversions, TerrainChunk, TerrainGrid, NEIGHBOR_DELTA}, + resources::{DeltaTime, TimeOfDay, TimeScale}, + terrain::{BlockKind, TerrainChunk, TerrainGrid, CoordinateConversion, NEIGHBOR_DELTA}, vol::ReadVol, weather::WeatherGrid, }; diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 631786e043..00929e3eb0 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -15,7 +15,7 @@ use common::{ item::Reagent, object, shockwave::{self, ShockwaveDodgeable}, - Beam, Body, CharacterActivity, CharacterState, Ori, Pos, Scale, Shockwave, Vel, + BeamSegment, Body, CharacterActivity, CharacterState, Fluid, Ori, PhysicsState, Pos, Scale, Shockwave, Vel, }, figure::Segment, outcome::Outcome, @@ -916,7 +916,7 @@ impl ParticleMgr { let dt = scene_data.state.get_delta_time(); let mut rng = thread_rng(); - for (entity, interpolated, vel, character_state, body, ori, character_activity) in ( + for (entity, interpolated, vel, character_state, body, ori, character_activity, physics) in ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), @@ -924,6 +924,7 @@ impl ParticleMgr { &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() { @@ -1317,6 +1318,34 @@ impl ParticleMgr { ); } }, + CharacterState::Glide(_) => { + if let Some(Fluid::Air { + vel: air_vel, + elevation: _, + }) = physics.in_fluid + { + self.particles.resize_with( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(10))), // scale with wind speed + || { + let start_pos = interpolated.pos + + Vec3::new( + body.max_radius(), + body.max_radius(), + body.height() / 2.0, + ) + .map(|d| d * rng.gen_range(-10.0..10.0)); + Particle::new_directed( + Duration::from_millis(300), // scale with wind speed + time, + ParticleMode::Airflow, + start_pos, + start_pos + air_vel.0, + ) + }, + ); + } + }, _ => {}, } } diff --git a/world/src/lib.rs b/world/src/lib.rs index bdb9c95771..e1a9f2a667 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -54,8 +54,7 @@ use common::{ resources::TimeOfDay, rtsim::ChunkResource, terrain::{ - Block, BlockKind, CoordinateConversions, SpriteKind, TerrainChunk, TerrainChunkMeta, - TerrainChunkSize, TerrainGrid, + Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid, }, vol::{ReadVol, RectVolSize, WriteVol}, }; From 52328b89d281a1ff79d9a7544e1f945cdea0744d Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Wed, 31 Jan 2024 19:39:35 +0200 Subject: [PATCH 13/22] rebase --- assets/voxygen/shaders/particle-vert.glsl | 4 +++- common/src/resources.rs | 2 +- common/systems/src/phys.rs | 4 +++- voxygen/src/render/pipelines/particle.rs | 2 +- voxygen/src/scene/mod.rs | 22 +++++++++++++------ voxygen/src/scene/particle.rs | 26 ++++++++++++----------- world/src/lib.rs | 18 +++------------- 7 files changed, 41 insertions(+), 37 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 5128043cd2..f276619907 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -95,7 +95,7 @@ const int ENERGY_PHOENIX = 55; const int PHOENIX_BEAM = 56; const int PHOENIX_BUILD_UP_AIM = 57; const int CLAY_SHRAPNEL = 58; -const int AIRFLOW = 47; +const int AIRFLOW = 59; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -983,6 +983,8 @@ void main() { vec3(5 * (1 - percent())), vec4(vec3(clay_color * 3, clay_color * 2, clay_color), 1), spin_in_axis(vec3(1,0,0),0) + ); + break; case AIRFLOW: perp_axis = normalize(cross(inst_dir, vec3(0.0, 0.0, 1.0))); attr = Attr( diff --git a/common/src/resources.rs b/common/src/resources.rs index 1d3c968e89..acac63151d 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -12,7 +12,7 @@ impl TimeOfDay { fn get_angle_rad(self) -> f32 { const TIME_FACTOR: f64 = (std::f64::consts::PI * 2.0) / (3600.0 * 24.0); - ((self.0 as f32 * TIME_FACTOR) % (std::f64::consts::PI * 2.0)) as f32 + ((self.0 * TIME_FACTOR) % (std::f64::consts::PI * 2.0)) as f32 } /// Computes the direction of light from the sun based on the time of day. diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 796dd212d5..5e4fc293c7 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -619,7 +619,9 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "Apply Weather INIT"); let pos_2d = pos.0.as_().xy(); let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); - let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { continue; }; + let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { + continue; + }; let meta = current_chunk.meta(); if !state.is_glide() || meta.alt() - 25. > pos.0.z { diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 3bef36fb38..9fd14f365f 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -110,7 +110,7 @@ pub enum ParticleMode { PhoenixBeam = 56, PhoenixBuildUpAim = 57, ClayShrapnel = 58, - Airflow = 47, + Airflow = 59, } impl ParticleMode { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index d1d94242b9..1dccfe9ab2 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -40,7 +40,7 @@ use common::{ }, outcome::Outcome, resources::{DeltaTime, TimeOfDay, TimeScale}, - terrain::{BlockKind, TerrainChunk, TerrainGrid, CoordinateConversion, NEIGHBOR_DELTA}, + terrain::{BlockKind, TerrainChunk, TerrainGrid}, vol::ReadVol, weather::WeatherGrid, }; @@ -1653,13 +1653,19 @@ impl Scene { let ecs = client.state().ecs(); let vels = &ecs.read_component::(); - let Some(vel) = vels.get(client.entity()) else { return; }; + let Some(vel) = vels.get(client.entity()) else { + return; + }; let phys_states = &ecs.read_component::(); - let Some(phys) = phys_states.get(client.entity()) else {return;}; + let Some(phys) = phys_states.get(client.entity()) else { + return; + }; let positions = &ecs.read_component::(); - let Some(pos) = positions.get(client.entity()) else {return;}; + let Some(pos) = positions.get(client.entity()) else { + return; + }; let weather = ecs.read_resource::(); // take id and remove to delete the previous lines. @@ -1667,7 +1673,9 @@ impl Scene { const LINE_WIDTH: f32 = 0.05; // Fluid Velocity { - let Some(fluid) = phys.in_fluid else { return;}; + let Some(fluid) = phys.in_fluid else { + return; + }; let shape = DebugShape::Line([pos.0, pos.0 + fluid.flow_vel().0 / 2.], LINE_WIDTH); let id = self.debug.add_shape(shape); lines.fluid_vel = Some(id); @@ -1676,7 +1684,9 @@ impl Scene { } // Chunk Terrain Normal Vector { - let Some(chunk) = client.current_chunk() else { return;}; + let Some(chunk) = client.current_chunk() else { + return; + }; let shape = DebugShape::Line( [ pos.0, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 00929e3eb0..ba35f85dc7 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -15,7 +15,8 @@ use common::{ item::Reagent, object, shockwave::{self, ShockwaveDodgeable}, - BeamSegment, Body, CharacterActivity, CharacterState, Fluid, Ori, PhysicsState, Pos, Scale, Shockwave, Vel, + Beam, Body, CharacterActivity, CharacterState, Fluid, Ori, PhysicsState, Pos, Scale, + Shockwave, Vel, }, figure::Segment, outcome::Outcome, @@ -916,17 +917,18 @@ impl ParticleMgr { let dt = scene_data.state.get_delta_time(); let mut rng = thread_rng(); - for (entity, interpolated, vel, character_state, body, ori, character_activity, physics) in ( - &ecs.entities(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - &ecs.read_storage::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ) - .join() + for (entity, interpolated, vel, character_state, body, ori, character_activity, physics) in + ( + &ecs.entities(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() { match character_state { CharacterState::Boost(_) => { diff --git a/world/src/lib.rs b/world/src/lib.rs index e1a9f2a667..429e03bdae 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -36,6 +36,7 @@ pub use block::BlockGen; use civ::WorldCivStage; pub use column::ColumnSample; pub use common::terrain::site::{DungeonKindMeta, SettlementKindMeta}; +use common::terrain::CoordinateConversions; pub use index::{IndexOwned, IndexRef}; use sim::WorldSimStage; @@ -372,6 +373,7 @@ impl World { sim_chunk.tree_density, sim_chunk.cave.1.alt != 0.0, sim_chunk.river.is_river(), + sim_chunk.river.near_water(), sim_chunk.river.velocity, sim_chunk.temp, sim_chunk.humidity, @@ -389,21 +391,7 @@ impl World { .get_origin() .distance_squared(chunk_center_wpos2d) }) - .map(|id| index.sites[*id].kind.convert_to_meta().unwrap_or_default()) - .or_else(|| sim_chunk.poi.map(|poi| self.civs.pois[poi].name.clone())), - sim_chunk.get_biome(), - sim_chunk.alt, - sim_chunk.tree_density, - sim_chunk.cave.1.alt != 0.0, - sim_chunk.river.is_river(), - sim_chunk.river.near_water(), - sim_chunk.river.velocity, - sim_chunk.temp, - sim_chunk.humidity, - sim_chunk - .sites - .iter() - .find_map(|site| index.sites[*site].kind.convert_to_meta()), + .map(|id| index.sites[*id].kind.convert_to_meta().unwrap_or_default()), self.sim.approx_chunk_terrain_normal(chunk_pos), sim_chunk.rockiness, sim_chunk.cliff_height, From 16c41109d3e79c1869013ab5b7055278171147b7 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 13:30:30 +0200 Subject: [PATCH 14/22] Add Air Velocity to debug info --- voxygen/src/hud/mod.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index ef0d35c5f5..40a55e958f 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -270,6 +270,7 @@ widget_ids! { velocity, glide_ratio, glide_aoe, + air_vel, orientation, look_direction, loaded_distance, @@ -2735,6 +2736,7 @@ impl Hud { .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .set(self.ids.glide_ratio, ui_widgets); + // Glide Angle of Attack let glide_angle_text = angle_of_attack_text( debug_info.in_fluid, debug_info.velocity, @@ -2746,6 +2748,14 @@ impl Hud { .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .set(self.ids.glide_aoe, ui_widgets); + // Air velocity + let air_vel_text = air_velocity(debug_info.in_fluid); + Text::new(&air_vel_text) + .color(TEXT_COLOR) + .down_from(self.ids.glide_aoe, V_PAD) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .set(self.ids.air_vel, ui_widgets); // Player's orientation vector let orientation_text = match debug_info.ori { Some(ori) => { @@ -2759,7 +2769,7 @@ impl Hud { }; Text::new(&orientation_text) .color(TEXT_COLOR) - .down_from(self.ids.glide_aoe, V_PAD) + .down_from(self.ids.air_vel, V_PAD) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .set(self.ids.orientation, ui_widgets); @@ -5387,6 +5397,17 @@ pub fn angle_of_attack_text( } } +fn air_velocity(fluid: Option) -> String { + if let Some(comp::Fluid::Air { vel: air_vel, .. }) = fluid { + format!( + "Air Velocity: ({:.1}, {:.1}, {:.1})", + air_vel.0.x, air_vel.0.y, air_vel.0.z + ) + } else { + "Air Velocity: Not in Air".to_owned() + } +} + /// Converts multiplier to percentage. /// NOTE: floats are not the most precise type. /// From 1dee12af851f84e078b1cb5f8ae2740d8f6eb0bb Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 15:59:41 +0200 Subject: [PATCH 15/22] Improve airflow particles --- assets/voxygen/shaders/particle-vert.glsl | 43 ++++++++++++--- voxygen/anim/src/character/gliding.rs | 3 +- voxygen/src/scene/particle.rs | 65 ++++++++++++++++------- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index f276619907..1eb5b25819 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -164,10 +164,24 @@ mat4 spin_in_axis(vec3 axis, float 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); + 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 + ); } mat4 identity() { @@ -986,11 +1000,19 @@ void main() { ); break; case AIRFLOW: - perp_axis = normalize(cross(inst_dir, vec3(0.0, 0.0, 1.0))); + perp_axis = normalize(cross(inst_dir, vec3(1.0, 0.0, 0.0))); attr = Attr( + // offsets inst_dir * 0.2 * length(inst_dir) * percent() + inst_dir * percent() * 0.08, - vec3(0.03 * length(inst_dir), 0.03 * length(inst_dir), 20.0 * length(inst_dir) * percent() * (1 - percent())), + // scale + vec3( + 0.3 * length(inst_dir), + 0.3 * length(inst_dir), + 3.0 * length(inst_dir) * percent() * (1 - percent()) + ), + // color vec4(1.1, 1.1, 1.1, 0.3), + // rotation spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0) ); break; @@ -1018,7 +1040,14 @@ void main() { // First 3 normals are negative, next 3 are positive // TODO: Make particle normals match orientation - vec4 normals[6] = vec4[](vec4(-1,0,0,0), vec4(1,0,0,0), vec4(0,-1,0,0), vec4(0,1,0,0), vec4(0,0,-1,0), vec4(0,0,1,0)); + vec4 normals[6] = vec4[]( + vec4(-1,0,0,0), + vec4(1,0,0,0), + vec4(0,-1,0,0), + vec4(0,1,0,0), + vec4(0,0,-1,0), + vec4(0,0,1,0) + ); f_norm = // inst_pos * normalize(((normals[(v_norm_ao >> 0) & 0x7u]) * attr.rot).xyz); diff --git a/voxygen/anim/src/character/gliding.rs b/voxygen/anim/src/character/gliding.rs index 4b5dc22f9b..46fa9f5836 100644 --- a/voxygen/anim/src/character/gliding.rs +++ b/voxygen/anim/src/character/gliding.rs @@ -26,7 +26,8 @@ impl Animation for GlidingAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - next.glider_trails = true; + // TODO: remove glider trails completely + next.glider_trails = false; let speednorm = velocity.magnitude().min(50.0) / 50.0; let slow = (acc_vel * 0.1).sin(); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index ba35f85dc7..eb9177b9ad 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -1326,26 +1326,53 @@ impl ParticleMgr { elevation: _, }) = physics.in_fluid { - self.particles.resize_with( - self.particles.len() - + usize::from(self.scheduler.heartbeats(Duration::from_millis(10))), // scale with wind speed - || { - let start_pos = interpolated.pos - + Vec3::new( - body.max_radius(), - body.max_radius(), - body.height() / 2.0, - ) - .map(|d| d * rng.gen_range(-10.0..10.0)); - Particle::new_directed( - Duration::from_millis(300), // scale with wind speed - time, - ParticleMode::Airflow, - start_pos, - start_pos + air_vel.0, - ) - }, + // Empirical observation is that air_vel is somewhere + // between 0.0 and 13.0, but we are extending to be sure + const MAX_AIR_VEL: f32 = 15.0; + const MIN_AIR_VEL: f32 = -2.0; + + let minmax_norm = |val, min, max| (val - min) / (max - min); + + let wind_speed = air_vel.0.magnitude(); + + // Less means more frequent particles + let heartbeat = 200 + - Lerp::lerp( + 50u64, + 150, + minmax_norm(wind_speed, MIN_AIR_VEL, MAX_AIR_VEL), + ); + + let new_count = self.particles.len() + + usize::from( + self.scheduler.heartbeats(Duration::from_millis(heartbeat)), + ); + + // More number, longer particles + let duration = Lerp::lerp( + 0u64, + 1000, + minmax_norm(wind_speed, MIN_AIR_VEL, MAX_AIR_VEL), ); + let duration = Duration::from_millis(duration); + + self.particles.resize_with(new_count, || { + let start_pos = interpolated.pos + + Vec3::new( + body.max_radius(), + body.max_radius(), + body.height() / 2.0, + ) + .map(|d| d * rng.gen_range(-10.0..10.0)); + + Particle::new_directed( + duration, + time, + ParticleMode::Airflow, + start_pos, + start_pos + air_vel.0, + ) + }); } }, _ => {}, From aa97bd6bf611e714cbc9db20464e6f1937097a88 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 16:04:01 +0200 Subject: [PATCH 16/22] Clean up systems/phys code --- common/systems/src/phys.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 5e4fc293c7..74c17231cf 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -665,7 +665,6 @@ impl<'a> PhysicsData<'a> { let surrounding_chunks_metas = NEIGHBOR_DELTA .iter() .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) - .filter(|pos| read.terrain.contains_key(*pos)) .filter_map(|cpos| read.terrain.get_key(cpos).map(|c| c.meta())) .collect::>(); @@ -756,7 +755,7 @@ impl<'a> PhysicsData<'a> { wind_vel *= (1.0 - interp_tree_density).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons // clamp magnitude, we never want to throw players around way too fast. - let magn = wind_vel.magnitude_squared() + 0.0001; // Hack: adding a tiny value so that its never div by zero. + let magn = wind_vel.magnitude_squared().max(0.0001); wind_vel *= magn.min(600.) / magn; // 600 here is compared to squared ~ 25. this limits the magnitude of the wind. phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { From 9ac75cb7d143c791822fa3ea3af6c92c2f5c92d2 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 17:25:10 +0200 Subject: [PATCH 17/22] Extract wind simulation to its own function --- common/systems/src/phys.rs | 301 ++++++++++++++++++++----------------- 1 file changed, 162 insertions(+), 139 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 74c17231cf..6cd40310c0 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -117,6 +117,163 @@ fn integrate_forces( vel } +/// Simulates winds based on weather and terrain data for specific position +// TODO: Consider refactoring and exporting if one wants to build nice visuals +// +// It pretty much does only depends on pos. +// Character State is used to skip simulating wind_velocity for non-gliding +// entities, which should be adapted before exporting to general use. +fn simulated_wind_vel( + pos: &Pos, + weather: &WeatherGrid, + terrain: &TerrainGrid, + time_of_day: &TimeOfDay, + state: &CharacterState, +) -> Result>, ()> { + prof_span!(guard, "Apply Weather INIT"); + + let pos_2d = pos.0.as_().xy(); + let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); + let Some(current_chunk) = terrain.get_key(chunk_pos) else { + return Err(()); + }; + + let meta = current_chunk.meta(); + + // Skip simulating for non-gliding entities + if !state.is_glide() || meta.alt() - 25. > pos.0.z { + return Ok(None); + } + + let interp_weather = weather.get_interpolated(pos.0.xy()); + // Weather sim wind + let interp_alt = terrain + .get_interpolated(pos_2d, |c| c.meta().alt()) + .unwrap_or(0.); + let interp_tree_density = terrain + .get_interpolated(pos_2d, |c| c.meta().tree_density()) + .unwrap_or(0.); + let interp_town = terrain + .get_interpolated(pos_2d, |c| match c.meta().site() { + Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 3.5, + _ => 1.0, + }) + .unwrap_or(0.); + let normal = terrain + .get_interpolated(pos_2d, |c| { + c.meta() + .approx_chunk_terrain_normal() + .unwrap_or(Vec3::unit_z()) + }) + .unwrap_or(Vec3::unit_z()); + let above_ground = pos.0.z - interp_alt; + let wind_velocity = interp_weather.wind_vel(); + + let surrounding_chunks_metas = NEIGHBOR_DELTA + .iter() + .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) + .filter_map(|cpos| terrain.get_key(cpos).map(|c| c.meta())) + .collect::>(); + + drop(guard); + + prof_span!(guard, "thermals"); + + // === THERMALS === + + // Sun angle of incidence. + let sun_dir = time_of_day.get_sun_dir().normalized(); + let mut lift = ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 3.; + + // TODO: potential source of harsh edges in wind speed. + let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); + + // More thermals if hot chunks border cold chunks + lift *= match temperatures { + itertools::MinMaxResult::NoElements | itertools::MinMaxResult::OneElement(_) => 1.0, + itertools::MinMaxResult::MinMax(a, b) => 0.8 + ((a - b).abs() * 1.1), + } + .min(2.0); + + // TODO: potential source of harsh edges in wind speed. + // + // Way more thermals in strong rain as its often caused by strong thermals. + // Less in weak rain or cloudy .. + lift *= if interp_weather.rain.is_between(0.5, 1.0) && interp_weather.cloud.is_between(0.6, 1.0) + { + 1.5 + } else if interp_weather.rain.is_between(0.2, 0.5) && interp_weather.cloud.is_between(0.3, 0.6) + { + 0.8 + } else { + 1.0 + }; + + // The first 15 blocks are weaker. Starting from the ground should be difficult. + lift *= (above_ground / 15.).min(1.); + lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); + + // Smooth this, and increase height some more (500 isnt that much higher than + // the spires) + // + // plz, reviewers, I don't know what comment above means + if interp_alt > 500.0 { + lift *= 0.8; + } + + // More thermals above towns, the materials tend to heat up more. + lift *= interp_town; + + // Bodies of water cool the air, causing less thermals. + lift *= terrain + .get_interpolated(pos_2d, |c| 1. - c.meta().near_water() as i32 as f32) + .unwrap_or(1.); + + drop(guard); + + prof_span!(guard, "ridge lift"); + + // === Ridge/Wave lift === + + let mut ridge_lift = { + let steepness = normal.angle_between(normal.with_z(0.)).max(0.5); + + // angle between normal and wind + let mut angle = wind_velocity.angle_between(normal.xy()); // 1.4 radians of zero + + // a deadzone of +-1.5 radians if wind is blowing away from + // the mountainside. + angle = (angle - 1.3).max(0.0); + + // the ridge lift is based on the angle and the velocity of the wind + angle * steepness * wind_velocity.magnitude() * 2.5 + }; + + // Cliffs mean more lift + // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs + ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; + + // Height based fall-off (https://www.desmos.com/calculator/jijqfunchg) + ridge_lift *= 1. / (1. + (1.3f32.powf(0.1 * above_ground - 15.))); + drop(guard); + + // More flat wind above ground (https://www.desmos.com/calculator/jryiyqsdnx) + let wind_factor = 1. / (0.25 + (0.96f32.powf(0.1 * above_ground - 15.))); + + let mut wind_vel = (wind_velocity * wind_factor).with_z(lift + ridge_lift); + + // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons + wind_vel *= (1.0 - interp_tree_density).max(0.7); + + // Clamp magnitude, we never want to throw players around way too fast. + let magn = wind_vel.magnitude_squared().max(0.0001); + + // 600 here is compared to squared ~ 25. this limits the magnitude of the wind. + wind_vel *= magn.min(600.) / magn; + + Ok(Some(wind_vel)) +} + fn calc_z_limit(char_state_maybe: Option<&CharacterState>, collider: &Collider) -> (f32, f32) { let modifier = if char_state_maybe.map_or(false, |c_s| c_s.is_dodge() || c_s.is_glide()) { 0.5 @@ -616,150 +773,16 @@ impl<'a> PhysicsData<'a> { ) .join() { - prof_span!(guard, "Apply Weather INIT"); - let pos_2d = pos.0.as_().xy(); - let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); - let Some(current_chunk) = read.terrain.get_key(chunk_pos) else { + let Ok(air_vel) = + simulated_wind_vel(pos, weather, &read.terrain, &read.time_of_day, state) + else { continue; }; + let air_vel = air_vel.unwrap_or_else(Vec3::zero); - let meta = current_chunk.meta(); - if !state.is_glide() || meta.alt() - 25. > pos.0.z { - phys.in_fluid = phys.in_fluid.map(|f| match f { - Fluid::Air { elevation, .. } => Fluid::Air { - vel: Vel::zero(), - elevation, - }, - fluid => fluid, - }); - continue; - } - let interp_weather = weather.get_interpolated(pos.0.xy()); - // Weather sim wind - let interp_alt = read - .terrain - .get_interpolated(pos_2d, |c| c.meta().alt()) - .unwrap_or(0.); - let interp_tree_density = read - .terrain - .get_interpolated(pos_2d, |c| c.meta().tree_density()) - .unwrap_or(0.); - let interp_town = read - .terrain - .get_interpolated(pos_2d, |c| match c.meta().site() { - Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 3.5, - _ => 1.0, - }) - .unwrap_or(0.); - let normal = read - .terrain - .get_interpolated(pos_2d, |c| { - c.meta() - .approx_chunk_terrain_normal() - .unwrap_or(Vec3::unit_z()) - }) - .unwrap_or(Vec3::unit_z()); - let above_ground = pos.0.z - interp_alt; - let wind_velocity = interp_weather.wind_vel(); - - let surrounding_chunks_metas = NEIGHBOR_DELTA - .iter() - .map(move |&(x, y)| chunk_pos + Vec2::new(x, y)) - .filter_map(|cpos| read.terrain.get_key(cpos).map(|c| c.meta())) - .collect::>(); - - drop(guard); - - prof_span!(guard, "thermals"); - - // === THERMALS === - - // sun angle of incidence. - let sun_dir = read.time_of_day.get_sun_dir().normalized(); - let mut lift = ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 3.; - - // TODO: potential source of harsh edges in wind speed. - let temperatures = surrounding_chunks_metas.iter().map(|m| m.temp()).minmax(); - - // more thermals if hot chunks border cold chunks - lift *= match temperatures { - itertools::MinMaxResult::NoElements - | itertools::MinMaxResult::OneElement(_) => 1.0, - itertools::MinMaxResult::MinMax(a, b) => 0.8 + ((a - b).abs() * 1.1), - } - .min(2.0); - - // TODO: potential source of harsh edges in wind speed. - // way more thermals in strong rain as its often caused by strong thermals. - // less in weak rain or cloudy.. - lift *= if interp_weather.rain.is_between(0.5, 1.0) - && interp_weather.cloud.is_between(0.6, 1.0) - { - 1.5 - } else if interp_weather.rain.is_between(0.2, 0.5) - && interp_weather.cloud.is_between(0.3, 0.6) - { - 0.8 - } else { - 1.0 - }; - // the first 15 blocks are weaker. starting from the ground should be difficult. - lift *= (above_ground / 15.).min(1.); - lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); - - // smooth this, and increase height some more (500 isnt that much higher than - // the spires) - if interp_alt > 500.0 { - lift *= 0.8; - } - - // more thermals above towns, the materials tend to heat up more. - lift *= interp_town; - - // bodies of water cool the air, causing less thermals - lift *= read - .terrain - .get_interpolated(pos_2d, |c| 1. - c.meta().near_water() as i32 as f32) - .unwrap_or(1.); - - drop(guard); - - prof_span!(guard, "ridge lift"); - - // === Ridge/Wave lift === - - // WORKING! - let mut ridge_lift = { - let steepness = normal.angle_between(normal.with_z(0.)).max(0.5); - // angle between normal and wind - let mut angle = wind_velocity.angle_between(normal.xy()); // 1.4 radians of zero - // a deadzone of +-1.5 radians if wind is blowing away from - // the mountainside. - angle = (angle - 1.3).max(0.0); - - // the ridge lift is based on the angle and the velocity of the wind - angle * steepness * wind_velocity.magnitude() * 2.5 - }; - - // Cliffs mean more lift - ridge_lift *= 0.9 + (meta.cliff_height() / 44.0) * 1.2; // 44 seems to be max, according to a lerp in WorldSim::generate_cliffs - - // height based fall-off https://www.desmos.com/calculator/jijqfunchg - ridge_lift *= 1. / (1. + (1.3f32.powf(0.1 * above_ground - 15.))); - drop(guard); - - // more flat wind above ground https://www.desmos.com/calculator/jryiyqsdnx - let wind_factor = 1. / (0.25 + (0.96f32.powf(0.1 * above_ground - 15.))); - - let mut wind_vel = (wind_velocity * wind_factor).with_z(lift + ridge_lift); - wind_vel *= (1.0 - interp_tree_density).max(0.7); // probably 0. to 1. src: SiteKind::is_suitable_loc comparisons - - // clamp magnitude, we never want to throw players around way too fast. - let magn = wind_vel.magnitude_squared().max(0.0001); - wind_vel *= magn.min(600.) / magn; // 600 here is compared to squared ~ 25. this limits the magnitude of the wind. phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { - vel: Vel(wind_vel), + vel: Vel(air_vel), elevation, }, fluid => fluid, From cd9e5bb9c90976c33b099006636d39237bd4b372 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 18:16:26 +0200 Subject: [PATCH 18/22] Nerf glider AR --- common/src/states/glide_wield.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index a47c151084..0671421250 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -26,10 +26,30 @@ impl From<&JoinData<'_>> for Data { Self { // Aspect ratio is what really matters for lift/drag ratio // and the aerodynamics model works for ARs up to 25. + // // The inflated dimensions are hopefully only a temporary // bandaid for the poor glide ratio experienced under 2.5G. + // + // The formula is: + // s: span_length_modifier + // c: chord_length_modifier + // h: height (this is a hack to balance different races) + // + // p_a = Pi/4 * c * h * s * h + // AR + // = (s * h)^2 / p_a + // = (s * h)^2 / (Pi / 4 * (c * h) * (s * h)) + // = (s * h) / (c * h) / (Pi / 4) + // = s / c / Pi/4 + // + // or if c is 1, + // = s / Pi/4 + // + // In other words, the bigger `span_length` the better. + // // A span/chord ratio of 4.5 gives an AR of ~5.73. - span_length: scale * 4.5, + // A span/chord ratio of 3.0 gives an ARI of ~3.82. + span_length: scale * 3.0, chord_length: scale, ori: *data.ori, } From 16e7c30078612505ec3ecb4b3f8c235f2c552903 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 5 Feb 2024 15:40:32 +0200 Subject: [PATCH 19/22] Improve comments --- common/systems/src/phys.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 6cd40310c0..89afb8e4c2 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -155,7 +155,7 @@ fn simulated_wind_vel( .unwrap_or(0.); let interp_town = terrain .get_interpolated(pos_2d, |c| match c.meta().site() { - Some(SiteKindMeta::Castle) | Some(SiteKindMeta::Settlement(_)) => 3.5, + Some(SiteKindMeta::Settlement(_)) => 3.5, _ => 1.0, }) .unwrap_or(0.); @@ -182,6 +182,9 @@ fn simulated_wind_vel( // === THERMALS === // Sun angle of incidence. + // + // 0.0..1.0, 0.25 morning, 0.45 midday, 0.66 evening, 0.79 night, 0.0/1.0 + // midnight let sun_dir = time_of_day.get_sun_dir().normalized(); let mut lift = ((sun_dir - normal.normalized()).magnitude() - 0.5).max(0.2) * 3.; @@ -213,10 +216,8 @@ fn simulated_wind_vel( lift *= (above_ground / 15.).min(1.); lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); - // Smooth this, and increase height some more (500 isnt that much higher than + // TODO: Smooth this, and increase height some more (500 isnt that much higher than // the spires) - // - // plz, reviewers, I don't know what comment above means if interp_alt > 500.0 { lift *= 0.8; } @@ -231,8 +232,6 @@ fn simulated_wind_vel( drop(guard); - prof_span!(guard, "ridge lift"); - // === Ridge/Wave lift === let mut ridge_lift = { @@ -255,7 +254,6 @@ fn simulated_wind_vel( // Height based fall-off (https://www.desmos.com/calculator/jijqfunchg) ridge_lift *= 1. / (1. + (1.3f32.powf(0.1 * above_ground - 15.))); - drop(guard); // More flat wind above ground (https://www.desmos.com/calculator/jryiyqsdnx) let wind_factor = 1. / (0.25 + (0.96f32.powf(0.1 * above_ground - 15.))); @@ -763,8 +761,6 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "Apply Weather"); if let Some(weather) = &read.weather { - // 0.0..1.0, 0.25 morning, 0.45 midday, 0.66 evening, 0.79 night, 0.0/1.0 - // midnight for (_, state, pos, phys) in ( &read.entities, &read.character_states, From 9b5f9704f886ce76ebfae4b81d709dccb9f03652 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 5 Feb 2024 16:32:04 +0200 Subject: [PATCH 20/22] Move condition checks outside of simulated_wind fn --- common/systems/src/phys.rs | 47 +++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 89afb8e4c2..b987081c65 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -118,18 +118,13 @@ fn integrate_forces( } /// Simulates winds based on weather and terrain data for specific position -// TODO: Consider refactoring and exporting if one wants to build nice visuals -// -// It pretty much does only depends on pos. -// Character State is used to skip simulating wind_velocity for non-gliding -// entities, which should be adapted before exporting to general use. +// TODO: Consider exporting it if one wants to build nice visuals fn simulated_wind_vel( pos: &Pos, weather: &WeatherGrid, terrain: &TerrainGrid, time_of_day: &TimeOfDay, - state: &CharacterState, -) -> Result>, ()> { +) -> Result, ()> { prof_span!(guard, "Apply Weather INIT"); let pos_2d = pos.0.as_().xy(); @@ -140,11 +135,6 @@ fn simulated_wind_vel( let meta = current_chunk.meta(); - // Skip simulating for non-gliding entities - if !state.is_glide() || meta.alt() - 25. > pos.0.z { - return Ok(None); - } - let interp_weather = weather.get_interpolated(pos.0.xy()); // Weather sim wind let interp_alt = terrain @@ -216,8 +206,8 @@ fn simulated_wind_vel( lift *= (above_ground / 15.).min(1.); lift *= (220. - above_ground / 20.).clamp(0.0, 1.0); - // TODO: Smooth this, and increase height some more (500 isnt that much higher than - // the spires) + // TODO: Smooth this, and increase height some more (500 isnt that much higher + // than the spires) if interp_alt > 500.0 { lift *= 0.8; } @@ -269,7 +259,7 @@ fn simulated_wind_vel( // 600 here is compared to squared ~ 25. this limits the magnitude of the wind. wind_vel *= magn.min(600.) / magn; - Ok(Some(wind_vel)) + Ok(wind_vel) } fn calc_z_limit(char_state_maybe: Option<&CharacterState>, collider: &Collider) -> (f32, f32) { @@ -769,16 +759,35 @@ impl<'a> PhysicsData<'a> { ) .join() { - let Ok(air_vel) = - simulated_wind_vel(pos, weather, &read.terrain, &read.time_of_day, state) + // Don't simulate for non-gliding, for now + if !state.is_glide() { + continue; + } + + let pos_2d = pos.0.as_().xy(); + let chunk_pos: Vec2 = pos_2d.wpos_to_cpos(); + let Some(current_chunk) = &read.terrain.get_key(chunk_pos) else { + // oopsie + continue; + }; + + let meta = current_chunk.meta(); + + // Skip simulating for entites deeply under the ground + if pos.0.z < meta.alt() - 25.0 { + continue; + } + + // If couldn't simulate wind for some reason, skip + let Ok(wind_vel) = + simulated_wind_vel(pos, weather, &read.terrain, &read.time_of_day) else { continue; }; - let air_vel = air_vel.unwrap_or_else(Vec3::zero); phys.in_fluid = phys.in_fluid.map(|f| match f { Fluid::Air { elevation, .. } => Fluid::Air { - vel: Vel(air_vel), + vel: Vel(wind_vel), elevation, }, fluid => fluid, From ff3f1b9b1cb139326f4a190090f87bbb5fd32cc2 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Mon, 5 Feb 2024 16:59:09 +0200 Subject: [PATCH 21/22] Re-enable glider trails --- voxygen/anim/src/character/gliding.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/voxygen/anim/src/character/gliding.rs b/voxygen/anim/src/character/gliding.rs index 46fa9f5836..4b5dc22f9b 100644 --- a/voxygen/anim/src/character/gliding.rs +++ b/voxygen/anim/src/character/gliding.rs @@ -26,8 +26,7 @@ impl Animation for GlidingAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - // TODO: remove glider trails completely - next.glider_trails = false; + next.glider_trails = true; let speednorm = velocity.magnitude().min(50.0) / 50.0; let slow = (acc_vel * 0.1).sin(); From ce98de4370c355a1f21e679ef4a5da87ddc45160 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Tue, 6 Feb 2024 14:25:47 +0200 Subject: [PATCH 22/22] Move debug_vectors_enabled flag to Scene --- client/src/lib.rs | 3 --- voxygen/src/scene/mod.rs | 5 ++++- voxygen/src/ui/egui/mod.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index fbc95e1905..112a04c2db 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -311,8 +311,6 @@ pub struct Client { flashing_lights_enabled: bool, - pub debug_vectors_enabled: bool, - /// Terrrain view distance server_view_distance_limit: Option, view_distance: Option, @@ -989,7 +987,6 @@ impl Client { dt_adjustment: 1.0, connected_server_constants: server_constants, - debug_vectors_enabled: false, }) } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 1dccfe9ab2..6db5454bd3 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -123,6 +123,8 @@ pub struct Scene { pub interpolated_time_of_day: Option, last_lightning: Option<(Vec3, f64)>, local_time: f64, + + pub debug_vectors_enabled: bool, } pub struct SceneData<'a> { @@ -362,6 +364,7 @@ impl Scene { interpolated_time_of_day: None, last_lightning: None, local_time: 0.0, + debug_vectors_enabled: false, } } @@ -1649,7 +1652,7 @@ impl Scene { lines.fluid_vel.take().map(|id| self.debug.remove_shape(id)); lines.wind.take().map(|id| self.debug.remove_shape(id)); lines.vel.take().map(|id| self.debug.remove_shape(id)); - if client.debug_vectors_enabled { + if self.debug_vectors_enabled { let ecs = client.state().ecs(); let vels = &ecs.read_component::(); diff --git a/voxygen/src/ui/egui/mod.rs b/voxygen/src/ui/egui/mod.rs index 2e440b12a9..7c44be44b5 100644 --- a/voxygen/src/ui/egui/mod.rs +++ b/voxygen/src/ui/egui/mod.rs @@ -103,7 +103,7 @@ impl EguiState { } }, EguiAction::SetShowDebugVector(enabled) => { - client.debug_vectors_enabled = enabled; + scene.debug_vectors_enabled = enabled; }, });