mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Improve glider controls; physics-based heuristic for character ori
This commit is contained in:
parent
7721bbe999
commit
b0619bbc3f
999
Cargo.lock
generated
999
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"] }
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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!(
|
||||
|
@ -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
78
common/src/glider.rs
Normal 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 }
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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"] }
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user