mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Glider physics
This commit is contained in:
parent
de25358ab7
commit
34660462e9
@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- New large birds npcs
|
- New large birds npcs
|
||||||
- Day period dependant wildlife spawns
|
- Day period dependant wildlife spawns
|
||||||
- You can now block and parry with melee weapons
|
- You can now block and parry with melee weapons
|
||||||
|
- Lift is now calculated for gliders based on dimensions (currently same for all)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -81,12 +82,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Projectiles now generally have a different arc because they no longer have their own gravity modifier
|
- Projectiles now generally have a different arc because they no longer have their own gravity modifier
|
||||||
- Increased agent system target search efficiency speeding up the server
|
- Increased agent system target search efficiency speeding up the server
|
||||||
- Added more parallelization to terrain serialization and removed extra cloning speeding up the server
|
- Added more parallelization to terrain serialization and removed extra cloning speeding up the server
|
||||||
|
- Energy now recharges while gliding
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed command: "debug", use "/kit debug" instead
|
- Removed command: "debug", use "/kit debug" instead
|
||||||
- Gravity component has been removed
|
- Gravity component has been removed
|
||||||
- In-air movement has been removed
|
- In-air movement has been removed
|
||||||
|
- Energy cost of deploying the glider has been removed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -5,51 +5,51 @@
|
|||||||
),
|
),
|
||||||
map: {
|
map: {
|
||||||
"Starter": (
|
"Starter": (
|
||||||
vox_spec: ("glider.glider_starter", (-15.0, -5.0, -5.0)),
|
vox_spec: ("glider.glider_starter", (-15.0, -5.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"PlainCloth": (
|
"PlainCloth": (
|
||||||
vox_spec: ("glider.glider_basic_white", (-25.0, -20.0, -5.0)),
|
vox_spec: ("glider.glider_basic_white", (-25.0, -20.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"RedCloth": (
|
"RedCloth": (
|
||||||
vox_spec: ("glider.glider_basic_red", (-25.0, -20.0, -5.0)),
|
vox_spec: ("glider.glider_basic_red", (-25.0, -20.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"Blue0": (
|
"Blue0": (
|
||||||
vox_spec: ("glider.glider_blue", (-26.0, -26.0, -5.0)),
|
vox_spec: ("glider.glider_blue", (-26.0, -26.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"ButterflyMorpho": (
|
"ButterflyMorpho": (
|
||||||
vox_spec: ("glider.glider_butterfly1", (-26.0, -13.0, -5.0)),
|
vox_spec: ("glider.glider_butterfly1", (-26.0, -13.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"ButterflyMonarch": (
|
"ButterflyMonarch": (
|
||||||
vox_spec: ("glider.glider_butterfly2", (-26.0, -13.0, -5.0)),
|
vox_spec: ("glider.glider_butterfly2", (-26.0, -13.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"MothLuna": (
|
"MothLuna": (
|
||||||
vox_spec: ("glider.glider_moth", (-26.0, -22.0, -5.0)),
|
vox_spec: ("glider.glider_moth", (-26.0, -22.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"SandRaptor": (
|
"SandRaptor": (
|
||||||
vox_spec: ("glider.glider_sandraptor", (-26.0, -25.0, -5.0)),
|
vox_spec: ("glider.glider_sandraptor", (-26.0, -25.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"SnowRaptor": (
|
"SnowRaptor": (
|
||||||
vox_spec: ("glider.glider_snowraptor", (-26.0, -25.0, -5.0)),
|
vox_spec: ("glider.glider_snowraptor", (-26.0, -25.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"WoodRaptor": (
|
"WoodRaptor": (
|
||||||
vox_spec: ("glider.glider_woodraptor", (-26.0, -25.0, -5.0)),
|
vox_spec: ("glider.glider_woodraptor", (-26.0, -25.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"Purple0": (
|
"Purple0": (
|
||||||
vox_spec: ("glider.glider_cultists", (-26.0, -16.0, -5.0)),
|
vox_spec: ("glider.glider_cultists", (-26.0, -26.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
"Leaves": (
|
"Leaves": (
|
||||||
vox_spec: ("glider.glider_leaves", (-26.0, -26.0, -5.0)),
|
vox_spec: ("glider.glider_leaves", (-26.0, -26.0, 0.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -1224,7 +1224,7 @@ impl Client {
|
|||||||
.map(|cs| {
|
.map(|cs| {
|
||||||
matches!(
|
matches!(
|
||||||
cs,
|
cs,
|
||||||
comp::CharacterState::GlideWield | comp::CharacterState::Glide
|
comp::CharacterState::GlideWield | comp::CharacterState::Glide(_)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ pub enum CharacterState {
|
|||||||
Dance,
|
Dance,
|
||||||
Talk,
|
Talk,
|
||||||
Sneak,
|
Sneak,
|
||||||
Glide,
|
Glide(glide::Data),
|
||||||
GlideWield,
|
GlideWield,
|
||||||
/// A stunned state
|
/// A stunned state
|
||||||
Stunned(stunned::Data),
|
Stunned(stunned::Data),
|
||||||
@ -173,7 +173,7 @@ impl CharacterState {
|
|||||||
CharacterState::Climb(_)
|
CharacterState::Climb(_)
|
||||||
| CharacterState::Equipping(_)
|
| CharacterState::Equipping(_)
|
||||||
| CharacterState::Dance
|
| CharacterState::Dance
|
||||||
| CharacterState::Glide
|
| CharacterState::Glide(_)
|
||||||
| CharacterState::GlideWield
|
| CharacterState::GlideWield
|
||||||
| CharacterState::Talk
|
| CharacterState::Talk
|
||||||
| CharacterState::Roll(_),
|
| CharacterState::Roll(_),
|
||||||
|
@ -17,7 +17,6 @@ pub struct Energy {
|
|||||||
pub enum EnergySource {
|
pub enum EnergySource {
|
||||||
Ability,
|
Ability,
|
||||||
Climb,
|
Climb,
|
||||||
Glide,
|
|
||||||
LevelUp,
|
LevelUp,
|
||||||
HitEnemy,
|
HitEnemy,
|
||||||
Regen,
|
Regen,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use super::{
|
use super::{
|
||||||
body::{object, Body},
|
body::{object, Body},
|
||||||
Density, Vel,
|
CharacterState, Density, Ori, Vel,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{AIR_DENSITY, WATER_DENSITY},
|
consts::{AIR_DENSITY, WATER_DENSITY},
|
||||||
util::Dir,
|
util::{Dir, Plane, Projection},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
@ -88,7 +88,12 @@ impl Default for Fluid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
pub fn aerodynamic_forces(&self, rel_flow: &Vel, fluid_density: f32) -> Vec3<f32> {
|
pub fn aerodynamic_forces(
|
||||||
|
&self,
|
||||||
|
rel_flow: &Vel,
|
||||||
|
fluid_density: f32,
|
||||||
|
character_state: Option<&CharacterState>,
|
||||||
|
) -> Vec3<f32> {
|
||||||
let v_sq = rel_flow.0.magnitude_squared();
|
let v_sq = rel_flow.0.magnitude_squared();
|
||||||
if v_sq < 0.25 {
|
if v_sq < 0.25 {
|
||||||
// don't bother with miniscule forces
|
// don't bother with miniscule forces
|
||||||
@ -96,7 +101,72 @@ impl Body {
|
|||||||
} else {
|
} else {
|
||||||
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
|
let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt());
|
||||||
// All the coefficients come pre-multiplied by their reference area
|
// All the coefficients come pre-multiplied by their reference area
|
||||||
0.5 * fluid_density * v_sq * self.parasite_drag_coefficient() * *rel_flow_dir
|
0.5 * fluid_density
|
||||||
|
* v_sq
|
||||||
|
* character_state
|
||||||
|
.and_then(|cs| match cs {
|
||||||
|
CharacterState::Glide(data) => {
|
||||||
|
Some((data.aspect_ratio, data.planform_area, data.ori))
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.map(|(ar, area, ori)| {
|
||||||
|
if ar > 25.0 {
|
||||||
|
tracing::warn!(
|
||||||
|
"Calculating lift for wings with an aspect ratio of {}. The \
|
||||||
|
formulas are only valid for aspect ratios below 25.",
|
||||||
|
ar
|
||||||
|
)
|
||||||
|
};
|
||||||
|
(ar.min(24.0), area, ori)
|
||||||
|
})
|
||||||
|
.map(|(ar, area, ori)| {
|
||||||
|
// 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, area, 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()
|
||||||
|
};
|
||||||
|
|
||||||
|
// drag coefficient due to lift
|
||||||
|
let c_d = {
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
zero_lift_drag_coefficient(area)
|
||||||
|
+ self.parasite_drag_coefficient()
|
||||||
|
+ c_l.powi(2) / (PI * e * ar)
|
||||||
|
};
|
||||||
|
debug_assert!(c_d.is_sign_positive());
|
||||||
|
debug_assert!(c_l.is_sign_positive() || aoa.is_sign_negative());
|
||||||
|
|
||||||
|
c_l * *lift_dir + c_d * *rel_flow_dir
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| self.parasite_drag_coefficient() * *rel_flow_dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,24 +195,28 @@ impl Body {
|
|||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
cd * std::f32::consts::PI * dim.x * dim.z
|
cd * PI * dim.x * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
// Cross-section, zero-lift angle; exclude the wings (width * 0.2)
|
// Cross-section, zero-lift angle; exclude the wings (width * 0.2)
|
||||||
Body::BirdMedium(_) | Body::BirdLarge(_) | Body::Dragon(_) => {
|
Body::BirdMedium(_) | Body::BirdLarge(_) | Body::Dragon(_) => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5);
|
||||||
|
// "Field Estimates of Body Drag Coefficient on the Basis of Dives in Passerine
|
||||||
|
// Birds", Anders Hedenström and Felix Liechti, 2001
|
||||||
let cd = match self {
|
let cd = match self {
|
||||||
Body::BirdMedium(_) => 0.2,
|
Body::BirdLarge(_) | Body::BirdMedium(_) => 0.2,
|
||||||
Body::BirdLarge(_) => 0.4,
|
// arbitrary
|
||||||
_ => 0.7,
|
_ => 0.7,
|
||||||
};
|
};
|
||||||
cd * std::f32::consts::PI * dim.x * 0.2 * dim.z
|
cd * PI * dim.x * 0.2 * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
// Cross-section, zero-lift angle; exclude the fins (width * 0.2)
|
// Cross-section, zero-lift angle; exclude the fins (width * 0.2)
|
||||||
Body::FishMedium(_) | Body::FishSmall(_) => {
|
Body::FishMedium(_) | Body::FishSmall(_) => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5);
|
||||||
0.031 * std::f32::consts::PI * dim.x * 0.2 * dim.z
|
// "A Simple Method to Determine Drag Coefficients in Aquatic Animals",
|
||||||
|
// D. Bilo and W. Nachtigall, 1980
|
||||||
|
0.031 * PI * dim.x * 0.2 * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
Body::Object(object) => match object {
|
Body::Object(object) => match object {
|
||||||
@ -158,7 +232,7 @@ impl Body {
|
|||||||
| object::Body::FireworkYellow
|
| object::Body::FireworkYellow
|
||||||
| object::Body::MultiArrow => {
|
| object::Body::MultiArrow => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5);
|
||||||
0.02 * std::f32::consts::PI * dim.x * dim.z
|
0.02 * PI * dim.x * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
// spherical-ish objects
|
// spherical-ish objects
|
||||||
@ -176,12 +250,12 @@ impl Body {
|
|||||||
| object::Body::Pumpkin4
|
| object::Body::Pumpkin4
|
||||||
| object::Body::Pumpkin5 => {
|
| object::Body::Pumpkin5 => {
|
||||||
let dim = self.dimensions().map(|a| a * 0.5);
|
let dim = self.dimensions().map(|a| a * 0.5);
|
||||||
0.5 * std::f32::consts::PI * dim.x * dim.z
|
0.5 * PI * dim.x * dim.z
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions();
|
||||||
2.0 * (std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
2.0 * (PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -189,17 +263,91 @@ impl Body {
|
|||||||
// Airships tend to use the square of the cube root of its volume for
|
// Airships tend to use the square of the cube root of its volume for
|
||||||
// reference area
|
// reference area
|
||||||
let dim = self.dimensions();
|
let dim = self.dimensions();
|
||||||
(std::f32::consts::PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
(PI / 6.0 * dim.x * dim.y * dim.z).powf(2.0 / 3.0)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Geometric angle of attack
|
||||||
## References:
|
///
|
||||||
|
/// # 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)
|
||||||
|
}
|
||||||
|
|
||||||
1. "Field Estimates of Body Drag Coefficient on the Basis of Dives in Passerine Birds",
|
/// Total lift coefficient for a finite wing of symmetric aerofoil shape and
|
||||||
Anders Hedenström and Felix Liechti, 2001
|
/// elliptical pressure distribution.
|
||||||
2. "A Simple Method to Determine Drag Coefficients in Aquatic Animals",
|
pub fn lift_coefficient(aspect_ratio: f32, planform_area: f32, aoa: f32) -> f32 {
|
||||||
D. Bilo and W. Nachtigall, 1980
|
let aoa_abs = aoa.abs();
|
||||||
*/
|
let stall_angle = PI * 0.1;
|
||||||
|
planform_area
|
||||||
|
* if aoa_abs < stall_angle {
|
||||||
|
lift_slope(aspect_ratio, None) * aoa
|
||||||
|
} 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 = aoa.signum();
|
||||||
|
let c_l_max = lift_slope(aspect_ratio, None) * 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The zero-lift profile drag coefficient is the parasite drag on the wings
|
||||||
|
/// at the angle of attack which generates no lift
|
||||||
|
pub fn zero_lift_drag_coefficient(planform_area: f32) -> f32 {
|
||||||
|
// TODO: verify that it's correct to multiply by planform
|
||||||
|
// avg value for Harris' hawk (Parabuteo unicinctus) [1]
|
||||||
|
planform_area * 0.02
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The change in lift over change in angle of attack¹. Multiplying by angle
|
||||||
|
/// of attack gives the lift coefficient (for a finite wing, not aerofoil).
|
||||||
|
/// Aspect ratio is the ratio of total wing span squared over planform area.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Only valid for symmetric, elliptical wings at small² angles of attack³.
|
||||||
|
/// Does not apply to twisted, cambered or delta wings. (It still gives a
|
||||||
|
/// reasonably accurate approximation if the wing shape is not truly
|
||||||
|
/// elliptical.)
|
||||||
|
/// 1. geometric angle of attack, i.e. the pitch angle relative to
|
||||||
|
/// freestream flow
|
||||||
|
/// 2. up to around ~18°, at which point maximum lift has been achieved and
|
||||||
|
/// thereafter falls precipitously, causing a stall (this is the stall
|
||||||
|
/// angle) 3. effective aoa, i.e. geometric aoa - induced aoa; assumes
|
||||||
|
/// no sideslip
|
||||||
|
// TODO: Look into handling tapered wings
|
||||||
|
fn lift_slope(aspect_ratio: f32, sweep_angle: Option<f32>) -> f32 {
|
||||||
|
// lift slope for a thin aerofoil, given by Thin Aerofoil Theory
|
||||||
|
let a0 = 2.0 * PI;
|
||||||
|
if let Some(sweep) = sweep_angle {
|
||||||
|
// for swept wings we use Kuchemann's modification to Helmbold's
|
||||||
|
// equation
|
||||||
|
let a0_cos_sweep = a0 * sweep.cos();
|
||||||
|
let x = a0_cos_sweep / (PI * aspect_ratio);
|
||||||
|
a0_cos_sweep / ((1.0 + x.powi(2)).sqrt() + x)
|
||||||
|
} else if aspect_ratio < 4.0 {
|
||||||
|
// for low aspect ratio wings (AR < 4) we use Helmbold's equation
|
||||||
|
let x = a0 / (PI * aspect_ratio);
|
||||||
|
a0 / ((1.0 + x.powi(2)).sqrt() + x)
|
||||||
|
} else {
|
||||||
|
// for high aspect ratio wings (AR > 4) we use the equation given by
|
||||||
|
// Prandtl's lifting-line theory
|
||||||
|
a0 / (1.0 + (a0 / (PI * aspect_ratio)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -148,8 +148,26 @@ impl Ori {
|
|||||||
self.to_quat() * local
|
self.to_quat() * local
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_horizontal(self) -> Option<Self> {
|
pub fn to_horizontal(self) -> Self {
|
||||||
Dir::from_unnormalized(self.look_dir().xy().into()).map(|ori| ori.into())
|
let fw = self.look_dir();
|
||||||
|
Dir::from_unnormalized(fw.xy().into())
|
||||||
|
.or_else(|| {
|
||||||
|
// if look_dir is straight down, pitch up, or if straight up, pitch down
|
||||||
|
Dir::from_unnormalized(
|
||||||
|
if fw.dot(Vec3::unit_z()) < 0.0 {
|
||||||
|
self.up()
|
||||||
|
} else {
|
||||||
|
self.down()
|
||||||
|
}
|
||||||
|
.xy()
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|dir| dir.into())
|
||||||
|
.expect(
|
||||||
|
"If the horizontal component of a Dir can not be normalized, the horizontal \
|
||||||
|
component of a Dir perpendicular to it must be",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pitched_up(self, angle_radians: f32) -> Self {
|
pub fn pitched_up(self, angle_radians: f32) -> Self {
|
||||||
|
@ -1,64 +1,178 @@
|
|||||||
use super::utils::handle_climb;
|
use super::utils::handle_climb;
|
||||||
use crate::{
|
use crate::{
|
||||||
comp::{inventory::slot::EquipSlot, CharacterState, Ori, StateUpdate},
|
comp::{
|
||||||
|
fluid_dynamics::angle_of_attack, inventory::slot::EquipSlot, CharacterState, Ori,
|
||||||
|
StateUpdate, Vel,
|
||||||
|
},
|
||||||
states::behavior::{CharacterBehavior, JoinData},
|
states::behavior::{CharacterBehavior, JoinData},
|
||||||
util::Dir,
|
util::{Dir, Plane, Projection},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use vek::Vec2;
|
use std::f32::consts::PI;
|
||||||
|
use vek::*;
|
||||||
const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90;
|
|
||||||
const GLIDE_ACCEL: f32 = 5.0;
|
|
||||||
const GLIDE_MAX_SPEED: f32 = 30.0;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Data;
|
pub struct Data {
|
||||||
|
/// The aspect ratio is the ratio of the span squared to actual planform
|
||||||
|
/// area
|
||||||
|
pub aspect_ratio: f32,
|
||||||
|
pub planform_area: f32,
|
||||||
|
pub ori: Ori,
|
||||||
|
last_vel: Vel,
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
Self {
|
||||||
|
aspect_ratio,
|
||||||
|
planform_area,
|
||||||
|
ori,
|
||||||
|
last_vel: Vel::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tgt_dir(data: &JoinData) -> Dir {
|
||||||
|
let look_ori = Ori::from(data.inputs.look_dir);
|
||||||
|
look_ori
|
||||||
|
.yawed_right(PI / 3.0 * look_ori.right().xy().dot(data.inputs.move_dir))
|
||||||
|
.pitched_up(PI * 0.05)
|
||||||
|
.pitched_down(
|
||||||
|
data.inputs
|
||||||
|
.look_dir
|
||||||
|
.xy()
|
||||||
|
.try_normalized()
|
||||||
|
.map_or(0.0, |ld| PI * 0.1 * ld.dot(data.inputs.move_dir)),
|
||||||
|
)
|
||||||
|
.look_dir()
|
||||||
|
}
|
||||||
|
|
||||||
impl CharacterBehavior for Data {
|
impl CharacterBehavior for Data {
|
||||||
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
fn behavior(&self, data: &JoinData) -> StateUpdate {
|
||||||
let mut update = StateUpdate::from(data);
|
let mut update = StateUpdate::from(data);
|
||||||
|
|
||||||
// If player is on ground, end glide
|
// If player is on ground, end glide
|
||||||
if data.physics.on_ground {
|
if data.physics.on_ground
|
||||||
|
&& (data.vel.0 - data.physics.ground_vel).magnitude_squared() < 2_f32.powi(2)
|
||||||
|
{
|
||||||
update.character = CharacterState::GlideWield;
|
update.character = CharacterState::GlideWield;
|
||||||
return update;
|
update.ori = update.ori.to_horizontal();
|
||||||
}
|
} else if data.physics.in_liquid().is_some()
|
||||||
if data
|
|| data.inventory.equipped(EquipSlot::Glider).is_none()
|
||||||
.physics
|
|
||||||
.in_liquid()
|
|
||||||
.map(|depth| depth > 0.5)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
{
|
||||||
update.character = CharacterState::Idle;
|
update.character = CharacterState::Idle;
|
||||||
|
update.ori = update.ori.to_horizontal();
|
||||||
|
} else if !handle_climb(&data, &mut update) {
|
||||||
|
let air_flow = data
|
||||||
|
.physics
|
||||||
|
.in_fluid
|
||||||
|
.map(|fluid| fluid.relative_flow(data.vel))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
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(0.1)
|
||||||
|
};
|
||||||
|
|
||||||
|
Dir::from_unnormalized(air_flow.0)
|
||||||
|
.map(|flow_dir| {
|
||||||
|
let tgt_dir = 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.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))
|
||||||
|
};
|
||||||
|
|
||||||
|
update.ori = {
|
||||||
|
let slerp_s = {
|
||||||
|
let angle = data.ori.look_dir().angle_between(*data.inputs.look_dir);
|
||||||
|
let rate = data.body.base_ori_rate() * PI / angle;
|
||||||
|
(data.dt.0 * rate).min(0.1)
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
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.pos.0 = {
|
||||||
|
// offset character pos such that it's the center of rotation is not around the
|
||||||
|
// character
|
||||||
|
let center_off = data.body.height() * 0.7;
|
||||||
|
update.pos.0 + *data.ori.up() * center_off - *update.ori.up() * center_off
|
||||||
|
};
|
||||||
|
update.character = CharacterState::Glide(Self {
|
||||||
|
ori,
|
||||||
|
last_vel: *data.vel,
|
||||||
|
..*self
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
update.ori = update.ori.to_horizontal();
|
||||||
}
|
}
|
||||||
if data.inventory.equipped(EquipSlot::Glider).is_none() {
|
|
||||||
update.character = CharacterState::Idle
|
|
||||||
};
|
|
||||||
|
|
||||||
let horiz_vel = Vec2::<f32>::from(update.vel.0);
|
|
||||||
let horiz_speed_sq = horiz_vel.magnitude_squared();
|
|
||||||
|
|
||||||
// Move player according to movement direction vector
|
|
||||||
if horiz_speed_sq < GLIDE_MAX_SPEED.powi(2) {
|
|
||||||
update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine orientation vector from movement direction vector
|
|
||||||
if let Some(dir) = Dir::from_unnormalized(update.vel.0) {
|
|
||||||
update.ori = update.ori.slerped_towards(Ori::from(dir), 2.0 * data.dt.0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply Glide antigrav lift
|
|
||||||
if update.vel.0.z < 0.0 {
|
|
||||||
let lift = (GLIDE_ANTIGRAV + update.vel.0.z.powi(2) * 0.15)
|
|
||||||
* (horiz_speed_sq * f32::powf(0.075, 2.0)).clamp(0.2, 1.0);
|
|
||||||
|
|
||||||
update.vel.0.z += lift * data.dt.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a wall in front of character and they are trying to climb go to
|
|
||||||
// climb
|
|
||||||
handle_climb(&data, &mut update);
|
|
||||||
|
|
||||||
update
|
update
|
||||||
}
|
}
|
||||||
@ -66,6 +180,7 @@ impl CharacterBehavior for Data {
|
|||||||
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
fn unwield(&self, data: &JoinData) -> StateUpdate {
|
||||||
let mut update = StateUpdate::from(data);
|
let mut update = StateUpdate::from(data);
|
||||||
update.character = CharacterState::Idle;
|
update.character = CharacterState::Idle;
|
||||||
|
update.ori = update.ori.to_horizontal();
|
||||||
update
|
update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
use super::utils::*;
|
use super::utils::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
comp::{slot::EquipSlot, CharacterState, EnergySource, InventoryAction, StateUpdate},
|
comp::{slot::EquipSlot, CharacterState, InventoryAction, StateUpdate},
|
||||||
states::behavior::{CharacterBehavior, JoinData},
|
states::{
|
||||||
|
behavior::{CharacterBehavior, JoinData},
|
||||||
|
glide,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Data;
|
pub struct Data;
|
||||||
@ -18,18 +21,7 @@ impl CharacterBehavior for Data {
|
|||||||
|
|
||||||
// If not on the ground while wielding glider enter gliding state
|
// If not on the ground while wielding glider enter gliding state
|
||||||
if !data.physics.on_ground {
|
if !data.physics.on_ground {
|
||||||
// Expend energy to slow a fall
|
update.character = CharacterState::Glide(glide::Data::new(10.0, 0.6, *data.ori));
|
||||||
let energy_cost = (0.5 * (data.vel.0.z + 15.0).min(0.0).powi(2)) as i32;
|
|
||||||
if update
|
|
||||||
.energy
|
|
||||||
.try_change_by(-energy_cost, EnergySource::Glide)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
update.character = CharacterState::Glide;
|
|
||||||
} else {
|
|
||||||
update.energy.set_to(0, EnergySource::Glide);
|
|
||||||
update.character = CharacterState::Idle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if data
|
if data
|
||||||
.physics
|
.physics
|
||||||
|
@ -363,15 +363,24 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submers
|
|||||||
|
|
||||||
/// Updates components to move entity as if it's flying
|
/// Updates components to move entity as if it's flying
|
||||||
pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool {
|
pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool {
|
||||||
if let Some(force) = data.body.fly_thrust() {
|
let glider = match data.character {
|
||||||
|
CharacterState::Glide(data) => Some(data),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(force) = data
|
||||||
|
.body
|
||||||
|
.fly_thrust()
|
||||||
|
.or_else(|| glider.is_some().then_some(0.0))
|
||||||
|
{
|
||||||
let thrust = efficiency * force;
|
let thrust = efficiency * force;
|
||||||
|
|
||||||
let accel = thrust / data.mass.0;
|
let accel = thrust / data.mass.0;
|
||||||
|
|
||||||
|
handle_orientation(data, update, efficiency);
|
||||||
|
|
||||||
// Elevation control
|
// Elevation control
|
||||||
match data.body {
|
match data.body {
|
||||||
// flappy flappy
|
// flappy flappy
|
||||||
Body::Dragon(_) | Body::BirdMedium(_) | Body::BirdLarge(_) => {
|
Body::Dragon(_) | Body::BirdLarge(_) | Body::BirdMedium(_) => {
|
||||||
let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0));
|
let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0));
|
||||||
update.vel.0.z += data.dt.0 * (anti_grav + accel * data.inputs.move_z.max(0.0));
|
update.vel.0.z += data.dt.0 * (anti_grav + accel * data.inputs.move_z.max(0.0));
|
||||||
},
|
},
|
||||||
@ -468,7 +477,7 @@ pub fn attempt_sneak(data: &JoinData, update: &mut StateUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks that player can `Climb` and updates `CharacterState` if so
|
/// Checks that player can `Climb` and updates `CharacterState` if so
|
||||||
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
|
pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) -> bool {
|
||||||
if data.inputs.climb.is_some()
|
if data.inputs.climb.is_some()
|
||||||
&& data.physics.on_wall.is_some()
|
&& data.physics.on_wall.is_some()
|
||||||
&& !data.physics.on_ground
|
&& !data.physics.on_ground
|
||||||
@ -482,6 +491,9 @@ pub fn handle_climb(data: &JoinData, update: &mut StateUpdate) {
|
|||||||
&& update.energy.current() > 100
|
&& update.energy.current() > 100
|
||||||
{
|
{
|
||||||
update.character = CharacterState::Climb(climb::Data::create_adjusted_by_skills(data));
|
update.character = CharacterState::Climb(climb::Data::create_adjusted_by_skills(data));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Idle => states::idle::Data.handle_event(&j, action),
|
CharacterState::Idle => states::idle::Data.handle_event(&j, action),
|
||||||
CharacterState::Talk => states::talk::Data.handle_event(&j, action),
|
CharacterState::Talk => states::talk::Data.handle_event(&j, action),
|
||||||
CharacterState::Climb(data) => data.handle_event(&j, action),
|
CharacterState::Climb(data) => data.handle_event(&j, action),
|
||||||
CharacterState::Glide => states::glide::Data.handle_event(&j, action),
|
CharacterState::Glide(data) => data.handle_event(&j, action),
|
||||||
CharacterState::GlideWield => {
|
CharacterState::GlideWield => {
|
||||||
states::glide_wield::Data.handle_event(&j, action)
|
states::glide_wield::Data.handle_event(&j, action)
|
||||||
},
|
},
|
||||||
@ -349,7 +349,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
CharacterState::Idle => states::idle::Data.behavior(&j),
|
CharacterState::Idle => states::idle::Data.behavior(&j),
|
||||||
CharacterState::Talk => states::talk::Data.behavior(&j),
|
CharacterState::Talk => states::talk::Data.behavior(&j),
|
||||||
CharacterState::Climb(data) => data.behavior(&j),
|
CharacterState::Climb(data) => data.behavior(&j),
|
||||||
CharacterState::Glide => states::glide::Data.behavior(&j),
|
CharacterState::Glide(data) => data.behavior(&j),
|
||||||
CharacterState::GlideWield => states::glide_wield::Data.behavior(&j),
|
CharacterState::GlideWield => states::glide_wield::Data.behavior(&j),
|
||||||
CharacterState::Stunned(data) => data.behavior(&j),
|
CharacterState::Stunned(data) => data.behavior(&j),
|
||||||
CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j),
|
CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j),
|
||||||
|
@ -45,6 +45,7 @@ fn integrate_forces(
|
|||||||
body: &Body,
|
body: &Body,
|
||||||
density: &Density,
|
density: &Density,
|
||||||
mass: &Mass,
|
mass: &Mass,
|
||||||
|
character_state: Option<&CharacterState>,
|
||||||
fluid: &Fluid,
|
fluid: &Fluid,
|
||||||
gravity: f32,
|
gravity: f32,
|
||||||
) -> Vel {
|
) -> Vel {
|
||||||
@ -58,7 +59,7 @@ fn integrate_forces(
|
|||||||
// Aerodynamic/hydrodynamic forces
|
// Aerodynamic/hydrodynamic forces
|
||||||
if !rel_flow.0.is_approx_zero() {
|
if !rel_flow.0.is_approx_zero() {
|
||||||
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
|
debug_assert!(!rel_flow.0.map(|a| a.is_nan()).reduce_or());
|
||||||
let impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0);
|
let impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0, character_state);
|
||||||
debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or());
|
debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or());
|
||||||
if !impulse.is_approx_zero() {
|
if !impulse.is_approx_zero() {
|
||||||
let new_v = vel.0 + impulse / mass.0;
|
let new_v = vel.0 + impulse / mass.0;
|
||||||
@ -564,6 +565,7 @@ impl<'a> PhysicsData<'a> {
|
|||||||
velocities,
|
velocities,
|
||||||
read.stickies.maybe(),
|
read.stickies.maybe(),
|
||||||
&read.bodies,
|
&read.bodies,
|
||||||
|
read.character_states.maybe(),
|
||||||
&write.physics_states,
|
&write.physics_states,
|
||||||
&read.masses,
|
&read.masses,
|
||||||
&read.densities,
|
&read.densities,
|
||||||
@ -575,7 +577,18 @@ impl<'a> PhysicsData<'a> {
|
|||||||
prof_span!(guard, "velocity update rayon job");
|
prof_span!(guard, "velocity update rayon job");
|
||||||
guard
|
guard
|
||||||
},
|
},
|
||||||
|_guard, (pos, vel, sticky, body, physics_state, mass, density, _)| {
|
|_guard,
|
||||||
|
(
|
||||||
|
pos,
|
||||||
|
vel,
|
||||||
|
sticky,
|
||||||
|
body,
|
||||||
|
character_state,
|
||||||
|
physics_state,
|
||||||
|
mass,
|
||||||
|
density,
|
||||||
|
_,
|
||||||
|
)| {
|
||||||
let in_loaded_chunk = read
|
let in_loaded_chunk = read
|
||||||
.terrain
|
.terrain
|
||||||
.get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
|
.get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
|
||||||
@ -597,7 +610,14 @@ impl<'a> PhysicsData<'a> {
|
|||||||
},
|
},
|
||||||
Some(fluid) => {
|
Some(fluid) => {
|
||||||
vel.0 = integrate_forces(
|
vel.0 = integrate_forces(
|
||||||
&dt, *vel, body, density, mass, &fluid, GRAVITY,
|
&dt,
|
||||||
|
*vel,
|
||||||
|
body,
|
||||||
|
density,
|
||||||
|
mass,
|
||||||
|
character_state,
|
||||||
|
&fluid,
|
||||||
|
GRAVITY,
|
||||||
)
|
)
|
||||||
.0
|
.0
|
||||||
},
|
},
|
||||||
@ -688,7 +708,8 @@ impl<'a> PhysicsData<'a> {
|
|||||||
let mut tgt_pos = pos.0 + pos_delta;
|
let mut tgt_pos = pos.0 + pos_delta;
|
||||||
|
|
||||||
let was_on_ground = physics_state.on_ground;
|
let was_on_ground = physics_state.on_ground;
|
||||||
let block_snap = body.map_or(false, |b| !matches!(b, Body::Ship(_)));
|
let block_snap =
|
||||||
|
body.map_or(false, |b| !matches!(b, Body::Object(_) | Body::Ship(_)));
|
||||||
let climbing =
|
let climbing =
|
||||||
character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_)));
|
character_state.map_or(false, |cs| matches!(cs, CharacterState::Climb(_)));
|
||||||
|
|
||||||
@ -841,11 +862,12 @@ impl<'a> PhysicsData<'a> {
|
|||||||
depth,
|
depth,
|
||||||
vel: Vel::zero(),
|
vel: Vel::zero(),
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| match physics_state.in_fluid {
|
||||||
Some(Fluid::Air {
|
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
|
||||||
elevation: pos.0.z,
|
elevation: pos.0.z,
|
||||||
vel: Vel::zero(),
|
vel: Vel::default(),
|
||||||
})
|
}),
|
||||||
|
fluid => fluid,
|
||||||
});
|
});
|
||||||
|
|
||||||
tgt_pos = pos.0;
|
tgt_pos = pos.0;
|
||||||
@ -1517,11 +1539,12 @@ fn box_voxel_collision<'a, T: BaseVol<Vox = Block> + ReadVol>(
|
|||||||
depth,
|
depth,
|
||||||
vel: Vel::zero(),
|
vel: Vel::zero(),
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| match physics_state.in_fluid {
|
||||||
Some(Fluid::Air {
|
Some(Fluid::Water { .. }) | None => Some(Fluid::Air {
|
||||||
elevation: pos.0.z,
|
elevation: pos.0.z,
|
||||||
vel: Vel::zero(),
|
vel: Vel::default(),
|
||||||
})
|
}),
|
||||||
|
fluid => fluid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +190,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
| CharacterState::Sit { .. }
|
| CharacterState::Sit { .. }
|
||||||
| CharacterState::Dance { .. }
|
| CharacterState::Dance { .. }
|
||||||
| CharacterState::Sneak { .. }
|
| CharacterState::Sneak { .. }
|
||||||
|
| CharacterState::Glide { .. }
|
||||||
| CharacterState::GlideWield { .. }
|
| CharacterState::GlideWield { .. }
|
||||||
| CharacterState::Wielding { .. }
|
| CharacterState::Wielding { .. }
|
||||||
| CharacterState::Equipping { .. }
|
| CharacterState::Equipping { .. }
|
||||||
@ -232,9 +233,8 @@ impl<'a> System<'a> for Sys {
|
|||||||
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0);
|
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Ability and glider use does not regen and sets the rate back to zero.
|
// Ability use does not regen and sets the rate back to zero.
|
||||||
CharacterState::Glide { .. }
|
CharacterState::BasicMelee { .. }
|
||||||
| CharacterState::BasicMelee { .. }
|
|
||||||
| CharacterState::DashMelee { .. }
|
| CharacterState::DashMelee { .. }
|
||||||
| CharacterState::LeapMelee { .. }
|
| CharacterState::LeapMelee { .. }
|
||||||
| CharacterState::SpinMelee { .. }
|
| CharacterState::SpinMelee { .. }
|
||||||
|
@ -223,7 +223,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
});
|
});
|
||||||
let is_gliding = matches!(
|
let is_gliding = matches!(
|
||||||
read_data.char_states.get(entity),
|
read_data.char_states.get(entity),
|
||||||
Some(CharacterState::GlideWield) | Some(CharacterState::Glide)
|
Some(CharacterState::GlideWield) | Some(CharacterState::Glide(_))
|
||||||
) && !physics_state.on_ground;
|
) && !physics_state.on_ground;
|
||||||
|
|
||||||
// This controls how picky NPCs are about their pathfinding. Giants are larger
|
// This controls how picky NPCs are about their pathfinding. Giants are larger
|
||||||
|
@ -2,21 +2,11 @@ use super::{
|
|||||||
super::{vek::*, Animation},
|
super::{vek::*, Animation},
|
||||||
CharacterSkeleton, SkeletonAttr,
|
CharacterSkeleton, SkeletonAttr,
|
||||||
};
|
};
|
||||||
use common::comp::item::ToolKind;
|
|
||||||
|
|
||||||
pub struct GlideWieldAnimation;
|
pub struct GlideWieldAnimation;
|
||||||
|
|
||||||
type GlideWieldAnimationDependency = (
|
|
||||||
Option<ToolKind>,
|
|
||||||
Option<ToolKind>,
|
|
||||||
Vec3<f32>,
|
|
||||||
Vec3<f32>,
|
|
||||||
Vec3<f32>,
|
|
||||||
f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Animation for GlideWieldAnimation {
|
impl Animation for GlideWieldAnimation {
|
||||||
type Dependency = GlideWieldAnimationDependency;
|
type Dependency = ();
|
||||||
type Skeleton = CharacterSkeleton;
|
type Skeleton = CharacterSkeleton;
|
||||||
|
|
||||||
#[cfg(feature = "use-dyn-lib")]
|
#[cfg(feature = "use-dyn-lib")]
|
||||||
@ -26,13 +16,12 @@ impl Animation for GlideWieldAnimation {
|
|||||||
|
|
||||||
fn update_skeleton_inner(
|
fn update_skeleton_inner(
|
||||||
skeleton: &Self::Skeleton,
|
skeleton: &Self::Skeleton,
|
||||||
(_active_tool_kind, _second_tool_kind, velocity, _orientation, _last_ori, _global_time): Self::Dependency,
|
_: Self::Dependency,
|
||||||
_anim_time: f32,
|
_anim_time: f32,
|
||||||
rate: &mut f32,
|
rate: &mut f32,
|
||||||
s_a: &SkeletonAttr,
|
s_a: &SkeletonAttr,
|
||||||
) -> Self::Skeleton {
|
) -> Self::Skeleton {
|
||||||
let mut next = (*skeleton).clone();
|
let mut next = (*skeleton).clone();
|
||||||
let speed = Vec2::<f32>::from(velocity).magnitude();
|
|
||||||
*rate = 1.0;
|
*rate = 1.0;
|
||||||
|
|
||||||
next.hand_l.position = Vec3::new(-2.0 - s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
|
next.hand_l.position = Vec3::new(-2.0 - s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
|
||||||
@ -41,14 +30,9 @@ impl Animation for GlideWieldAnimation {
|
|||||||
next.hand_r.position = Vec3::new(2.0 + s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
|
next.hand_r.position = Vec3::new(2.0 + s_a.hand.0, s_a.hand.1, s_a.hand.2 + 15.0);
|
||||||
next.hand_r.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(-0.2);
|
next.hand_r.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(-0.2);
|
||||||
next.glider.scale = Vec3::one() * 1.0;
|
next.glider.scale = Vec3::one() * 1.0;
|
||||||
|
next.glider.orientation = Quaternion::rotation_x(0.35);
|
||||||
|
|
||||||
if speed > 0.5 {
|
next.glider.position = Vec3::new(0.0, -5.0, 13.0);
|
||||||
next.glider.orientation = Quaternion::rotation_x(0.8);
|
|
||||||
next.glider.position = Vec3::new(0.0, -10.0, 15.0);
|
|
||||||
} else {
|
|
||||||
next.glider.orientation = Quaternion::rotation_x(0.35);
|
|
||||||
next.glider.position = Vec3::new(0.0, -9.0, 17.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,11 @@ use super::{
|
|||||||
super::{vek::*, Animation},
|
super::{vek::*, Animation},
|
||||||
CharacterSkeleton, SkeletonAttr,
|
CharacterSkeleton, SkeletonAttr,
|
||||||
};
|
};
|
||||||
use common::comp::item::ToolKind;
|
use std::ops::Mul;
|
||||||
use std::{f32::consts::PI, ops::Mul};
|
|
||||||
|
|
||||||
pub struct GlidingAnimation;
|
pub struct GlidingAnimation;
|
||||||
|
|
||||||
type GlidingAnimationDependency = (
|
type GlidingAnimationDependency = (Vec3<f32>, Quaternion<f32>, Quaternion<f32>, f32, f32);
|
||||||
Option<ToolKind>,
|
|
||||||
Option<ToolKind>,
|
|
||||||
Vec3<f32>,
|
|
||||||
Vec3<f32>,
|
|
||||||
Vec3<f32>,
|
|
||||||
f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl Animation for GlidingAnimation {
|
impl Animation for GlidingAnimation {
|
||||||
type Dependency = GlidingAnimationDependency;
|
type Dependency = GlidingAnimationDependency;
|
||||||
@ -27,89 +19,63 @@ impl Animation for GlidingAnimation {
|
|||||||
|
|
||||||
fn update_skeleton_inner(
|
fn update_skeleton_inner(
|
||||||
skeleton: &Self::Skeleton,
|
skeleton: &Self::Skeleton,
|
||||||
(_active_tool_kind, _second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency,
|
(velocity, orientation, glider_orientation, global_time, acc_vel): Self::Dependency,
|
||||||
anim_time: f32,
|
anim_time: f32,
|
||||||
_rate: &mut f32,
|
_rate: &mut f32,
|
||||||
s_a: &SkeletonAttr,
|
s_a: &SkeletonAttr,
|
||||||
) -> Self::Skeleton {
|
) -> Self::Skeleton {
|
||||||
let mut next = (*skeleton).clone();
|
let mut next = (*skeleton).clone();
|
||||||
|
|
||||||
let speed = Vec2::<f32>::from(velocity).magnitude();
|
let speednorm = velocity.magnitude().min(50.0) / 50.0;
|
||||||
|
let slow = (acc_vel * 0.5).sin();
|
||||||
let quick = (anim_time * 7.0).sin();
|
|
||||||
let quicka = (anim_time * 7.0 + PI / 2.0).sin();
|
|
||||||
let wave_stop = (anim_time * 1.5).min(PI / 2.0).sin();
|
|
||||||
let slow = (anim_time * 3.0).sin();
|
|
||||||
let slowb = (anim_time * 3.0 + PI).sin();
|
|
||||||
let slowa = (anim_time * 3.0 + PI / 2.0).sin();
|
|
||||||
|
|
||||||
let head_look = Vec2::new(
|
let head_look = Vec2::new(
|
||||||
((global_time + anim_time) / 5.0).floor().mul(7331.0).sin() * 0.5,
|
((global_time + anim_time) as f32 / 4.0)
|
||||||
((global_time + anim_time) / 5.0).floor().mul(1337.0).sin() * 0.25,
|
.floor()
|
||||||
|
.mul(7331.0)
|
||||||
|
.sin()
|
||||||
|
* 0.5,
|
||||||
|
((global_time + anim_time) as f32 / 4.0)
|
||||||
|
.floor()
|
||||||
|
.mul(1337.0)
|
||||||
|
.sin()
|
||||||
|
* 0.25,
|
||||||
);
|
);
|
||||||
|
|
||||||
let ori: Vec2<f32> = Vec2::from(orientation);
|
let speedlog = speednorm.powi(2);
|
||||||
let last_ori = Vec2::from(last_ori);
|
let chest_ori = Quaternion::rotation_z(slow * 0.01);
|
||||||
let tilt = if ::vek::Vec2::new(ori, last_ori)
|
let chest_global_inv = (orientation * chest_ori).inverse();
|
||||||
.map(|o| o.magnitude_squared())
|
let glider_ori = chest_global_inv * glider_orientation;
|
||||||
.map(|m| m > 0.0001 && m.is_finite())
|
let glider_pos = Vec3::new(0.0, -5.0 + speedlog * 2.0, 13.0);
|
||||||
.reduce_and()
|
|
||||||
&& ori.angle_between(last_ori).is_finite()
|
|
||||||
{
|
|
||||||
ori.angle_between(last_ori).min(0.05)
|
|
||||||
* last_ori.determine_side(Vec2::zero(), ori).signum()
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
};
|
|
||||||
|
|
||||||
let tiltcancel = if anim_time > 1.0 { 1.0 } else { anim_time };
|
next.head.orientation = Quaternion::rotation_x(0.5 + head_look.y * speednorm)
|
||||||
|
* Quaternion::rotation_z(head_look.x);
|
||||||
|
|
||||||
next.head.position = Vec3::new(0.0, s_a.head.0 + 1.0, s_a.head.1);
|
next.glider.position = glider_pos;
|
||||||
next.head.orientation = Quaternion::rotation_x(0.35 - slow * 0.10 + head_look.y)
|
next.glider.orientation = glider_ori;
|
||||||
* Quaternion::rotation_z(head_look.x + slowa * 0.15);
|
|
||||||
|
|
||||||
next.chest.orientation = Quaternion::rotation_z(slowa * 0.02);
|
|
||||||
|
|
||||||
next.belt.orientation = Quaternion::rotation_z(slowa * 0.1 + tilt * tiltcancel * 12.0);
|
|
||||||
next.belt.position = Vec3::new(0.0, s_a.belt.0, s_a.belt.1);
|
|
||||||
|
|
||||||
next.shorts.orientation = Quaternion::rotation_z(slowa * 0.12 + tilt * tiltcancel * 16.0);
|
|
||||||
next.shorts.position = Vec3::new(0.0, s_a.shorts.0, s_a.shorts.1);
|
|
||||||
|
|
||||||
next.hand_l.position = Vec3::new(-9.5, -3.0, 10.0);
|
|
||||||
next.hand_l.orientation =
|
|
||||||
Quaternion::rotation_x(-2.7 + slowa * -0.1) * Quaternion::rotation_y(0.2);
|
|
||||||
|
|
||||||
next.hand_r.position = Vec3::new(9.5, -3.0, 10.0);
|
|
||||||
next.hand_r.orientation =
|
|
||||||
Quaternion::rotation_x(-2.7 + slowa * -0.10) * Quaternion::rotation_y(-0.2);
|
|
||||||
|
|
||||||
next.foot_l.position = Vec3::new(
|
|
||||||
-s_a.foot.0,
|
|
||||||
s_a.foot.1 + slowa * -1.0 + tilt * tiltcancel * -35.0,
|
|
||||||
-1.0 + s_a.foot.2,
|
|
||||||
);
|
|
||||||
next.foot_l.orientation = Quaternion::rotation_x(
|
|
||||||
(wave_stop * -0.7 - quicka * -0.21 + slow * 0.19) * speed * 0.04,
|
|
||||||
) * Quaternion::rotation_z(tilt * tiltcancel * 20.0);
|
|
||||||
|
|
||||||
next.foot_r.position = Vec3::new(
|
|
||||||
s_a.foot.0,
|
|
||||||
s_a.foot.1 + slowa * 1.0 + tilt * tiltcancel * 35.0,
|
|
||||||
-1.0 + s_a.foot.2,
|
|
||||||
);
|
|
||||||
next.foot_r.orientation = Quaternion::rotation_x(
|
|
||||||
(wave_stop * -0.8 + quick * -0.25 + slowb * 0.13) * speed * 0.04,
|
|
||||||
) * Quaternion::rotation_z(tilt * tiltcancel * 20.0);
|
|
||||||
|
|
||||||
next.glider.position = Vec3::new(0.0, -13.0 + slow * 0.10, 8.0);
|
|
||||||
next.glider.orientation =
|
|
||||||
Quaternion::rotation_x(0.8) * Quaternion::rotation_y(slowa * 0.04);
|
|
||||||
next.glider.scale = Vec3::one();
|
next.glider.scale = Vec3::one();
|
||||||
|
|
||||||
next.torso.position = Vec3::new(0.0, -1.0, 0.0) / 11.0 * s_a.scaler;
|
next.chest.orientation = chest_ori;
|
||||||
next.torso.orientation = Quaternion::rotation_x(-0.03 * speed.max(12.0) + slow * 0.04)
|
|
||||||
* Quaternion::rotation_y(tilt * tiltcancel * 32.0);
|
//necessary for overwriting jump anim
|
||||||
|
next.belt.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.shorts.orientation = Quaternion::rotation_z(0.0);
|
||||||
|
next.belt.position = Vec3::new(0.0, s_a.belt.0, s_a.belt.1);
|
||||||
|
next.shorts.position = Vec3::new(0.0, s_a.shorts.0, s_a.shorts.1);
|
||||||
|
|
||||||
|
next.hand_l.position =
|
||||||
|
glider_pos + glider_ori * Vec3::new(-s_a.hand.0 + -2.0, s_a.hand.1 + 8.0, s_a.hand.2);
|
||||||
|
next.hand_l.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(0.2);
|
||||||
|
|
||||||
|
next.hand_r.position =
|
||||||
|
glider_pos + glider_ori * Vec3::new(s_a.hand.0 + 2.0, s_a.hand.1 + 8.0, s_a.hand.2);
|
||||||
|
next.hand_r.orientation = Quaternion::rotation_x(3.35) * Quaternion::rotation_y(-0.2);
|
||||||
|
|
||||||
|
next.foot_l.position = Vec3::new(-s_a.foot.0, s_a.foot.1 + speedlog * -1.0, s_a.foot.2);
|
||||||
|
next.foot_l.orientation = Quaternion::rotation_x(-speedlog + slow * -0.3 * speedlog);
|
||||||
|
|
||||||
|
next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1 + speedlog * -1.0, s_a.foot.2);
|
||||||
|
next.foot_r.orientation = Quaternion::rotation_x(-speedlog + slow * 0.3 * speedlog);
|
||||||
|
|
||||||
next
|
next
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use crate::audio::sfx::SfxEvent;
|
|||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
bird_large, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, InputKind,
|
bird_large, humanoid, quadruped_medium, quadruped_small, Body, CharacterState, InputKind,
|
||||||
PhysicsState,
|
Ori, PhysicsState,
|
||||||
},
|
},
|
||||||
states,
|
states,
|
||||||
terrain::BlockKind,
|
terrain::BlockKind,
|
||||||
@ -236,7 +236,7 @@ fn maps_land_on_ground_to_run() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn maps_glider_open() {
|
fn maps_glider_open() {
|
||||||
let result = MovementEventMapper::map_movement_event(
|
let result = MovementEventMapper::map_movement_event(
|
||||||
&CharacterState::Glide {},
|
&CharacterState::Glide(states::glide::Data::new(10.0, 1.0, Ori::default())),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
&PreviousEntityState {
|
&PreviousEntityState {
|
||||||
event: SfxEvent::Jump,
|
event: SfxEvent::Jump,
|
||||||
@ -255,7 +255,7 @@ fn maps_glider_open() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn maps_glide() {
|
fn maps_glide() {
|
||||||
let result = MovementEventMapper::map_movement_event(
|
let result = MovementEventMapper::map_movement_event(
|
||||||
&CharacterState::Glide {},
|
&CharacterState::Glide(states::glide::Data::new(10.0, 1.0, Ori::default())),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
&PreviousEntityState {
|
&PreviousEntityState {
|
||||||
event: SfxEvent::Glide,
|
event: SfxEvent::Glide,
|
||||||
|
@ -1474,18 +1474,10 @@ impl FigureMgr {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CharacterState::Glide { .. } => {
|
CharacterState::Glide(data) => {
|
||||||
anim::character::GlidingAnimation::update_skeleton(
|
anim::character::GlidingAnimation::update_skeleton(
|
||||||
&target_base,
|
&target_base,
|
||||||
(
|
(rel_vel, ori, data.ori.into(), time, state.acc_vel),
|
||||||
active_tool_kind,
|
|
||||||
second_tool_kind,
|
|
||||||
rel_vel,
|
|
||||||
// TODO: Update to use the quaternion.
|
|
||||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
|
||||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
|
||||||
time,
|
|
||||||
),
|
|
||||||
state.state_time,
|
state.state_time,
|
||||||
&mut state_animation_rate,
|
&mut state_animation_rate,
|
||||||
skeleton_attr,
|
skeleton_attr,
|
||||||
@ -1519,15 +1511,7 @@ impl FigureMgr {
|
|||||||
CharacterState::GlideWield { .. } => {
|
CharacterState::GlideWield { .. } => {
|
||||||
anim::character::GlideWieldAnimation::update_skeleton(
|
anim::character::GlideWieldAnimation::update_skeleton(
|
||||||
&target_base,
|
&target_base,
|
||||||
(
|
(),
|
||||||
active_tool_kind,
|
|
||||||
second_tool_kind,
|
|
||||||
rel_vel,
|
|
||||||
// TODO: Update to use the quaternion.
|
|
||||||
ori * anim::vek::Vec3::<f32>::unit_y(),
|
|
||||||
state.last_ori * anim::vek::Vec3::<f32>::unit_y(),
|
|
||||||
time,
|
|
||||||
),
|
|
||||||
state.state_time,
|
state.state_time,
|
||||||
&mut state_animation_rate,
|
&mut state_animation_rate,
|
||||||
skeleton_attr,
|
skeleton_attr,
|
||||||
|
Loading…
Reference in New Issue
Block a user