Simplified replacement drag logic, special-case volume entities to improve movement

This commit is contained in:
Joshua Barretto 2023-05-23 17:29:36 +01:00
parent 7b4bb2de99
commit 610d1d8497
5 changed files with 40 additions and 110 deletions

View File

@ -69,9 +69,9 @@ impl Body {
match self {
Body::DefaultAirship | Body::Volume => Vec3::new(25.0, 50.0, 40.0),
Body::AirBalloon => Vec3::new(25.0, 50.0, 40.0),
Body::SailBoat => Vec3::new(12.0, 17.0, 6.0),
Body::Galleon => Vec3::new(14.0, 32.0, 10.0),
Body::Skiff => Vec3::new(7.0, 32.0, 10.0),
Body::SailBoat => Vec3::new(12.0, 32.0, 6.0),
Body::Galleon => Vec3::new(14.0, 48.0, 10.0),
Body::Skiff => Vec3::new(7.0, 15.0, 10.0),
}
}

View File

@ -211,81 +211,11 @@ impl Body {
}
}
/// Physically incorrect (but relatively dt-independent) way to calculate drag coefficients for liquids.
/// Physically incorrect (but relatively dt-independent) way to calculate
/// drag coefficients for liquids.
// TODO: Remove this in favour of `aerodynamic_forces` (see: `integrate_forces`)
pub fn drag_coefficient_liquid(
&self,
rel_flow: &Vel,
fluid_density: f32,
wings: Option<&Wings>,
scale: f32,
) -> Vec3<f32> {
let v_sq = rel_flow.0.magnitude_squared();
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
0.5 * fluid_density
* match wings {
Some(&Wings {
aspect_ratio,
planform_area,
ori,
}) => {
if aspect_ratio > 25.0 {
tracing::warn!(
"Calculating lift for wings with an aspect ratio of {}. The \
formulas are only valid for aspect ratios below 25.",
aspect_ratio
)
};
let ar = aspect_ratio.min(24.0);
// We have an elliptical wing; proceed to calculate its lift and drag
// aoa will be positive when we're pitched up and negative otherwise
let aoa = angle_of_attack(&ori, &rel_flow_dir);
// c_l will be positive when aoa is positive (we have positive lift,
// producing an upward force) and negative otherwise
let c_l = lift_coefficient(ar, aoa);
// lift dir will be orthogonal to the local relative flow vector.
// Local relative flow is the resulting vector of (relative) freestream
// flow + downwash (created by the vortices
// of the wing tips)
let lift_dir: Dir = {
// induced angle of attack
let aoa_i = c_l / (PI * ar);
// effective angle of attack; the aoa as seen by aerofoil after
// downwash
let aoa_eff = aoa - aoa_i;
// Angle between chord line and local relative wind is aoa_eff
// radians. Direction of lift is
// perpendicular to local relative wind.
// At positive lift, local relative wind will be below our cord line
// at an angle of aoa_eff. Thus if
// we pitch down by aoa_eff radians then
// our chord line will be colinear with local relative wind vector
// and our up will be the direction
// of lift.
ori.pitched_down(aoa_eff).up()
};
// induced drag coefficient (drag due to lift)
let cdi = {
// Oswald's efficiency factor (empirically derived--very magical)
// (this definition should not be used for aspect ratios > 25)
let e = 1.78 * (1.0 - 0.045 * ar.powf(0.68)) - 0.64;
c_l.powi(2) / (PI * e * ar)
};
// drag coefficient
let c_d = zero_lift_drag_coefficient() + cdi;
debug_assert!(c_d.is_sign_positive());
debug_assert!(c_l.is_sign_positive() || aoa.is_sign_negative());
planform_area * scale.powf(2.0) * (c_l * *lift_dir + c_d * *rel_flow_dir)
+ self.parasite_drag(scale).powf(0.75) * *rel_flow_dir
},
_ => self.parasite_drag(scale).powf(0.75) * *rel_flow_dir,
}
pub fn drag_coefficient_liquid(&self, fluid_density: f32, scale: f32) -> f32 {
fluid_density * self.parasite_drag(scale)
}
/// Parasite drag is the sum of pressure drag and skin friction.

View File

@ -220,8 +220,8 @@ impl Body {
quadruped_low::Species::Mossdrake => 1.7,
_ => 2.0,
},
Body::Ship(ship) if ship.has_water_thrust() => 0.35,
Body::Ship(_) => 0.12,
Body::Ship(ship) if ship.has_water_thrust() => 5.0 / self.dimensions().y,
Body::Ship(_) => 6.0 / self.dimensions().y,
Body::Arthropod(_) => 3.5,
}
}
@ -236,15 +236,15 @@ impl Body {
match self {
Body::Object(_) => return None,
Body::ItemDrop(_) => return None,
Body::Ship(ship) if ship.has_water_thrust() => 350.0 * self.mass().0,
Body::Ship(ship) if ship.has_water_thrust() => 500.0 * self.mass().0,
Body::Ship(_) => return None,
Body::BipedLarge(_) => 120.0 * self.mass().0,
Body::Golem(_) => 100.0 * self.mass().0,
Body::BipedSmall(_) => 1000.0 * self.mass().0,
Body::BirdMedium(_) => 400.0 * self.mass().0,
Body::BirdLarge(_) => 400.0 * self.mass().0,
Body::FishMedium(_) => 900.0 * self.mass().0,
Body::FishSmall(_) => 1000.0 * self.mass().0,
Body::FishMedium(_) => 200.0 * self.mass().0,
Body::FishSmall(_) => 300.0 * self.mass().0,
Body::Dragon(_) => 50.0 * self.mass().0,
// Humanoids are a bit different: we try to give them thrusts that result in similar
// speeds for gameplay reasons
@ -844,7 +844,7 @@ pub fn handle_climb(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
.map(|depth| depth > 1.0)
.unwrap_or(false)
//&& update.vel.0.z < 0.0
// *All* entities can climb when in liquids, to let them get out of
// *All* entities can climb when in liquids, to let them climb out near the surface
&& (data.body.can_climb() || data.physics.in_liquid().is_some())
&& update.energy.current() > 1.0
{

View File

@ -11,7 +11,7 @@ use common::{
link::Is,
mounting::{Rider, VolumeRider},
outcome::Outcome,
resources::DeltaTime,
resources::{DeltaTime, GameMode},
states,
terrain::{Block, BlockKind, TerrainGrid},
uid::Uid,
@ -62,20 +62,23 @@ fn integrate_forces(
// Aerodynamic/hydrodynamic forces
if !rel_flow.0.is_approx_zero() {
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
// HACK: We should really use the latter logic (i.e: `aerodynamic_forces`) for liquids, but it results in
// pretty serious dt-dependent problems that are extremely difficult to resolve. This is a compromise: for
// liquids only, we calculate drag using an incorrect (but still visually plausible) model that is much more
// resistant to differences in dt. Because it's physically incorrect anyway, there are magic coefficients that
// HACK: We should really use the latter logic (i.e: `aerodynamic_forces`) for
// liquids, but it results in pretty serious dt-dependent problems that
// are extremely difficult to resolve. This is a compromise: for liquids
// only, we calculate drag using an incorrect (but still visually plausible)
// model that is much more resistant to differences in dt. Because it's
// physically incorrect anyway, there are magic coefficients that
// exist simply to get us closer to what water 'should' feel like.
if fluid.is_liquid() {
let fric = body.drag_coefficient_liquid(
&rel_flow,
fluid_density.0,
wings,
scale.map_or(1.0, |s| s.0),
).magnitude() * 0.02;
let fric = body
.drag_coefficient_liquid(fluid_density.0, scale.map_or(1.0, |s| s.0))
.powf(0.75)
* 0.02;
vel.0 *= (1.0 / (1.0 + fric)).powf(dt.0 * 10.0);
let fvel = fluid.flow_vel();
// Drag is relative to fluid velocity, so compensate before applying drag
vel.0 = (vel.0 - fvel.0) * (1.0 / (1.0 + fric)).powf(dt.0 * 10.0) + fvel.0;
} else {
let impulse = dt.0
* body.aerodynamic_forces(
@ -132,6 +135,7 @@ pub struct PhysicsRead<'a> {
terrain: ReadExpect<'a, TerrainGrid>,
dt: Read<'a, DeltaTime>,
event_bus: Read<'a, EventBus<ServerEvent>>,
game_mode: ReadExpect<'a, GameMode>,
scales: ReadStorage<'a, Scale>,
stickies: ReadStorage<'a, Sticky>,
immovables: ReadStorage<'a, Immovable>,
@ -667,6 +671,12 @@ impl<'a> PhysicsData<'a> {
if in_loaded_chunk
// And not already stuck on a block (e.g., for arrows)
&& !(physics_state.on_surface().is_some() && sticky.is_some())
// HACK: Special-case boats. Experimentally, clients are *bad* at making guesses about movement,
// and this is a particular problem for volume entities since careful control of velocity is
// required for nice movement of entities on top of the volume. Special-case volume entities here
// to prevent weird drag/gravity guesses messing with movement, relying on the client's hermite
// interpolation instead.
&& !(matches!(body, Body::Ship(_)) && matches!(&*read.game_mode, GameMode::Client))
{
// Clamp dt to an effective 10 TPS, to prevent gravity
// from slamming the players into the floor when
@ -1748,7 +1758,9 @@ fn box_voxel_collision<T: BaseVol<Vox = Block> + ReadVol>(
(old_depth + old_pos.z - pos.0.z).max(depth)
});
physics_state.ground_vel = ground_vel;
if depth > 0.0 {
physics_state.ground_vel = ground_vel;
}
Fluid::Liquid {
kind,

View File

@ -1651,13 +1651,7 @@ fn handle_spawn_airship(
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
flicker: 1.0,
animated: true,
});
.create_ship(pos, ori, ship, |ship| ship.make_collider());
if let Some(pos) = destination {
let (kp, ki, kd) =
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));
@ -1695,13 +1689,7 @@ fn handle_spawn_ship(
let ori = comp::Ori::from(common::util::Dir::new(dir.unwrap_or(Vec3::unit_y())));
let mut builder = server
.state
.create_ship(pos, ori, ship, |ship| ship.make_collider())
.with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0,
flicker: 1.0,
animated: true,
});
.create_ship(pos, ori, ship, |ship| ship.make_collider());
if let Some(pos) = destination {
let (kp, ki, kd) =
comp::agent::pid_coefficients(&comp::Body::Ship(ship)).unwrap_or((1.0, 0.0, 0.0));