diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 3cd9721a47..ea67f71a8b 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -72,6 +72,7 @@ const int WATER = 30; const int ICE_SPIKES = 31; const int DRIP = 32; const int TORNADO = 33; +const int VORTEX = 34; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -553,6 +554,29 @@ void main() { spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) ); break; + case VORTEX: + f_reflect = 1.0; + float mag = length(inst_dir); + float s = 4 * start_end(max(0, slow_start(2.0) - 0.3), 0); + float r = 0.2 * (pow(1 * rand0, 2) + pow(rand1 * 2, 2) + pow(rand2 * 4, 2)); + attr = Attr( + spiral_motion( + s * normalize(inst_dir) * mag, + 0.5 * max(0, slow_start(2.0) - 0.3) * max(0, floor(0.5 * r - 0.5)) * min(linear_scale(10), 1), + lifetime / (20.0 * inst_lifespan), + 0.4 * r, + 1.1 * inst_time * (length(inst_dir)) + ), + vec3( + (1.0 - 0.5 * abs(r)) + * (1.5 + 0.5 * sin(tick.x * 10 - lifetime * 10)) + * max(0, mag - 12) * s + * 0.02 + ), + vec4(vec3(1, 1, 1), 1), + spin_in_axis(inst_dir, tick.z) + ); + break; default: attr = Attr( linear_motion( diff --git a/common/src/comp/fluid_dynamics.rs b/common/src/comp/fluid_dynamics.rs index 370ef134ba..74136fd3fe 100644 --- a/common/src/comp/fluid_dynamics.rs +++ b/common/src/comp/fluid_dynamics.rs @@ -232,7 +232,7 @@ pub fn angle_of_attack(ori: &Ori, rel_flow_dir: &Dir) -> f32 { /// Total lift coefficient for a finite wing of symmetric aerofoil shape and /// elliptical pressure distribution. (Multiplied by reference area.) -fn lift_coefficient( +pub fn lift_coefficient( planform_area: f32, angle_of_attack: f32, lift_slope: f32, @@ -355,7 +355,7 @@ impl WingShape { } /// Induced drag coefficient (drag due to lift) -fn induced_drag_coefficient(aspect_ratio: f32, lift_coefficient: f32) -> f32 { +pub fn induced_drag_coefficient(aspect_ratio: f32, lift_coefficient: f32) -> f32 { let ar = aspect_ratio; if ar > 25.0 { tracing::warn!( diff --git a/common/src/glider.rs b/common/src/glider.rs index f36bd5594c..0c04d13ea1 100644 --- a/common/src/glider.rs +++ b/common/src/glider.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ fluid_dynamics::{Drag, Glide, WingShape}, - Ori, + Body, Ori, Pos, }, util::Dir, }; @@ -17,6 +17,7 @@ pub struct Glider { pub wing_shape: WingShape, pub planform_area: f32, pub ori: Ori, + span_length: f32, } impl Glider { @@ -32,10 +33,24 @@ impl Glider { Self { wing_shape: WingShape::Elliptical { aspect_ratio }, planform_area, + span_length, ori, } } + pub fn pos(&self, pilot: (&Pos, &Ori, &Body)) -> Pos { + let height = pilot.2.dimensions().z; + let cg = Vec3::unit_z() * height * tweak!(0.7); + let pos_from_cg = *pilot.1.up() * height * tweak!(0.5); + Pos(pilot.0.0 + cg + pos_from_cg) + } + + pub fn wing_tips(&self, pilot: (&Pos, &Ori, &Body)) -> (Pos, Pos) { + let right = tweak!(0.65) * *self.ori.right() * self.span_length / 2.0; + let pos = self.pos(pilot).0; + (Pos(pos - right), Pos(pos + right)) + } + 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); } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 381ff30cd2..b24489f172 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -84,6 +84,7 @@ pub enum ParticleMode { IceSpikes = 31, Drip = 32, Tornado = 33, + Vortex = 34, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 1b70bd660f..52b43c4b09 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -9,8 +9,10 @@ use crate::{ use common::{ assets::{AssetExt, DotVoxAsset}, comp::{ - self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body, - CharacterState, Ori, Pos, Shockwave, Vel, + self, aura, beam, body, buff, + fluid_dynamics::{angle_of_attack, induced_drag_coefficient, lift_coefficient, Glide}, + item::Reagent, + object, shockwave, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave, Vel, }, figure::Segment, outcome::Outcome, @@ -18,6 +20,7 @@ use common::{ spiral::Spiral2d, states::{self, utils::StageSection}, terrain::TerrainChunk, + util::Dir, vol::{RectRasterableVol, SizedVol}, }; use common_base::span; @@ -561,16 +564,69 @@ impl ParticleMgr { let dt = scene_data.state.get_delta_time(); let mut rng = thread_rng(); - for (entity, pos, vel, character_state, body) in ( + for (entity, pos, vel, ori, character_state, body) in ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), + &ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() { match character_state { + CharacterState::Glide(data) => { + let trail_tick_count = + 33.max(self.scheduler.heartbeats(Duration::from_millis(1))); + let trail = ecs + .read_storage::() + .get(entity) + .and_then(|physics| physics.in_fluid) + .and_then(|fluid| { + vel.map(|vel| (fluid.dynamic_pressure(vel), fluid.relative_flow(vel))) + }) + .and_then(|(q, rel_flow)| { + Some(rel_flow.0.magnitude_squared()) + .filter(|v_sq| v_sq > &std::f32::EPSILON) + .map(|v_sq| (q, Dir::new(rel_flow.0 / v_sq.sqrt()))) + }) + .map(|(q, flow_dir)| { + // hacky heuristic for vortex trails; they're nowhere near an accurate + // representation, but + // - it's sort of the right direction + // - it scales with induced drag (which is due to these vortices) + let wing_shape = data.glider.wing_shape(); + let aoa = angle_of_attack(data.glider.ori(), &flow_dir); + let c_l = lift_coefficient( + data.glider.planform_area(), + aoa, + wing_shape.lift_slope(), + wing_shape.stall_angle(), + ); + let c_d_i = induced_drag_coefficient(wing_shape.aspect_ratio(), c_l); + -q * *data.glider.ori().look_dir() * c_d_i.abs() + }) + .unwrap_or_default(); + + self.particles.reserve(trail_tick_count as usize); + for i in 0..trail_tick_count { + let (left_tip, right_tip) = data.glider.wing_tips((pos, ori, body)); + self.particles.push(Particle::new_directed( + Duration::from_millis(300), + time + i as f64 / 1000.0, + ParticleMode::Vortex, + left_tip.0, + left_tip.0 + trail * 0.2, + )); + self.particles.push(Particle::new_directed( + Duration::from_millis(300), + time + i as f64 / 1000.0, + ParticleMode::Vortex, + right_tip.0, + right_tip.0 + trail * 0.2, + )); + } + }, CharacterState::Boost(_) => { self.particles.resize_with( self.particles.len()