From 9ac75cb7d143c791822fa3ea3af6c92c2f5c92d2 Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Thu, 1 Feb 2024 17:25:10 +0200 Subject: [PATCH] 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,