From dcec45075d88606ef60e289ef69a239fdacc5088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20B=C3=B6klin?= Date: Mon, 19 Apr 2021 14:43:42 +0200 Subject: [PATCH] Implement lift and adapt things to work with it This reverts commit 823d38a058e757542ac423af5da457ca02cce674. --- Cargo.lock | 2 + client/src/lib.rs | 2 +- common/Cargo.toml | 2 +- common/src/comp/body.rs | 42 +++++++ common/src/comp/character_state.rs | 2 +- common/src/comp/fluid_dynamics.rs | 158 +++++++++++++++++++++++++- common/src/comp/mod.rs | 2 +- common/src/lib.rs | 2 + common/src/states/glide.rs | 55 ++++++--- common/src/states/glide_wield.rs | 11 +- common/src/states/utils.rs | 68 ++++++++++- common/sys/Cargo.toml | 2 +- common/sys/src/character_behavior.rs | 4 +- common/sys/src/phys.rs | 31 ++++- server/src/sys/agent.rs | 2 +- voxygen/anim/src/character/gliding.rs | 155 ++++++++++++++----------- voxygen/src/scene/figure/mod.rs | 9 +- 17 files changed, 436 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce4db3af9d..546b55f4a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5463,6 +5463,7 @@ dependencies = [ "hashbrown", "image", "indexmap", + "inline_tweak", "lazy_static", "num-derive", "num-traits", @@ -5541,6 +5542,7 @@ dependencies = [ "bincode", "hashbrown", "indexmap", + "inline_tweak", "ordered-float 2.1.1", "rand 0.8.3", "rayon", diff --git a/client/src/lib.rs b/client/src/lib.rs index 4443e2638e..a6fab98f9a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1192,7 +1192,7 @@ impl Client { .map(|cs| { matches!( cs, - comp::CharacterState::GlideWield | comp::CharacterState::Glide + comp::CharacterState::GlideWield | comp::CharacterState::Glide(_) ) }); diff --git a/common/Cargo.toml b/common/Cargo.toml index 8c3ef5d6c9..ea560c58c5 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -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"] } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 4da8d22f83..60d538aead 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -595,6 +595,21 @@ impl Body { } } + pub fn wings(&self) -> Option { + matches!( + self, + Body::BirdMedium(_) + | Body::BirdSmall(_) + | Body::Dragon(_) + | Body::FishMedium(_) + | Body::FishSmall(_) + ) + .then_some({ + let dim = self.dimensions().xy(); + RigidWings::new(dim.x, dim.y * 0.2) + }) + } + pub fn immune_to(&self, buff: BuffKind) -> bool { match buff { BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)), @@ -651,3 +666,30 @@ impl Body { impl Component for Body { type Storage = DerefFlaggedStorage>; } + +/// Rigid, elliptical wings +// Not exactly great for birds and such, but for now it'll have to do. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct RigidWings { + aspect_ratio: f32, + planform_area: f32, + // sweep_angle: Option, +} + +impl RigidWings { + /// Wings from total span (wing-tip to wing-tip) and + /// chord length (leading edge to trailing edge) + pub fn new(span_length: f32, chord_length: f32) -> Self { + let planform_area = std::f32::consts::PI * chord_length * span_length * 0.25; + Self { + aspect_ratio: span_length.powi(2) / planform_area, + planform_area, + } + } + + /// The aspect ratio is the ratio of the span squared to actual planform + /// area + pub fn aspect_ratio(&self) -> f32 { self.aspect_ratio } + + pub fn planform_area(&self) -> f32 { self.planform_area } +} diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 587b3f5b1f..7617e58283 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -50,7 +50,7 @@ pub enum CharacterState { Dance, Talk, Sneak, - Glide, + Glide(glide::Data), GlideWield, /// A stunned state Stunned(stunned::Data), diff --git a/common/src/comp/fluid_dynamics.rs b/common/src/comp/fluid_dynamics.rs index d2fa85afac..45ec020abd 100644 --- a/common/src/comp/fluid_dynamics.rs +++ b/common/src/comp/fluid_dynamics.rs @@ -1,6 +1,6 @@ use super::{ - body::{object, Body}, - Density, Vel, + body::{object, Body, RigidWings}, + Density, Ori, Vel, }; use crate::{ consts::{AIR_DENSITY, WATER_DENSITY}, @@ -88,7 +88,13 @@ impl Default for Fluid { } impl Body { - pub fn aerodynamic_forces(&self, rel_flow: &Vel, fluid_density: f32) -> Vec3 { + pub fn aerodynamic_forces( + &self, + ori: &Ori, + rel_flow: &Vel, + fluid_density: f32, + wings: Option<&RigidWings>, + ) -> Vec3 { let v_sq = rel_flow.0.magnitude_squared(); if v_sq < 0.25 { // don't bother with miniscule forces @@ -96,7 +102,69 @@ impl Body { } else { let rel_flow_dir = Dir::new(rel_flow.0 / v_sq.sqrt()); // 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 + * wings + .filter(|_| crate::lift_enabled()) + .map(|wings| { + // Since we have wings, we proceed to calculate the lift and drag + + let ar = wings.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 = wings.lift_coefficient(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; + /*println!( + "CL={:.1}, α={:.1}°, αᵢ={:.1}°, αₑ={:.1}°, AR={:.1}", + c_l, + aoa.to_degrees(), + aoa_i.to_degrees(), + aoa_eff.to_degrees(), + ar + );*/ + // 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; + + wings.zero_lift_drag_coefficient() + + 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()); + /*println!( + "L/D (at α={:.1}, AR={:.1}) = {:.1}/{:.1} = {:.1}", + aoa.to_degrees(), + ar, + 0.5 * fluid_density * v_sq * c_l, + 0.5 * fluid_density * v_sq * c_d, + c_l / c_d + );*/ + + c_l * *lift_dir + c_d * *rel_flow_dir + }) + .unwrap_or_else(|| self.parasite_drag_coefficient() * *rel_flow_dir) } } @@ -194,6 +262,88 @@ impl Body { } } +/// Geometric angle of attack +fn angle_of_attack(ori: &Ori, rel_flow_dir: &Dir) -> f32 { + PI / 2.0 - ori.up().angle_between(rel_flow_dir.to_vec()) +} + +impl RigidWings { + /// Total lift coefficient for a finite wing of symmetric aerofoil shape and + /// elliptical pressure distribution. + pub fn lift_coefficient(&self, aoa: f32) -> f32 { + let aoa_abs = aoa.abs(); + let stall_angle = PI * 0.1; + inline_tweak::tweak!(1.0) + * self.planform_area() + * if aoa_abs < stall_angle { + self.lift_slope(None) * aoa + } else if inline_tweak::tweak!(true) { + // 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 = self.lift_slope(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 + } + } else { + 0.0 + } + } + + /// 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(&self) -> f32 { + // avg value for Harris' hawk (Parabuteo unicinctus) [1] + self.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 + fn lift_slope(&self, sweep_angle: Option) -> f32 { + // lift slope for a thin aerofoil, given by Thin Aerofoil Theory + let ar = self.aspect_ratio(); + 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 * ar); + a0_cos_sweep / ((1.0 + x.powi(2)).sqrt() + x) + } else if ar < 4.0 { + // for low aspect ratio wings (AR < 4) we use Helmbold's equation + let x = a0 / (PI * ar); + 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 * ar))) + } + } +} + /* ## References: diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 2c3664a2c7..5574d911ce 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -52,7 +52,7 @@ pub use self::{ body::{ biped_large, biped_small, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object, quadruped_low, quadruped_medium, quadruped_small, ship, theropod, - AllBodies, Body, BodyData, + AllBodies, Body, BodyData, RigidWings, }, buff::{ Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, diff --git a/common/src/lib.rs b/common/src/lib.rs index c78e861453..49fd057d14 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -87,3 +87,5 @@ pub use comp::inventory::loadout_builder::LoadoutBuilder; pub use explosion::{Explosion, RadiusEffect}; #[cfg(not(target_arch = "wasm32"))] pub use skillset_builder::SkillSetBuilder; + +pub fn lift_enabled() -> bool { inline_tweak::tweak!(true) } diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 7835c33463..a51b82f5f6 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -1,7 +1,10 @@ use super::utils::handle_climb; use crate::{ - comp::{inventory::slot::EquipSlot, CharacterState, Ori, StateUpdate}, - states::behavior::{CharacterBehavior, JoinData}, + comp::{inventory::slot::EquipSlot, CharacterState, Ori, RigidWings, StateUpdate}, + states::{ + behavior::{CharacterBehavior, JoinData}, + utils::fly_move, + }, util::Dir, }; use serde::{Deserialize, Serialize}; @@ -12,7 +15,19 @@ const GLIDE_ACCEL: f32 = 5.0; const GLIDE_MAX_SPEED: f32 = 30.0; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Data; +pub struct Data { + pub wings: RigidWings, + pub ori: Ori, +} + +impl Data { + pub fn new(span_length: f32, chord_length: f32, ori: Ori) -> Self { + Self { + wings: RigidWings::new(span_length, chord_length), + ori, + } + } +} impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { @@ -35,25 +50,29 @@ impl CharacterBehavior for Data { update.character = CharacterState::Idle }; - let horiz_vel = Vec2::::from(update.vel.0); - let horiz_speed_sq = horiz_vel.magnitude_squared(); + if crate::lift_enabled() { + fly_move(data, &mut update, inline_tweak::tweak!(0.1)); + } else { + let horiz_vel = Vec2::::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; - } + // 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); - }; + // 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); + // 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; + 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 diff --git a/common/src/states/glide_wield.rs b/common/src/states/glide_wield.rs index 68512accc5..780af50e36 100644 --- a/common/src/states/glide_wield.rs +++ b/common/src/states/glide_wield.rs @@ -1,7 +1,10 @@ use super::utils::*; use crate::{ comp::{slot::EquipSlot, CharacterState, EnergySource, InventoryAction, StateUpdate}, - states::behavior::{CharacterBehavior, JoinData}, + states::{ + behavior::{CharacterBehavior, JoinData}, + glide, + }, }; pub struct Data; @@ -24,7 +27,11 @@ impl CharacterBehavior for Data { .try_change_by(-energy_cost, EnergySource::Glide) .is_ok() { - update.character = CharacterState::Glide; + update.character = CharacterState::Glide(glide::Data::new( + inline_tweak::tweak!(10.0), + inline_tweak::tweak!(1.0), + *data.ori, + )); } else { update.energy.set_to(0, EnergySource::Glide); update.character = CharacterState::Idle; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index d31bd89c35..f3b9fe5e66 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -6,7 +6,7 @@ use crate::{ quadruped_low, quadruped_medium, quadruped_small, ship, skills::{Skill, SwimSkill}, theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, - InventoryAction, StateUpdate, + InventoryAction, Ori, StateUpdate, }, consts::{FRIC_GROUND, GRAVITY}, event::{LocalEvent, ServerEvent}, @@ -367,18 +367,78 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submers /// Updates components to move entity as if it's flying pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool { - if let Some(force) = data.body.can_fly() { + let glider = match data.character { + CharacterState::Glide(data) => Some(data), + _ => None, + }; + if let Some(force) = data + .body + .can_fly() + .or_else(|| glider.is_some().then_some(0.0)) + { let thrust = efficiency * force; let accel = thrust / data.mass.0; - handle_orientation(data, update, efficiency); + // if lift is enabled we do some more advanced stuff with pitch and roll + if crate::lift_enabled() && !matches!(data.body, Body::Ship(_)) { + let mut ori = glider.map(|g| g.ori).unwrap_or(update.ori); + let fw_dir = ori.look_dir().to_horizontal(); + let tgt_ori = Some(data.inputs.move_dir) + .filter(|mv_dir| !mv_dir.is_approx_zero()) + .map(|mv_dir| { + Vec3::new( + mv_dir.x, + mv_dir.y, + Lerp::lerp_unclamped( + 0.0, + data.inputs.look_dir.z + inline_tweak::tweak!(0.3), + mv_dir.magnitude_squared() * inline_tweak::tweak!(2.0), + ), + ) + }) + .and_then(Dir::from_unnormalized) + .and_then(|tgt_dir| { + Dir::from_unnormalized(data.vel.0) + .and_then(|moving_dir| moving_dir.to_horizontal()) + .map(|moving_dir| { + Ori::from(tgt_dir).rolled_right( + (1.0 - moving_dir.dot(*tgt_dir).max(0.0)) + * ori.right().dot(*tgt_dir).signum() + * std::f32::consts::PI + / 3.0, + ) + }) + }) + .or_else(|| fw_dir.map(Ori::from)) + .unwrap_or_default(); + let rate = { + let angle = ori.look_dir().angle_between(*data.inputs.look_dir); + data.body.base_ori_rate() * efficiency * std::f32::consts::PI / angle + }; + + ori = ori.slerped_towards(tgt_ori, (data.dt.0 * rate).min(0.1)); + if let Some(data) = glider { + update.character = CharacterState::Glide(glide::Data { ori, ..*data }); + if let Some(char_ori) = ori.to_horizontal() { + update.ori = char_ori; + } + } else { + update.ori = ori; + } + } else { + handle_orientation(data, update, efficiency); + } // Elevation control match data.body { // flappy flappy Body::Dragon(_) | Body::BirdMedium(_) | Body::BirdSmall(_) => { - let anti_grav = GRAVITY * (1.0 + data.inputs.move_z.min(0.0)); + let anti_grav = if crate::lift_enabled() { + 0.0 + } else { + 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)); }, // floaty floaty diff --git a/common/sys/Cargo.toml b/common/sys/Cargo.toml index a2abfdd798..2eb88b2426 100644 --- a/common/sys/Cargo.toml +++ b/common/sys/Cargo.toml @@ -43,4 +43,4 @@ bincode = { version = "1.3.1", optional = true } plugin-api = { package = "veloren-plugin-api", path = "../../plugin/api", optional = true } # Tweak running code -# inline_tweak = { version = "1.0.8", features = ["release_tweak"] } +inline_tweak = { version = "1.0.8", features = ["release_tweak"] } diff --git a/common/sys/src/character_behavior.rs b/common/sys/src/character_behavior.rs index 251df59873..0780522aab 100644 --- a/common/sys/src/character_behavior.rs +++ b/common/sys/src/character_behavior.rs @@ -287,7 +287,7 @@ impl<'a> System<'a> for Sys { CharacterState::Idle => states::idle::Data.handle_event(&j, action), CharacterState::Talk => states::talk::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 => { states::glide_wield::Data.handle_event(&j, action) }, @@ -351,7 +351,7 @@ impl<'a> System<'a> for Sys { CharacterState::Idle => states::idle::Data.behavior(&j), CharacterState::Talk => states::talk::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::Stunned(data) => data.behavior(&j), CharacterState::Sit => states::sit::Data::behavior(&states::sit::Data, &j), diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 40566cf246..b2cceb3612 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -13,6 +13,7 @@ use common::{ event::{EventBus, ServerEvent}, outcome::Outcome, resources::DeltaTime, + states, terrain::{Block, TerrainGrid}, uid::Uid, util::Projection, @@ -64,9 +65,11 @@ fn fluid_density(height: f32, fluid: &Fluid) -> Density { fn integrate_forces( dt: &DeltaTime, mut vel: Vel, + ori: &Ori, body: &Body, density: &Density, mass: &Mass, + character_state: Option<&CharacterState>, fluid: &Fluid, gravity: f32, ) -> Vel { @@ -80,7 +83,24 @@ 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 impulse = dt.0 * body.aerodynamic_forces(&rel_flow, fluid_density.0); + let glider: Option<&states::glide::Data> = character_state.and_then(|cs| match cs { + CharacterState::Glide(data) => Some(data), + _ => None, + }); + // let wings: Option<(RigidWings, Ori)> = + // body.wings().map(|w| (w, ori)).or(character_state.and_then(|cs| { + // match cs { + // CharacterState::Glide(states::glide::Data{wings, ori}) => + // Some((*wings, *ori)), _ => None, + // } + // })); + let impulse = dt.0 + * body.aerodynamic_forces( + glider.map(|g| &g.ori).unwrap_or(ori), + &rel_flow, + fluid_density.0, + glider.map(|g| g.wings).or_else(|| body.wings()).as_ref(), + ); debug_assert!(!impulse.map(|a| a.is_nan()).reduce_or()); if !impulse.is_approx_zero() { let v_sq = rel_flow.0.magnitude_squared(); @@ -97,7 +117,8 @@ fn integrate_forces( // Hydrostatic/aerostatic forces // modify gravity to account for the effective density as a result of buoyancy - let down_force = dt.0 * gravity * (density.0 - fluid_density.0) / density.0; + let down_force = + dt.0 * inline_tweak::tweak!(1.0) * gravity * (density.0 - fluid_density.0) / density.0; vel.0.z -= down_force; vel @@ -578,7 +599,9 @@ impl<'a> PhysicsData<'a> { ( positions, velocities, + &write.orientations, &read.bodies, + read.character_states.maybe(), &write.physics_states, &read.masses, &read.densities, @@ -590,7 +613,7 @@ impl<'a> PhysicsData<'a> { prof_span!(guard, "velocity update rayon job"); guard }, - |_guard, (pos, vel, body, physics_state, mass, density, _)| { + |_guard, (pos, vel, ori, body, character_state, physics_state, mass, density, _)| { let in_loaded_chunk = read .terrain .get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) @@ -609,7 +632,7 @@ impl<'a> PhysicsData<'a> { }, Some(fluid) => { vel.0 = integrate_forces( - &dt, *vel, body, density, mass, &fluid, GRAVITY, + &dt, *vel, ori, body, density, mass, character_state, &fluid, GRAVITY, ) .0 }, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 04d7350d55..fd7a62f6f6 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -221,7 +221,7 @@ impl<'a> System<'a> for Sys { }); let is_gliding = matches!( read_data.char_states.get(entity), - Some(CharacterState::GlideWield) | Some(CharacterState::Glide) + Some(CharacterState::GlideWield) | Some(CharacterState::Glide(_)) ) && !physics_state.on_ground; // This controls how picky NPCs are about their pathfinding. Giants are larger diff --git a/voxygen/anim/src/character/gliding.rs b/voxygen/anim/src/character/gliding.rs index 68276ad03a..84de2cd208 100644 --- a/voxygen/anim/src/character/gliding.rs +++ b/voxygen/anim/src/character/gliding.rs @@ -11,8 +11,10 @@ type GlidingAnimationDependency = ( Option, Option, Vec3, - Vec3, - Vec3, + Quaternion, + Quaternion, + Quaternion, + f32, f32, ); @@ -27,90 +29,105 @@ impl Animation for GlidingAnimation { fn update_skeleton_inner( skeleton: &Self::Skeleton, - (_active_tool_kind, _second_tool_kind, velocity, orientation, last_ori, global_time): Self::Dependency, + ( + _active_tool_kind, + _second_tool_kind, + velocity, + orientation, + last_ori, + glider_orientation, + global_time, + acc_vel, + ): Self::Dependency, anim_time: f32, _rate: &mut f32, s_a: &SkeletonAttr, ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - let speed = Vec2::::from(velocity).magnitude(); + let speednorm = velocity.xy().magnitude().min(30.0) / 30.0; + let speedxyznorm = velocity.magnitude().min(30.0) / 30.0; - 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 slow = (acc_vel * 0.5).sin(); + let slowa = (acc_vel * 0.5 + PI / 2.0).sin(); let head_look = Vec2::new( - ((global_time + anim_time) / 5.0).floor().mul(7331.0).sin() * 0.5, - ((global_time + anim_time) / 5.0).floor().mul(1337.0).sin() * 0.25, + ((global_time + anim_time) as f32 / 4.0) + .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 = Vec2::from(orientation); - let last_ori = Vec2::from(last_ori); - let tilt = if ::vek::Vec2::new(ori, last_ori) - .map(|o| o.magnitude_squared()) - .map(|m| m > 0.0001 && m.is_finite()) - .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 tilt = { + let ori: Vec2 = Vec2::from(orientation * Vec3::unit_y()); + let last_ori: Vec2 = Vec2::from(last_ori * Vec3::unit_y()); + if ::vek::Vec2::new(ori, last_ori) + .map(|o| o.magnitude_squared()) + .map(|m| m > 0.001 && m.is_finite()) + .reduce_and() + && ori.angle_between(last_ori).is_finite() + { + ori.angle_between(last_ori).min(0.2) + * last_ori.determine_side(Vec2::zero(), ori).signum() + * 1.3 + } else { + 0.0 + } }; - - let tiltcancel = if anim_time > 1.0 { 1.0 } else { anim_time }; - - next.head.position = Vec3::new(0.0, s_a.head.0 + 1.0, s_a.head.1); - next.head.orientation = Quaternion::rotation_x(0.35 - slow * 0.10 + head_look.y) - * 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, + let torso_ori = Quaternion::slerp( + Quaternion::rotation_x(-0.06 * speednorm.max(5.0) + slow * 0.04) + * Quaternion::rotation_y(speednorm * tilt * 2.0 / speednorm.max(0.2)) + * Quaternion::rotation_z(speednorm * tilt * 3.0 * speednorm), + orientation.inverse() * glider_orientation, + 0.3, ); - 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); + let chest_ori = Quaternion::rotation_z(slowa * 0.01); + let chest_global_inv = (orientation * torso_ori * chest_ori).inverse(); + let glider_pos = Vec3::new(0.0, -5.0, 18.0); + let glider_ori = chest_global_inv * glider_orientation; + let center_of_rot = glider_pos * 0.8; - 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.head.orientation = Quaternion::rotation_x(head_look.y + speednorm.min(28.0) * 0.03) + * Quaternion::rotation_z(head_look.x); - 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.torso.position = + (center_of_rot - orientation.inverse() * glider_orientation * center_of_rot) / 11.0 + * s_a.scaler; + next.torso.orientation = torso_ori; + + next.chest.orientation = chest_ori; + + next.belt.orientation = Quaternion::rotation_z(slowa * 0.1); + + next.shorts.position = Vec3::new(s_a.shorts.0, 0.0, s_a.shorts.1); + next.shorts.orientation = chest_ori.inverse() * Quaternion::rotation_z(slowa * 0.12); + + 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 + -5.0); + 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 + -5.0); + 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, s_a.foot.2); + next.foot_l.orientation = + Quaternion::rotation_x(-0.8 * speedxyznorm + slow * -0.5 * speedxyznorm); + + next.foot_r.position = Vec3::new(s_a.foot.0, s_a.foot.1, s_a.foot.2); + next.foot_r.orientation = + Quaternion::rotation_x(-0.8 * speedxyznorm + slow * 0.5 * speedxyznorm); + + next.glider.position = glider_pos; + next.glider.orientation = glider_ori; next.glider.scale = Vec3::one(); - next.torso.position = Vec3::new(0.0, -1.0, 0.0) / 11.0 * s_a.scaler; - next.torso.orientation = Quaternion::rotation_x(-0.03 * speed.max(12.0) + slow * 0.04) - * Quaternion::rotation_y(tilt * tiltcancel * 32.0); - next } } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 72accad05c..746444c0f9 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -1445,17 +1445,18 @@ impl FigureMgr { ) } }, - CharacterState::Glide { .. } => { + CharacterState::Glide(data) => { anim::character::GlidingAnimation::update_skeleton( &target_base, ( active_tool_kind, second_tool_kind, rel_vel, - // TODO: Update to use the quaternion. - ori * anim::vek::Vec3::::unit_y(), - state.last_ori * anim::vek::Vec3::::unit_y(), + ori, + state.last_ori, + data.ori.into(), time, + state.acc_vel, ), state.state_time, &mut state_animation_rate,