Extract wind simulation to its own function

This commit is contained in:
juliancoffee 2024-02-01 17:25:10 +02:00
parent aa97bd6bf6
commit 9ac75cb7d1

View File

@ -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<Option<Vec3<f32>>, ()> {
prof_span!(guard, "Apply Weather INIT");
let pos_2d = pos.0.as_().xy();
let chunk_pos: Vec2<i32> = 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::<Vec<_>>();
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<i32> = 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::<Vec<_>>();
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,