Improve glider controls; physics-based heuristic for character ori

This commit is contained in:
Ludvig Böklin 2021-05-02 12:38:12 +02:00
parent 7721bbe999
commit b0619bbc3f
14 changed files with 896 additions and 752 deletions

999
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@ default = ["simd"]
[dependencies]
common-base = { package = "veloren-common-base", path = "base" }
# inline_tweak = "1.0.8"
inline_tweak = "1.0.8"
# Serde
serde = { version = "1.0.110", features = ["derive", "rc"] }

View File

@ -741,18 +741,13 @@ impl Component for Body {
}
impl Body {
pub fn aerodynamic_forces(
&self,
ori: Option<&Ori>,
rel_flow: &Vel,
fluid_density: f32,
) -> Vec3<f32> {
match (self, ori) {
(Body::BirdMedium(bird), Some(ori)) => bird_medium::FlyingBirdMedium::from((bird, ori))
pub fn aerodynamic_forces(&self, ori: &Ori, rel_flow: &Vel, fluid_density: f32) -> Vec3<f32> {
match self {
Body::BirdMedium(bird) => bird_medium::FlyingBirdMedium::from((bird, ori))
.aerodynamic_forces(rel_flow, fluid_density),
(Body::BirdLarge(bird), Some(ori)) => bird_large::FlyingBirdLarge::from((bird, ori))
Body::BirdLarge(bird) => bird_large::FlyingBirdLarge::from((bird, ori))
.aerodynamic_forces(rel_flow, fluid_density),
(Body::Dragon(not_bird), Some(ori)) => dragon::FlyingDragon::from((not_bird, ori))
Body::Dragon(not_bird) => dragon::FlyingDragon::from((not_bird, ori))
.aerodynamic_forces(rel_flow, fluid_density),
_ => self.drag(rel_flow, fluid_density),
}
@ -772,8 +767,8 @@ impl Drag for Body {
match self {
// Cross-section, head/feet first
Body::BipedLarge(_) | Body::BipedSmall(_) | Body::Golem(_) | Body::Humanoid(_) => {
const SCALE: f32 = 0.7;
let radius = self.dimensions().xy().map(|a| SCALE * a * 0.5);
// const SCALE: f32 = 0.7;
let radius = self.dimensions().xy().map(|a| inline_tweak::tweak!(0.6) * a * 0.5);
const CD: f32 = 0.7;
CD * PI * radius.x * radius.y
},

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
fluid_dynamics::{Drag, WingShape, WingState, Glide},
fluid_dynamics::{Drag, Glide, WingShape, WingState},
Ori,
},
make_case_elim, make_proj_elim,

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
fluid_dynamics::{Drag, WingShape, WingState, Glide},
fluid_dynamics::{Drag, Glide, WingShape, WingState},
Ori,
},
make_case_elim, make_proj_elim,

View File

@ -136,9 +136,13 @@ pub trait Drag: Clone {
}
}
/// An implementation of Glide is the ability for a winged body to glide in a
/// fixed-wing configuration.
///
/// NOTE: Wing (singular) implies the full span of a complete wing; a wing in
/// two parts (one on each side of a fuselage or other central body) is
/// considered a single wing.
pub trait Glide: Drag {
const STALL_ANGLE: f32 = PI * 0.1;
fn wing_shape(&self) -> &WingShape;
fn planform_area(&self) -> f32;
@ -147,67 +151,56 @@ pub trait Glide: Drag {
fn is_gliding(&self) -> bool;
/// Total lift coefficient for a finite wing of symmetric aerofoil shape and
/// elliptical pressure distribution. (Multiplied by reference area.)
fn lift_coefficient(&self, angle_of_attack: f32) -> f32 {
let aoa_abs = angle_of_attack.abs();
self.planform_area()
* if self.is_gliding() { 1.0 } else { 0.2 }
* if aoa_abs <= Self::STALL_ANGLE {
self.wing_shape().lift_slope() * angle_of_attack
} else {
// This is when flow separation and turbulence starts to kick in.
// Going to just make something up (based on some data), as the alternative is
// to just throw your hands up and return 0
let aoa_s = angle_of_attack.signum();
let c_l_max = self.wing_shape().lift_slope() * Self::STALL_ANGLE;
let deg_45 = PI / 4.0;
if aoa_abs < deg_45 {
// drop directly to 0.6 * max lift at stall angle
// then climb back to max at 45°
Lerp::lerp(0.6 * c_l_max, c_l_max, aoa_abs / deg_45) * aoa_s
} else {
// let's just say lift goes down linearly again until we're at 90°
Lerp::lerp(c_l_max, 0.0, (aoa_abs - deg_45) / deg_45) * aoa_s
}
}
}
/// Total drag coefficient (multiplied by reference area)
fn drag_coefficient(&self, aspect_ratio: f32, lift_coefficient: f32) -> f32 {
self.parasite_drag_coefficient() + induced_drag_coefficient(aspect_ratio, lift_coefficient)
}
fn aerodynamic_forces(&self, rel_flow: &Vel, fluid_density: f32) -> Vec3<f32> {
let v_sq = rel_flow.0.magnitude_squared();
if v_sq < 0.1 {
// don't bother with miniscule forces
Vec3::zero()
if self.is_gliding() {
let ori = self.ori();
let planform_area = self.planform_area();
let parasite_drag_coefficient = self.parasite_drag_coefficient();
let f = |flow_v: Vec3<f32>, wing_shape: &WingShape| -> Vec3<f32> {
let v_sq = flow_v.magnitude_squared();
if v_sq < std::f32::EPSILON {
Vec3::zero()
} else {
let freestream_dir = Dir::new(flow_v / v_sq.sqrt());
let ar = wing_shape.aspect_ratio();
// aoa will be positive when we're pitched up and negative otherwise
let aoa = angle_of_attack(ori, &freestream_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(
planform_area,
aoa,
wing_shape.lift_slope(),
wing_shape.stall_angle(),
);
let c_d = parasite_drag_coefficient + induced_drag_coefficient(ar, c_l);
0.5 * fluid_density
* v_sq
* (c_l * *lift_dir(ori, ar, c_l, aoa) + c_d * *freestream_dir)
}
};
let wing_shape = self.wing_shape();
let lateral = f(
rel_flow.0.projected(&Plane::from(self.ori().look_dir())),
&wing_shape.with_aspect_ratio(1.0 / wing_shape.aspect_ratio()),
);
let longitudinal = f(
rel_flow.0.projected(&Plane::from(self.ori().right())),
&wing_shape,
);
lateral + longitudinal
} else {
let q = 0.5 * fluid_density * v_sq;
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
if self.is_gliding() {
let ori = self.ori();
let ar = self.wing_shape().aspect_ratio();
// 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 = self.lift_coefficient(aoa);
let lift = q * c_l * *lift_dir(ori, ar, c_l, aoa);
let drag = q * self.drag_coefficient(ar, c_l) * *rel_flow_dir;
lift + drag
} else {
q * self.parasite_drag_coefficient() * *rel_flow_dir
}
self.drag(rel_flow, fluid_density)
}
}
}
pub fn lift_dir(ori: &Ori, aspect_ratio: f32, lift_coefficient: f32, angle_of_attack: f32) -> Dir {
fn lift_dir(ori: &Ori, aspect_ratio: f32, lift_coefficient: f32, angle_of_attack: f32) -> Dir {
// 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)
@ -226,16 +219,45 @@ pub fn lift_dir(ori: &Ori, aspect_ratio: f32, lift_coefficient: f32, angle_of_at
}
/// Geometric angle of attack
///
/// # Note
/// This ignores spanwise flow (i.e. we remove the spanwise flow component).
/// With greater yaw comes greater loss of accuracy as more flow goes
/// unaccounted for.
pub fn angle_of_attack(ori: &Ori, rel_flow_dir: &Dir) -> f32 {
rel_flow_dir
.projected(&Plane::from(ori.right()))
.map(|flow_dir| PI / 2.0 - ori.up().angle_between(flow_dir.to_vec()))
.unwrap_or(0.0)
if inline_tweak::tweak!(true) {
PI / 2.0 - ori.up().angle_between(rel_flow_dir.to_vec())
} else {
rel_flow_dir
.projected(&Plane::from(ori.right()))
.map(|flow_dir| PI / 2.0 - ori.up().angle_between(flow_dir.to_vec()))
.unwrap_or(0.0)
}
}
/// Total lift coefficient for a finite wing of symmetric aerofoil shape and
/// elliptical pressure distribution. (Multiplied by reference area.)
fn lift_coefficient(
planform_area: f32,
angle_of_attack: f32,
lift_slope: f32,
stall_angle: f32,
) -> f32 {
let aoa_abs = angle_of_attack.abs();
planform_area
* if aoa_abs <= stall_angle {
lift_slope * angle_of_attack
} else {
// This is when flow separation and turbulence starts to kick in.
// Going to just make something up (based on some data), as the alternative is
// to just throw your hands up and return 0
let aoa_s = angle_of_attack.signum();
let c_l_max = lift_slope * stall_angle;
let deg_45 = PI / 4.0;
if aoa_abs < deg_45 {
// drop directly to 0.6 * max lift at stall angle
// then climb back to max at 45°
Lerp::lerp(0.6 * c_l_max, c_l_max, aoa_abs / deg_45) * aoa_s
} else {
// let's just say lift goes down linearly again until we're at 90°
Lerp::lerp(c_l_max, 0.0, (aoa_abs - deg_45) / deg_45) * aoa_s
}
}
}
#[derive(Copy, Clone)]
@ -254,7 +276,22 @@ pub enum WingShape {
}
impl WingShape {
pub fn aspect_ratio(&self) -> f32 {
pub const fn stall_angle(&self) -> f32 {
// PI/10 or 18°
0.3141592653589793
}
pub const fn with_aspect_ratio(self, aspect_ratio: f32) -> Self {
match self {
Self::Elliptical { .. } => Self::Elliptical { aspect_ratio },
Self::Swept { angle, .. } => Self::Swept {
aspect_ratio,
angle,
},
}
}
pub const fn aspect_ratio(&self) -> f32 {
match self {
Self::Elliptical { aspect_ratio } => *aspect_ratio,
Self::Swept { aspect_ratio, .. } => *aspect_ratio,
@ -318,7 +355,7 @@ impl WingShape {
}
/// Induced drag coefficient (drag due to lift)
pub fn induced_drag_coefficient(aspect_ratio: f32, lift_coefficient: f32) -> f32 {
fn induced_drag_coefficient(aspect_ratio: f32, lift_coefficient: f32) -> f32 {
let ar = aspect_ratio;
if ar > 25.0 {
tracing::warn!(

View File

@ -2,7 +2,7 @@
pub const MAX_PICKUP_RANGE: f32 = 8.0;
pub const MAX_MOUNT_RANGE: f32 = 14.0;
pub const GRAVITY: f32 = 25.0;
pub const GRAVITY: f32 = 9.81;
pub const FRIC_GROUND: f32 = 0.15;
// Values for air taken from http://www-mdp.eng.cam.ac.uk/web/library/enginfo/aerothermal_dvd_only/aero/atmos/atmos.html

78
common/src/glider.rs Normal file
View File

@ -0,0 +1,78 @@
use crate::{
comp::{
fluid_dynamics::{Drag, Glide, WingShape},
Ori,
},
util::Dir,
};
use inline_tweak::tweak;
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
use vek::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Glider {
/// The aspect ratio is the ratio of the span squared to actual planform
/// area
pub wing_shape: WingShape,
pub planform_area: f32,
pub ori: Ori,
}
impl Glider {
/// A glider is modelled as an elliptical wing and has a span length
/// (distance from wing tip to wing tip) and a chord length (distance from
/// leading edge to trailing edge through its centre) measured in block
/// units.
///
/// https://en.wikipedia.org/wiki/Elliptical_wing
pub fn new(span_length: f32, chord_length: f32, ori: Ori) -> Self {
let planform_area = PI * chord_length * span_length * 0.25;
let aspect_ratio = span_length.powi(2) / planform_area;
Self {
wing_shape: WingShape::Elliptical { aspect_ratio },
planform_area,
ori,
}
}
pub fn roll(&mut self, angle_right: f32) { self.ori = self.ori.rolled_right(angle_right); }
pub fn pitch(&mut self, angle_up: f32) { self.ori = self.ori.pitched_up(angle_up); }
pub fn yaw(&mut self, angle_right: f32) { self.ori = self.ori.yawed_right(angle_right); }
pub fn slerp_roll_towards(&mut self, dir: Dir, s: f32) {
self.ori = self.ori.slerped_towards(
self.ori
.rolled_towards(if self.ori.up().dot(dir.to_vec()) < tweak!(0.0) {
Quaternion::rotation_3d(PI, self.ori.right()) * dir
} else {
dir
}),
s,
);
}
pub fn slerp_pitch_towards(&mut self, dir: Dir, s: f32) {
self.ori = self.ori.slerped_towards(self.ori.pitched_towards(dir), s);
}
pub fn slerp_yaw_towards(&mut self, dir: Dir, s: f32) {
self.ori = self.ori.slerped_towards(self.ori.yawed_towards(dir), s);
}
}
impl Drag for Glider {
fn parasite_drag_coefficient(&self) -> f32 { self.planform_area * 0.004 }
}
impl Glide for Glider {
fn wing_shape(&self) -> &WingShape { &self.wing_shape }
fn is_gliding(&self) -> bool { true }
fn planform_area(&self) -> f32 { self.planform_area }
fn ori(&self) -> &Ori { &self.ori }
}

View File

@ -44,6 +44,7 @@ pub mod explosion;
pub mod figure;
#[cfg(not(target_arch = "wasm32"))]
pub mod generation;
pub mod glider;
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
#[cfg(not(target_arch = "wasm32"))]
pub mod lottery;
@ -87,5 +88,6 @@ pub use combat::{DamageKind, DamageSource};
pub use comp::inventory::loadout_builder::LoadoutBuilder;
#[cfg(not(target_arch = "wasm32"))]
pub use explosion::{Explosion, RadiusEffect};
pub use glider::Glider;
#[cfg(not(target_arch = "wasm32"))]
pub use skillset_builder::SkillSetBuilder;

View File

@ -1,87 +1,103 @@
use super::utils::handle_climb;
use crate::{
comp::{
fluid_dynamics::{angle_of_attack, Drag, Glide, WingShape},
inventory::slot::EquipSlot,
CharacterState, Ori, StateUpdate, Vel,
},
comp::{inventory::slot::EquipSlot, CharacterState, ControllerInputs, Ori, StateUpdate, Vel},
glider::Glider,
states::behavior::{CharacterBehavior, JoinData},
util::{Dir, Plane, Projection},
util::Dir,
};
use inline_tweak::tweak;
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
use vek::*;
const PITCH_SLOW_TIME: f32 = 0.5;
const MAX_LIFT_DRAG_RATIO_AOA: f32 = PI * 0.04;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// The aspect ratio is the ratio of the span squared to actual planform
/// area
pub wing_shape: WingShape,
pub planform_area: f32,
pub ori: Ori,
last_vel: Vel,
pub glider: Glider,
last_ori: Ori,
timer: f32,
inputs_disabled: bool,
}
impl Data {
/// A glider is modelled as an elliptical wing and has a span length
/// (distance from wing tip to wing tip) and a chord length (distance from
/// leading edge to trailing edge through its centre) measured in block
/// units.
///
/// https://en.wikipedia.org/wiki/Elliptical_wing
pub fn new(span_length: f32, chord_length: f32, ori: Ori) -> Self {
let planform_area = PI * chord_length * span_length * 0.25;
let aspect_ratio = span_length.powi(2) / planform_area;
pub fn new(glider: Glider, ori: &Ori) -> Self {
Self {
wing_shape: WingShape::Elliptical { aspect_ratio },
planform_area,
ori,
last_vel: Vel::zero(),
last_ori: *ori,
timer: 0.0,
inputs_disabled: true,
glider,
}
}
fn tgt_dir(&self, data: &JoinData) -> Dir {
let move_dir = if self.inputs_disabled {
Vec2::zero()
} else {
data.inputs.move_dir
};
let look_ori = Ori::from(data.inputs.look_dir);
look_ori
.yawed_right(PI / 3.0 * look_ori.right().xy().dot(move_dir))
.pitched_up(PI * 0.04)
.pitched_down(
data.inputs
.look_dir
.xy()
.try_normalized()
.map_or(0.0, |ld| {
PI * 0.1 * ld.dot(move_dir) * self.timer.min(PITCH_SLOW_TIME)
/ PITCH_SLOW_TIME
}),
)
.look_dir()
fn pitch_input(&self, inputs: &ControllerInputs) -> Option<f32> {
inputs
.look_dir
.xy()
.try_normalized()
.map(|look_dir2| -look_dir2.dot(inputs.move_dir))
.map(|pitch| pitch * self.pitch_modifier())
.filter(|pitch| pitch.abs() > std::f32::EPSILON)
}
}
impl Drag for Data {
fn parasite_drag_coefficient(&self) -> f32 { self.planform_area * 0.004 }
}
fn roll_input(&self, inputs: &ControllerInputs) -> Option<f32> {
Some(Ori::from(inputs.look_dir).right().xy().dot(inputs.move_dir) * self.roll_modifier())
.filter(|roll| roll.abs() > std::f32::EPSILON)
}
impl Glide for Data {
fn wing_shape(&self) -> &WingShape { &self.wing_shape }
fn pitch_modifier(&self) -> f32 {
if self.inputs_disabled {
0.0
} else {
tweak!(1.0) * self.timer.min(PITCH_SLOW_TIME) / PITCH_SLOW_TIME
}
}
fn is_gliding(&self) -> bool { true }
fn roll_modifier(&self) -> f32 {
if self.inputs_disabled {
0.0
} else {
tweak!(1.0)
}
}
fn planform_area(&self) -> f32 { self.planform_area }
fn tgt_dir(&self, default_pitch: f32, max_pitch: f32, data: &JoinData) -> Dir {
let char_fw = data.ori.look_dir();
if data.inputs.look_dir.dot(*char_fw) > max_pitch.cos() {
Quaternion::rotation_3d(default_pitch, Ori::from(data.inputs.look_dir).right())
* data.inputs.look_dir
} else {
char_fw
.cross(*data.inputs.look_dir)
.try_normalized()
.map(|axis| Quaternion::rotation_3d(max_pitch, axis) * char_fw)
.unwrap_or_else(|| data.ori.up())
}
}
fn ori(&self) -> &Ori { &self.ori }
fn tgt_up(&self, max_roll: f32, tgt_dir: &Dir, flow_dir: &Dir, data: &JoinData) -> Dir {
let char_up = data.ori.up();
Dir::from_unnormalized(tgt_dir.to_vec() + flow_dir.to_vec())
.map(|tgt_lift_dir| {
tgt_lift_dir.slerped_to(
-*flow_dir,
Dir::from_unnormalized(data.vel.0)
.map_or(0.0, |moving_dir| moving_dir.dot(flow_dir.to_vec()).max(0.0)),
)
})
.and_then(|d| {
if d.dot(*char_up) > max_roll.cos() {
Some(d)
} else {
char_up
.cross(*d)
.try_normalized()
.map(|axis| Quaternion::rotation_3d(max_roll, axis) * char_up)
}
})
.unwrap_or(char_up)
}
}
impl CharacterBehavior for Data {
@ -100,110 +116,79 @@ impl CharacterBehavior for Data {
update.character = CharacterState::Idle;
update.ori = update.ori.to_horizontal();
} else if !handle_climb(&data, &mut update) {
// Tweaks
let def_pitch = MAX_LIFT_DRAG_RATIO_AOA * tweak!(2.0);
let max_pitch = tweak!(0.2) * PI;
let max_roll = tweak!(0.30) * PI;
let inputs_rate = tweak!(4.0);
let look_pitch_rate = tweak!(5.0);
let autoroll_rate = tweak!(5.0);
let rot_with_char = tweak!(false);
let yaw_correction_rate = tweak!(1.0);
let char_yaw_follow_rate = tweak!(2.0);
// ----
let air_flow = data
.physics
.in_fluid
.map(|fluid| fluid.relative_flow(data.vel))
.unwrap_or_default();
.unwrap_or_else(|| Vel(Vec3::unit_z()));
let flow_dir = Dir::from_unnormalized(air_flow.0).unwrap_or_else(Dir::up);
let tgt_dir = self.tgt_dir(def_pitch, max_pitch, data);
let inputs_disabled = self.inputs_disabled && !data.inputs.move_dir.is_approx_zero();
let char_up = data.ori.up();
let ori = {
let slerp_s = {
let angle = self.ori.look_dir().angle_between(*data.inputs.look_dir);
let rate = 0.4 * PI / angle;
(data.dt.0 * rate).min(1.0)
let mut glider = self.glider;
if rot_with_char {
glider.ori = glider
.ori
.prerotated(self.last_ori.up().rotation_between(char_up));
}
{
let glider_up = glider.ori.up();
let d = glider_up.dot(*char_up);
let cap = |max_ang: f32| -> Option<f32> {
(d - max_ang.cos()).is_sign_positive().then_some(max_ang)
};
Dir::from_unnormalized(air_flow.0)
.map(|flow_dir| {
let tgt_dir = self.tgt_dir(data);
let tgt_dir_ori = Ori::from(tgt_dir);
let tgt_dir_up = tgt_dir_ori.up();
// The desired up vector of our glider.
// We begin by projecting the flow dir on the plane with the normal of
// our tgt_dir to get an idea of how it will hit the glider
let tgt_up = flow_dir
.projected(&Plane::from(tgt_dir))
.map(|d| {
let d = if d.dot(*tgt_dir_up).is_sign_negative() {
// when the final direction of flow is downward we don't roll
// upside down but instead mirror the target up vector
Quaternion::rotation_3d(PI, *tgt_dir_ori.right()) * d
} else {
d
};
// slerp from untilted up towards the direction by a factor of
// lateral wind to prevent overly reactive adjustments
let lateral_wind_speed =
air_flow.0.projected(&self.ori.right()).magnitude();
tgt_dir_up.slerped_to(d, lateral_wind_speed / 15.0)
})
.unwrap_or_else(Dir::up);
let global_roll = tgt_dir_up.rotation_between(tgt_up);
let global_pitch = angle_of_attack(&tgt_dir_ori, &flow_dir)
* self.timer.min(PITCH_SLOW_TIME)
/ PITCH_SLOW_TIME;
if let Some(roll_input) = self.roll_input(data.inputs) {
if (d - max_roll.cos()).is_sign_positive()
|| (glider.ori.right().dot(*char_up).is_sign_positive()
== roll_input.is_sign_positive())
{
glider.roll(data.dt.0 * inputs_rate * roll_input * max_roll);
}
} else {
glider.slerp_roll_towards(
self.tgt_up(max_roll, &tgt_dir, &flow_dir, data),
autoroll_rate * data.dt.0,
);
}
self.ori.slerped_towards(
tgt_dir_ori.prerotated(global_roll).pitched_up(global_pitch),
slerp_s,
)
})
.unwrap_or_else(|| self.ori.slerped_towards(self.ori.uprighted(), slerp_s))
};
if let Some(pitch_input) = self.pitch_input(data.inputs) {
if (d - max_pitch.cos()).is_sign_positive()
|| (glider.ori.look_dir().dot(*char_up).is_sign_negative()
== pitch_input.is_sign_positive())
{
glider.pitch(data.dt.0 * inputs_rate * pitch_input * max_pitch);
}
}
glider.slerp_pitch_towards(tgt_dir, look_pitch_rate * data.dt.0);
}
update.ori = {
let slerp_s = {
let angle = data.ori.look_dir().angle_between(*data.inputs.look_dir);
let rate = 0.2 * data.body.base_ori_rate() * PI / angle;
(data.dt.0 * rate).min(1.0)
};
glider.slerp_yaw_towards(-flow_dir, data.dt.0 * yaw_correction_rate);
update.ori = data.ori.slerped_towards(
data.ori.yawed_towards(glider.ori.look_dir()),
data.dt.0 * char_yaw_follow_rate,
);
let rot_from_drag = {
let speed_factor =
air_flow.0.magnitude_squared().min(40_f32.powi(2)) / 40_f32.powi(2);
Quaternion::rotation_3d(
-PI / 2.0 * speed_factor,
ori.up()
.cross(air_flow.0)
.try_normalized()
.unwrap_or_else(|| *data.ori.right()),
)
};
let rot_from_accel = {
let accel = data.vel.0 - self.last_vel.0;
let accel_factor = accel.magnitude_squared().min(1.0) / 1.0;
Quaternion::rotation_3d(
PI / 2.0
* accel_factor
* if data.physics.on_ground.is_some() {
-1.0
} else {
1.0
},
ori.up()
.cross(accel)
.try_normalized()
.unwrap_or_else(|| *data.ori.right()),
)
};
update.ori.slerped_towards(
ori.to_horizontal()
.prerotated(rot_from_drag * rot_from_accel),
slerp_s,
)
};
update.character = CharacterState::Glide(Self {
ori,
last_vel: *data.vel,
last_ori: update.ori,
timer: self.timer + data.dt.0,
inputs_disabled,
..*self
inputs_disabled: self.inputs_disabled && !data.inputs.move_dir.is_approx_zero(),
glider,
});
} else {
update.ori = update.ori.to_horizontal();

View File

@ -1,6 +1,7 @@
use super::utils::*;
use crate::{
comp::{slot::EquipSlot, CharacterState, InventoryAction, StateUpdate},
glider::Glider,
states::{
behavior::{CharacterBehavior, JoinData},
glide,
@ -21,11 +22,12 @@ impl CharacterBehavior for Data {
// If not on the ground while wielding glider enter gliding state
if data.physics.on_ground.is_none() {
update.character = CharacterState::Glide(glide::Data::new(
let glider = Glider::new(
data.body.dimensions().z * 3.0,
data.body.dimensions().z / 3.0,
*data.ori,
));
);
update.character = CharacterState::Glide(glide::Data::new(glider, data.ori));
}
if data
.physics

View File

@ -30,4 +30,4 @@ slab = "0.4.2"
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "derive"], rev = "f985bec5d456f7b0dd8aae99848f9473c2cd9d46" }
# Tweak running code
# inline_tweak = { version = "1.0.8", features = ["release_tweak"] }
inline_tweak = { version = "1.0.8", features = ["release_tweak"] }

View File

@ -11,11 +11,12 @@ use common::{
resources::DeltaTime,
terrain::{Block, TerrainGrid},
uid::Uid,
util::{Projection, SpatialGrid},
util::{Dir, Projection, SpatialGrid},
vol::{BaseVol, ReadVol},
};
use common_base::{prof_span, span};
use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
use inline_tweak::tweak;
use rayon::iter::ParallelIterator;
use specs::{
shred::{ResourceId, World},
@ -38,18 +39,59 @@ fn fluid_density(height: f32, fluid: &Fluid) -> Density {
Density(fluid.density().0 * immersion + AIR_DENSITY * (1.0 - immersion))
}
fn integrate_glider_forces(
dt: &DeltaTime,
dv: Vec3<f32>,
vel: &mut Vel,
ori: &mut Ori,
mass: &Mass,
character_state: &CharacterState,
rel_wind: &Vel,
) -> Option<()> {
let glider = match character_state {
CharacterState::Glide(glider) => Some(glider.glider),
_ => None,
}?;
let dv_ = Some(dt.0 * glider.aerodynamic_forces(&rel_wind, AIR_DENSITY))
.filter(|imp| !imp.is_approx_zero())
.map(|imp| imp / mass.0 - dv)?;
vel.0 += dv_;
{
let glider_dir = ori.up();
let glider_dist = tweak!(2.5);
let glider_pos = *glider_dir * glider_dist;
if let Some(rot) = Dir::from_unnormalized(glider_pos + dv_)
.map(|u| {
let s = glider_dir.dot(*u).powf(tweak!(10.0) * dt.0);
glider_dir.slerped_to(
u,
tweak!(-0.7) + tweak!(1.0) * s.max(0.0) + tweak!(0.0) * s.min(0.0),
)
})
.map(|u| glider_dir.rotation_between(u))
{
*ori = ori.prerotated(rot);
}
}
Some(())
}
#[allow(clippy::too_many_arguments)]
fn integrate_forces(
dt: &DeltaTime,
mut vel: Vel,
ori: Option<&Ori>,
vel: &mut Vel,
ori: &mut Ori,
body: &Body,
character_state: Option<&CharacterState>,
density: &Density,
mass: &Mass,
fluid: &Fluid,
gravity: f32,
) -> Vel {
) {
let dim = body.dimensions();
let height = dim.z;
let rel_flow = fluid.relative_flow(&vel);
@ -60,15 +102,7 @@ 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());
let aerodynamic_forces = body.aerodynamic_forces(ori, &rel_flow, fluid_density.0)
+ match character_state {
Some(CharacterState::Glide(glider)) => {
glider.aerodynamic_forces(&rel_flow, fluid_density.0)
},
_ => Vec3::zero(),
};
// let impulse = dt.0 * body.aerodynamic_forces(ori, &rel_flow,
// fluid_density.0);
let aerodynamic_forces = body.aerodynamic_forces(ori, &rel_flow, fluid_density.0);
debug_assert!(!aerodynamic_forces.map(|a| a.is_nan()).reduce_or());
if !aerodynamic_forces.is_approx_zero() {
let new_v = vel.0 + dt.0 * aerodynamic_forces / mass.0;
@ -84,6 +118,9 @@ fn integrate_forces(
} else {
vel.0 = new_v;
}
character_state.and_then(|cs| {
integrate_glider_forces(dt, new_v - vel.0, vel, ori, mass, cs, &rel_flow)
});
};
debug_assert!(!vel.0.map(|a| a.is_nan()).reduce_or());
};
@ -91,8 +128,6 @@ fn integrate_forces(
// Hydrostatic/aerostatic forces
// modify gravity to account for the effective density as a result of buoyancy
vel.0.z -= dt.0 * gravity * (density.0 - fluid_density.0) / density.0;
vel
}
fn calc_z_limit(
@ -572,7 +607,11 @@ impl<'a> PhysicsData<'a> {
// Apply movement inputs
span!(guard, "Apply movement");
let (positions, velocities) = (&write.positions, &mut write.velocities);
let (positions, velocities, orientations) = (
&write.positions,
&mut write.velocities,
&mut write.orientations,
);
// First pass: update velocity using air resistance and gravity for each entity.
// We do this in a first pass because it helps keep things more stable for
@ -585,7 +624,7 @@ impl<'a> PhysicsData<'a> {
read.character_states.maybe(),
&write.physics_states,
&read.masses,
write.orientations.maybe(),
orientations,
&read.densities,
!&read.mountings,
)
@ -628,9 +667,9 @@ impl<'a> PhysicsData<'a> {
vel.0.z -= dt.0 * GRAVITY;
},
Some(fluid) => {
vel.0 = integrate_forces(
integrate_forces(
&dt,
*vel,
vel,
ori,
body,
character_state,
@ -638,8 +677,7 @@ impl<'a> PhysicsData<'a> {
mass,
&fluid,
GRAVITY,
)
.0
);
},
}
}
@ -680,7 +718,6 @@ impl<'a> PhysicsData<'a> {
&read.colliders,
positions,
velocities,
orientations,
read.bodies.maybe(),
read.character_states.maybe(),
&mut write.physics_states,
@ -703,7 +740,6 @@ impl<'a> PhysicsData<'a> {
collider,
pos,
vel,
_ori,
body,
character_state,
mut physics_state,

View File

@ -1491,7 +1491,7 @@ impl FigureMgr {
CharacterState::Glide(data) => {
anim::character::GlidingAnimation::update_skeleton(
&target_base,
(rel_vel, ori, data.ori.into(), time, state.acc_vel),
(rel_vel, ori, data.glider.ori.into(), time, state.acc_vel),
state.state_time,
&mut state_animation_rate,
skeleton_attr,