diff --git a/CHANGELOG.md b/CHANGELOG.md index e3aa65f467..792dd049d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Waypoints saved between sessions and shared with group members. - New rocks +- Weapon trails ### Changed diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index b098b45dbf..ca53f57f08 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -517,7 +517,7 @@ ], threshold: 0.5, ), - Attack(BasicMelee, Dagger): ( + Attack(BasicMelee(Action), Dagger): ( files: [ "voxygen.audio.sfx.abilities.swing_sword", ], @@ -551,7 +551,7 @@ ], threshold: 0.5, ), - Attack(BasicMelee, Shield): ( + Attack(BasicMelee(Action), Shield): ( files: [ "voxygen.audio.sfx.abilities.swing", ], diff --git a/assets/voxygen/i18n/en/hud/settings.ron b/assets/voxygen/i18n/en/hud/settings.ron index 8a86db6ac4..cfe5ffab23 100644 --- a/assets/voxygen/i18n/en/hud/settings.ron +++ b/assets/voxygen/i18n/en/hud/settings.ron @@ -89,6 +89,7 @@ "hud.settings.gpu_profiler": "Enable GPU timing (not supported everywhere)", "hud.settings.particles": "Particles", "hud.settings.lossy_terrain_compression": "Lossy terrain compression", + "hud.settings.weapon_trails": "Weapon trails", "hud.settings.resolution": "Resolution", "hud.settings.bit_depth": "Bit Depth", "hud.settings.refresh_rate": "Refresh Rate", diff --git a/assets/voxygen/shaders/trail-frag.glsl b/assets/voxygen/shaders/trail-frag.glsl new file mode 100644 index 0000000000..23d6585f49 --- /dev/null +++ b/assets/voxygen/shaders/trail-frag.glsl @@ -0,0 +1,44 @@ +#version 420 core + +#include + +#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION + +#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY + +#if (FLUID_MODE == FLUID_MODE_CHEAP) + #define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE +#elif (FLUID_MODE == FLUID_MODE_SHINY) + #define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_RADIANCE +#endif + +#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET + +#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN + +#define HAS_SHADOW_MAPS + +#include + +layout(location = 0) in vec3 f_pos; + +layout(location = 0) out vec4 tgt_color; + +#include +#include +#include + +const float FADE_DIST = 32.0; + +void main() { + vec3 trail_color = vec3(.55, .92, 1.0); + float trail_alpha = 0.05; + // Controls how much light affects alpha variation. TODO: Maybe a better name? + float light_variable = 0.075; + + // Make less faint at day (relative to night) by adding light to alpha. Probably hacky but looks fine. + // TODO: Trails should also eventually account for shadows, nearby lights, attenuation of sunlight in water, and block based lighting. Note: many of these will require alternative methods that don't require a normal. + trail_alpha += get_sun_brightness() * light_variable; + + tgt_color = vec4(trail_color, trail_alpha); +} diff --git a/assets/voxygen/shaders/trail-vert.glsl b/assets/voxygen/shaders/trail-vert.glsl new file mode 100644 index 0000000000..445f44a3c3 --- /dev/null +++ b/assets/voxygen/shaders/trail-vert.glsl @@ -0,0 +1,13 @@ +#version 420 core + +#include + +layout(location = 0) in vec3 v_pos; + +layout(location = 0) out vec3 f_pos; + +void main() { + f_pos = v_pos; + + gl_Position = all_mat * vec4(f_pos - focus_off.xyz, 1); +} diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index fde2e1d6b9..a79eb54b6c 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -344,9 +344,13 @@ impl From for Ability { } } +/// A lighter form of character state to pass around as needed for frontend +/// purposes +// Only add to this enum as needed for frontends, not necessary to immediately +// add a variant here when adding a new character state #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)] pub enum CharacterAbilityType { - BasicMelee, + BasicMelee(StageSection), BasicRanged, Boost, ChargedMelee(StageSection), @@ -361,12 +365,13 @@ pub enum CharacterAbilityType { RepeaterRanged, BasicAura, SelfBuff, + Other, } impl From<&CharacterState> for CharacterAbilityType { fn from(state: &CharacterState) -> Self { match state { - CharacterState::BasicMelee(_) => Self::BasicMelee, + CharacterState::BasicMelee(data) => Self::BasicMelee(data.stage_section), CharacterState::BasicRanged(_) => Self::BasicRanged, CharacterState::Boost(_) => Self::Boost, CharacterState::DashMelee(data) => Self::DashMelee(data.stage_section), @@ -381,7 +386,23 @@ impl From<&CharacterState> for CharacterAbilityType { CharacterState::RepeaterRanged(_) => Self::RepeaterRanged, CharacterState::BasicAura(_) => Self::BasicAura, CharacterState::SelfBuff(_) => Self::SelfBuff, - _ => Self::BasicMelee, + CharacterState::Idle(_) + | CharacterState::Climb(_) + | CharacterState::Sit + | CharacterState::Dance + | CharacterState::Talk + | CharacterState::Glide(_) + | CharacterState::GlideWield(_) + | CharacterState::Stunned(_) + | CharacterState::Equipping(_) + | CharacterState::Wielding(_) + | CharacterState::Roll(_) + | CharacterState::Blink(_) + | CharacterState::BasicSummon(_) + | CharacterState::SpriteSummon(_) + | CharacterState::UseItem(_) + | CharacterState::SpriteInteract(_) + | CharacterState::Wallrun(_) => Self::Other, } } } diff --git a/voxygen/anim/src/arthropod/mod.rs b/voxygen/anim/src/arthropod/mod.rs index a0c25c2599..eefe828ae7 100644 --- a/voxygen/anim/src/arthropod/mod.rs +++ b/voxygen/anim/src/arthropod/mod.rs @@ -113,6 +113,8 @@ impl Skeleton for ArthropodSkeleton { orientation: mount_orientation, scale: Vec3::one(), }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/biped_large/mod.rs b/voxygen/anim/src/biped_large/mod.rs index 76f7a3cefb..1515bcd258 100644 --- a/voxygen/anim/src/biped_large/mod.rs +++ b/voxygen/anim/src/biped_large/mod.rs @@ -137,6 +137,8 @@ impl Skeleton for BipedLargeSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/biped_small/mod.rs b/voxygen/anim/src/biped_small/mod.rs index d1704bbf77..09fee85b28 100644 --- a/voxygen/anim/src/biped_small/mod.rs +++ b/voxygen/anim/src/biped_small/mod.rs @@ -83,6 +83,8 @@ impl Skeleton for BipedSmallSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/bird_large/mod.rs b/voxygen/anim/src/bird_large/mod.rs index fd6b6198f0..ef0caea2a1 100644 --- a/voxygen/anim/src/bird_large/mod.rs +++ b/voxygen/anim/src/bird_large/mod.rs @@ -106,6 +106,8 @@ impl Skeleton for BirdLargeSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/bird_medium/mod.rs b/voxygen/anim/src/bird_medium/mod.rs index dab98a6819..83ee2507a6 100644 --- a/voxygen/anim/src/bird_medium/mod.rs +++ b/voxygen/anim/src/bird_medium/mod.rs @@ -60,6 +60,8 @@ impl Skeleton for BirdMediumSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/character/alpha.rs b/voxygen/anim/src/character/alpha.rs index 12bd669cfb..878b386048 100644 --- a/voxygen/anim/src/character/alpha.rs +++ b/voxygen/anim/src/character/alpha.rs @@ -47,33 +47,36 @@ impl Animation for AlphaAnimation { next.torso.position = Vec3::new(0.0, 0.0, 1.1); next.torso.orientation = Quaternion::rotation_z(0.0); + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } match ability_info.and_then(|a| a.tool) { Some(ToolKind::Sword | ToolKind::Dagger) => { next.main.position = Vec3::new(0.0, 0.0, 0.0); next.main.orientation = Quaternion::rotation_x(0.0); - next.chest.orientation = - Quaternion::rotation_z(move1 * 1.1 + move2 * -2.0 + move3 * 0.2); + next.chest.orientation = Quaternion::rotation_z(move1 * 1.1 + move2 * -2.0); next.head.position = Vec3::new(0.0 + move2 * 2.0, s_a.head.0, s_a.head.1); - next.head.orientation = - Quaternion::rotation_z(move1 * -0.9 + move2 * 1.8 + move3 * -0.2); + next.head.orientation = Quaternion::rotation_z(move1 * -0.9 + move2 * 1.8); }, Some(ToolKind::Axe) => { - let (move1, move2, move3) = match stage_section { + let (move1, move2, _move3) = match stage_section { Some(StageSection::Buildup) => (anim_time.powf(0.25), 0.0, 0.0), Some(StageSection::Action) => (1.0, anim_time, 0.0), Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4)), _ => (0.0, 0.0, 0.0), }; next.head.position = Vec3::new(move2 * 2.0, s_a.head.0 + move2 * 2.0, s_a.head.1); - next.chest.orientation = - Quaternion::rotation_x(0.0 + move1 * 0.6 + move2 * -0.6 + move3 * 0.4) - * Quaternion::rotation_y(0.0 + move1 * 0.0 + move2 * 0.0 + move3 * 0.0) - * Quaternion::rotation_z(0.0 + move1 * 1.5 + move2 * -2.5 + move3 * 1.5); - next.head.orientation = - Quaternion::rotation_z(0.0 + move1 * -1.5 + move2 * 2.5 + move3 * -1.0); + next.chest.orientation = Quaternion::rotation_x(0.0 + move1 * 0.6 + move2 * -0.6) + * Quaternion::rotation_y(0.0 + move1 * 0.0 + move2 * 0.0) + * Quaternion::rotation_z(0.0 + move1 * 1.5 + move2 * -2.5); + next.head.orientation = Quaternion::rotation_z(0.0 + move1 * -1.5 + move2 * 2.5); }, Some(ToolKind::Hammer) | Some(ToolKind::Pick) => { diff --git a/voxygen/anim/src/character/beta.rs b/voxygen/anim/src/character/beta.rs index e22a9e1de3..7b07365779 100644 --- a/voxygen/anim/src/character/beta.rs +++ b/voxygen/anim/src/character/beta.rs @@ -41,6 +41,13 @@ impl Animation for BetaAnimation { Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4)), _ => (0.0, 0.0, 0.0), }; + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } next.second.position = Vec3::new(0.0, 0.0, 0.0); next.second.orientation = Quaternion::rotation_z(0.0); next.main.position = Vec3::new(0.0, 0.0, 0.0); diff --git a/voxygen/anim/src/character/chargeswing.rs b/voxygen/anim/src/character/chargeswing.rs index 3cb36eefe2..3b06dc707d 100644 --- a/voxygen/anim/src/character/chargeswing.rs +++ b/voxygen/anim/src/character/chargeswing.rs @@ -55,6 +55,13 @@ impl Animation for ChargeswingAnimation { _ => (0.0, 0.0, 0.0, 0.0, 0.0), }; + if matches!( + stage_section, + Some(StageSection::Charge | StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } let pullback = 1.0 - movement3; let move1 = move1base * pullback; let move2 = move2base * pullback; diff --git a/voxygen/anim/src/character/dash.rs b/voxygen/anim/src/character/dash.rs index a217f3fbb6..0183bec84c 100644 --- a/voxygen/anim/src/character/dash.rs +++ b/voxygen/anim/src/character/dash.rs @@ -42,6 +42,13 @@ impl Animation for DashAnimation { Some(StageSection::Recover) => (1.1, 1.0, 1.0, anim_time.powi(4)), _ => (0.0, 0.0, 0.0, 0.0), }; + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } let pullback = 1.0 - move4; let move1 = movement1 * pullback; let move2 = movement2 * pullback; diff --git a/voxygen/anim/src/character/gliding.rs b/voxygen/anim/src/character/gliding.rs index 7bacb1fe9d..4fd4f595fa 100644 --- a/voxygen/anim/src/character/gliding.rs +++ b/voxygen/anim/src/character/gliding.rs @@ -26,6 +26,8 @@ impl Animation for GlidingAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); + next.glider_trails = true; + let speednorm = velocity.magnitude().min(50.0) / 50.0; let slow = (acc_vel * 0.5).sin(); diff --git a/voxygen/anim/src/character/leapmelee.rs b/voxygen/anim/src/character/leapmelee.rs index 1c65b230b2..76312b3822 100644 --- a/voxygen/anim/src/character/leapmelee.rs +++ b/voxygen/anim/src/character/leapmelee.rs @@ -42,6 +42,13 @@ impl Animation for LeapAnimation { Some(StageSection::Recover) => (1.0, 1.0, 1.0, anim_time.powf(0.75)), _ => (0.0, 0.0, 0.0, 0.0), }; + if matches!( + stage_section, + Some(StageSection::Movement | StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } let pullback = 1.0 - move4; let move1 = movement1 * pullback; let move2 = movement2 * pullback; diff --git a/voxygen/anim/src/character/mod.rs b/voxygen/anim/src/character/mod.rs index db7a935e65..b4a1a9c0ca 100644 --- a/voxygen/anim/src/character/mod.rs +++ b/voxygen/anim/src/character/mod.rs @@ -49,7 +49,7 @@ pub use self::{ stunned::StunnedAnimation, swim::SwimAnimation, swimwield::SwimWieldAnimation, talk::TalkAnimation, wallrun::WallrunAnimation, wield::WieldAnimation, }; -use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton}; +use super::{make_bone, vek::*, FigureBoneData, Offsets, Skeleton, TrailSource}; use common::comp; use core::{convert::TryFrom, f32::consts::PI}; @@ -78,6 +78,10 @@ skeleton_impls!(struct CharacterSkeleton { control_r, :: // Begin non-bone fields holding_lantern: bool, + main_weapon_trail: bool, + off_weapon_trail: bool, + // Cannot exist at same time as weapon trails. Since gliding and attacking are mutually exclusive, should never be a concern. + glider_trails: bool, }); impl CharacterSkeleton { @@ -125,6 +129,9 @@ impl Skeleton for CharacterSkeleton { } else { shorts_mat } * Mat4::::from(self.lantern); + let main_mat = control_l_mat * Mat4::::from(self.main); + let second_mat = control_r_mat * Mat4::::from(self.second); + let glider_mat = chest_mat * Mat4::::from(self.glider); *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ make_bone(head_mat), @@ -138,13 +145,14 @@ impl Skeleton for CharacterSkeleton { make_bone(torso_mat * Mat4::::from(self.foot_r)), make_bone(chest_mat * Mat4::::from(self.shoulder_l)), make_bone(chest_mat * Mat4::::from(self.shoulder_r)), - make_bone(chest_mat * Mat4::::from(self.glider)), - make_bone(control_l_mat * Mat4::::from(self.main)), - make_bone(control_r_mat * Mat4::::from(self.second)), + make_bone(glider_mat), + make_bone(main_mat), + make_bone(second_mat), make_bone(lantern_mat), // FIXME: Should this be control_l_mat? make_bone(control_mat * hand_l_mat * Mat4::::from(self.hold)), ]; + let weapon_trails = self.main_weapon_trail || self.off_weapon_trail; Offsets { lantern: Some((lantern_mat * Vec4::new(0.0, 0.5, -6.0, 1.0)).xyz()), // TODO: see quadruped_medium for how to animate this @@ -155,6 +163,20 @@ impl Skeleton for CharacterSkeleton { .into(), ..Default::default() }, + primary_trail_mat: if weapon_trails { + self.main_weapon_trail + .then_some((main_mat, TrailSource::Weapon)) + } else { + self.glider_trails + .then_some((glider_mat, TrailSource::GliderLeft)) + }, + secondary_trail_mat: if weapon_trails { + self.off_weapon_trail + .then_some((second_mat, TrailSource::Weapon)) + } else { + self.glider_trails + .then_some((glider_mat, TrailSource::GliderRight)) + }, } } } diff --git a/voxygen/anim/src/character/shockwave.rs b/voxygen/anim/src/character/shockwave.rs index 6c7ef2dd9c..0ae43008dc 100644 --- a/voxygen/anim/src/character/shockwave.rs +++ b/voxygen/anim/src/character/shockwave.rs @@ -43,6 +43,13 @@ impl Animation for ShockwaveAnimation { _ => (0.0, 0.0, 0.0), }; + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); next.hand_l.position = Vec3::new(s_a.sthl.0, s_a.sthl.1, s_a.sthl.2); diff --git a/voxygen/anim/src/character/spin.rs b/voxygen/anim/src/character/spin.rs index 8aa3357bbf..a9db898be6 100644 --- a/voxygen/anim/src/character/spin.rs +++ b/voxygen/anim/src/character/spin.rs @@ -50,6 +50,13 @@ impl Animation for SpinAnimation { next.main.orientation = Quaternion::rotation_x(0.0); next.head.position = Vec3::new(0.0, s_a.head.0, s_a.head.1); + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } match ability_info.and_then(|a| a.tool) { Some(ToolKind::Sword) => { next.head.position = Vec3::new( diff --git a/voxygen/anim/src/character/spinmelee.rs b/voxygen/anim/src/character/spinmelee.rs index f994546c4a..7d3cc13047 100644 --- a/voxygen/anim/src/character/spinmelee.rs +++ b/voxygen/anim/src/character/spinmelee.rs @@ -39,10 +39,18 @@ impl Animation for SpinMeleeAnimation { Some(StageSection::Recover) => (1.0, 1.0, anim_time.powf(2.0)), _ => (0.0, 0.0, 0.0), }; + let pullback = 1.0 - movement3; let move1 = movement1 * pullback; let move2 = movement2 * pullback; let mut next = (*skeleton).clone(); + if matches!( + stage_section, + Some(StageSection::Action | StageSection::Recover) + ) { + next.main_weapon_trail = true; + next.off_weapon_trail = true; + } next.main.position = Vec3::new(0.0, 0.0, 0.0); next.main.orientation = Quaternion::rotation_z(0.0); next.second.position = Vec3::new(0.0, 0.0, 0.0); diff --git a/voxygen/anim/src/dragon/mod.rs b/voxygen/anim/src/dragon/mod.rs index 7b82a7cc6a..d89d29d1d6 100644 --- a/voxygen/anim/src/dragon/mod.rs +++ b/voxygen/anim/src/dragon/mod.rs @@ -81,6 +81,8 @@ impl Skeleton for DragonSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/fish_medium/mod.rs b/voxygen/anim/src/fish_medium/mod.rs index 80685b7ea3..9b79e27d47 100644 --- a/voxygen/anim/src/fish_medium/mod.rs +++ b/voxygen/anim/src/fish_medium/mod.rs @@ -60,6 +60,8 @@ impl Skeleton for FishMediumSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/fish_small/mod.rs b/voxygen/anim/src/fish_small/mod.rs index a24dca196c..4848d69c11 100644 --- a/voxygen/anim/src/fish_small/mod.rs +++ b/voxygen/anim/src/fish_small/mod.rs @@ -51,6 +51,8 @@ impl Skeleton for FishSmallSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/fixture/mod.rs b/voxygen/anim/src/fixture/mod.rs index aa5b3e85fd..31f7d59d68 100644 --- a/voxygen/anim/src/fixture/mod.rs +++ b/voxygen/anim/src/fixture/mod.rs @@ -37,6 +37,8 @@ impl Skeleton for FixtureSkeleton { Offsets { lantern: None, mount_bone: Transform::default(), + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/golem/mod.rs b/voxygen/anim/src/golem/mod.rs index 4f3391f88b..bbe7920ad3 100644 --- a/voxygen/anim/src/golem/mod.rs +++ b/voxygen/anim/src/golem/mod.rs @@ -83,6 +83,8 @@ impl Skeleton for GolemSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/item_drop/mod.rs b/voxygen/anim/src/item_drop/mod.rs index 018be5b393..feaae4a369 100644 --- a/voxygen/anim/src/item_drop/mod.rs +++ b/voxygen/anim/src/item_drop/mod.rs @@ -43,6 +43,8 @@ impl Skeleton for ItemDropSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/lib.rs b/voxygen/anim/src/lib.rs index 0f9e54c801..3f0432713c 100644 --- a/voxygen/anim/src/lib.rs +++ b/voxygen/anim/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(generic_associated_types)] +#![feature(generic_associated_types, bool_to_option)] #![allow(incomplete_features)] #[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))] compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once"); @@ -69,6 +69,7 @@ pub mod vek; use self::vek::*; use bytemuck::{Pod, Zeroable}; +use common::comp::tool::ToolKind; #[cfg(feature = "use-dyn-lib")] use { lazy_static::lazy_static, std::ffi::CStr, std::sync::Arc, std::sync::Mutex, @@ -103,6 +104,50 @@ pub fn init() { lazy_static::initialize(&LIB); } pub struct Offsets { pub lantern: Option>, pub mount_bone: Transform, + pub primary_trail_mat: Option<(Mat4, TrailSource)>, + pub secondary_trail_mat: Option<(Mat4, TrailSource)>, +} + +#[derive(Clone, Copy)] +pub enum TrailSource { + Weapon, + GliderLeft, + GliderRight, +} + +impl TrailSource { + pub fn relative_offsets(&self, tool: Option) -> (Vec4, Vec4) { + // Offsets + const GLIDER_VERT: f32 = 5.0; + const GLIDER_HORIZ: f32 = 15.0; + // Trail width + const GLIDER_WIDTH: f32 = 1.0; + + match self { + Self::Weapon => { + let lengths = match tool { + Some(ToolKind::Sword) => (0.0, 29.25), + Some(ToolKind::Axe) => (10.0, 19.25), + Some(ToolKind::Hammer) => (10.0, 19.25), + Some(ToolKind::Staff) => (10.0, 19.25), + Some(ToolKind::Sceptre) => (10.0, 19.25), + _ => (0.0, 0.0), + }; + ( + Vec4::new(0.0, 0.0, lengths.0, 1.0), + Vec4::new(0.0, 0.0, lengths.1, 1.0), + ) + }, + Self::GliderLeft => ( + Vec4::new(GLIDER_HORIZ, 0.0, GLIDER_VERT, 1.0), + Vec4::new(GLIDER_HORIZ + GLIDER_WIDTH, 0.0, GLIDER_VERT, 1.0), + ), + Self::GliderRight => ( + Vec4::new(-GLIDER_HORIZ, 0.0, GLIDER_VERT, 1.0), + Vec4::new(-(GLIDER_HORIZ + GLIDER_WIDTH), 0.0, GLIDER_VERT, 1.0), + ), + } + } } pub trait Skeleton: Send + Sync + 'static { diff --git a/voxygen/anim/src/object/mod.rs b/voxygen/anim/src/object/mod.rs index 7d4cbbb2aa..8562efc17f 100644 --- a/voxygen/anim/src/object/mod.rs +++ b/voxygen/anim/src/object/mod.rs @@ -49,6 +49,8 @@ impl Skeleton for ObjectSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/quadruped_low/mod.rs b/voxygen/anim/src/quadruped_low/mod.rs index 66a70fec99..9d4379e7c8 100644 --- a/voxygen/anim/src/quadruped_low/mod.rs +++ b/voxygen/anim/src/quadruped_low/mod.rs @@ -97,6 +97,8 @@ impl Skeleton for QuadrupedLowSkeleton { orientation: mount_orientation, scale: Vec3::one(), }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/quadruped_medium/mod.rs b/voxygen/anim/src/quadruped_medium/mod.rs index a55e58b6e2..f077b21679 100644 --- a/voxygen/anim/src/quadruped_medium/mod.rs +++ b/voxygen/anim/src/quadruped_medium/mod.rs @@ -109,6 +109,8 @@ impl Skeleton for QuadrupedMediumSkeleton { orientation: mount_orientation, scale: Vec3::one(), }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/quadruped_small/mod.rs b/voxygen/anim/src/quadruped_small/mod.rs index fb9e21ffec..a45f22d580 100644 --- a/voxygen/anim/src/quadruped_small/mod.rs +++ b/voxygen/anim/src/quadruped_small/mod.rs @@ -74,6 +74,8 @@ impl Skeleton for QuadrupedSmallSkeleton { orientation: mount_orientation, scale: Vec3::one(), }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/ship/mod.rs b/voxygen/anim/src/ship/mod.rs index 9c02b67178..92eec74962 100644 --- a/voxygen/anim/src/ship/mod.rs +++ b/voxygen/anim/src/ship/mod.rs @@ -53,6 +53,8 @@ impl Skeleton for ShipSkeleton { ), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/anim/src/theropod/mod.rs b/voxygen/anim/src/theropod/mod.rs index 21db915f25..74f3c949c3 100644 --- a/voxygen/anim/src/theropod/mod.rs +++ b/voxygen/anim/src/theropod/mod.rs @@ -84,6 +84,8 @@ impl Skeleton for TheropodSkeleton { .into(), ..Default::default() }, + primary_trail_mat: None, + secondary_trail_mat: None, } } } diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs index 6495771f27..f4b4e89e38 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs @@ -91,7 +91,7 @@ fn maps_basic_melee() { ability_info: empty_ability_info(), }, timer: Duration::default(), - stage_section: states::utils::StageSection::Buildup, + stage_section: states::utils::StageSection::Action, exhausted: false, }), &PreviousEntityState { @@ -104,7 +104,10 @@ fn maps_basic_melee() { assert_eq!( result, - SfxEvent::Attack(CharacterAbilityType::BasicMelee, ToolKind::Axe) + SfxEvent::Attack( + CharacterAbilityType::BasicMelee(states::utils::StageSection::Action), + ToolKind::Axe + ) ); } diff --git a/voxygen/src/hud/settings_window/video.rs b/voxygen/src/hud/settings_window/video.rs index 4a4185d651..0d85de8537 100644 --- a/voxygen/src/hud/settings_window/video.rs +++ b/voxygen/src/hud/settings_window/video.rs @@ -101,6 +101,8 @@ widget_ids! { particles_label, lossy_terrain_compression_button, lossy_terrain_compression_label, + weapon_trails_button, + weapon_trails_label, // fullscreen_button, fullscreen_label, @@ -1164,6 +1166,31 @@ impl<'a> Widget for Video<'a> { )); } + // Weapon trails + Text::new(self.localized_strings.get("hud.settings.weapon_trails")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .right_from(state.ids.lossy_terrain_compression_label, 64.0) + .color(TEXT_COLOR) + .set(state.ids.weapon_trails_label, ui); + + let weapon_trails_enabled = ToggleButton::new( + self.global_state.settings.graphics.weapon_trails_enabled, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.weapon_trails_label, 10.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.weapon_trails_button, ui); + + if self.global_state.settings.graphics.weapon_trails_enabled != weapon_trails_enabled { + events.push(GraphicsChange::ToggleWeaponTrailsEnabled( + weapon_trails_enabled, + )); + } + // Resolution let resolutions: Vec<[u16; 2]> = state .video_modes diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index bd54371f29..4b51c032fb 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -9,7 +9,8 @@ drain_filter, once_cell, trait_alias, - option_get_or_insert_default + option_get_or_insert_default, + map_try_insert )] #![recursion_limit = "2048"] diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index b673ec01f2..12b3e53be7 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -109,6 +109,10 @@ impl Mesh { pub fn iter_mut(&mut self, vertex_range: Range) -> std::slice::IterMut { self.verts[vertex_range].iter_mut() } + + pub fn len(&self) -> usize { self.verts.len() } + + pub fn is_empty(&self) -> bool { self.len() == 0 } } impl IntoIterator for Mesh { diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index e1c9f4a7b4..0a7a9556fc 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -36,6 +36,7 @@ pub use self::{ Vertex as SpriteVertex, VERT_PAGE_SIZE as SPRITE_VERT_PAGE_SIZE, }, terrain::{Locals as TerrainLocals, TerrainLayout, Vertex as TerrainVertex}, + trail::Vertex as TrailVertex, ui::{ create_quad as create_ui_quad, create_quad_vert_gradient as create_ui_quad_vert_gradient, create_tri as create_ui_tri, @@ -48,7 +49,7 @@ pub use self::{ drawer::{ DebugDrawer, Drawer, FigureDrawer, FigureShadowDrawer, FirstPassDrawer, ParticleDrawer, PreparedUiDrawer, SecondPassDrawer, ShadowPassDrawer, SpriteDrawer, TerrainDrawer, - TerrainShadowDrawer, ThirdPassDrawer, UiDrawer, + TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer, UiDrawer, }, ColLightInfo, Renderer, }, diff --git a/voxygen/src/render/pipelines/clouds.rs b/voxygen/src/render/pipelines/clouds.rs index 7796f47689..95e03f65dc 100644 --- a/voxygen/src/render/pipelines/clouds.rs +++ b/voxygen/src/render/pipelines/clouds.rs @@ -180,7 +180,22 @@ impl CloudsPipeline { polygon_mode: wgpu::PolygonMode::Fill, conservative: false, }, - depth_stencil: None, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::Always, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: 0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), multisample: wgpu::MultisampleState { count: samples, mask: !0, diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index fac31813dc..f7476b6969 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -11,6 +11,7 @@ pub mod shadow; pub mod skybox; pub mod sprite; pub mod terrain; +pub mod trail; pub mod ui; use super::{Consts, Texture}; diff --git a/voxygen/src/render/pipelines/trail.rs b/voxygen/src/render/pipelines/trail.rs new file mode 100644 index 0000000000..5584db3a61 --- /dev/null +++ b/voxygen/src/render/pipelines/trail.rs @@ -0,0 +1,144 @@ +use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait}; +use bytemuck::{Pod, Zeroable}; +use std::{ + mem, + ops::{Add, Mul}, +}; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod, PartialEq)] +pub struct Vertex { + pub pos: [f32; 3], +} + +impl Vertex { + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![0 => Float32x3]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } + + pub fn zero() -> Self { Self { pos: [0.0; 3] } } +} + +impl Mul for Vertex { + type Output = Self; + + fn mul(self, val: f32) -> Self::Output { + Self { + pos: self.pos.map(|a| a * val), + } + } +} + +impl Add for Vertex { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + Self { + pos: self.pos.zip(other.pos).map(|(a, b)| a + b), + } + } +} + +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = Some(wgpu::IndexFormat::Uint16); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +pub struct TrailPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl TrailPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "TrailPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Trail pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[&global_layout.globals, &global_layout.shadow_textures], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Trail pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: 0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + // TODO: use a constant and/or pass in this format on pipeline construction + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::One, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index c07ccb4439..1ce1db0668 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1232,6 +1232,7 @@ impl Renderer { /// Create a new dynamic model with the specified size. pub fn create_dynamic_model(&mut self, size: usize) -> DynamicModel { + self.ensure_sufficient_index_length::(size); DynamicModel::new(&self.device, size) } diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index 26549224d6..1ce91101ca 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -5,7 +5,7 @@ use super::{ model::{DynamicModel, Model, SubModel}, pipelines::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, - sprite, terrain, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, + sprite, terrain, trail, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, }, }, Renderer, ShadowMap, ShadowMapRenderer, @@ -214,8 +214,7 @@ impl<'frame> Drawer<'frame> { /// Returns None if the clouds pipeline is not available pub fn second_pass(&mut self) -> Option { - let pipeline = &self.borrow.pipelines.all()?.clouds; - + let pipelines = &self.borrow.pipelines.all()?; let encoder = self.encoder.as_mut().unwrap(); let device = self.borrow.device; let mut render_pass = @@ -229,7 +228,11 @@ impl<'frame> Drawer<'frame> { store: true, }, }], - depth_stencil_attachment: None, + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.borrow.views.tgt_depth, + depth_ops: None, + stencil_ops: None, + }), }); render_pass.set_bind_group(0, &self.globals.bind_group, &[]); @@ -237,7 +240,8 @@ impl<'frame> Drawer<'frame> { Some(SecondPassDrawer { render_pass, borrow: &self.borrow, - pipeline, + clouds_pipeline: &pipelines.clouds, + trail_pipeline: &pipelines.trail, }) } @@ -603,6 +607,7 @@ impl<'frame> Drop for Drawer<'frame> { } // Shadow pass +#[must_use] pub struct ShadowPassDrawer<'pass> { render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, borrow: &'pass RendererBorrow<'pass>, @@ -633,6 +638,7 @@ impl<'pass> ShadowPassDrawer<'pass> { } } +#[must_use] pub struct FigureShadowDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } @@ -650,6 +656,7 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureShadowDrawer<'pass_ref, 'pass> { } } +#[must_use] pub struct TerrainShadowDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } @@ -668,6 +675,7 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> { } // First pass +#[must_use] pub struct FirstPassDrawer<'pass> { pub(super) render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, borrow: &'pass RendererBorrow<'pass>, @@ -766,6 +774,7 @@ impl<'pass> FirstPassDrawer<'pass> { } } +#[must_use] pub struct DebugDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, shadows: &'pass ShadowTexturesBindGroup, @@ -791,6 +800,8 @@ impl<'pass_ref, 'pass: 'pass_ref> Drop for DebugDrawer<'pass_ref, 'pass> { .set_bind_group(1, &self.shadows.bind_group, &[]); } } + +#[must_use] pub struct FigureDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } @@ -812,6 +823,7 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> { } } +#[must_use] pub struct TerrainDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, col_lights: Option<&'pass_ref Arc>>, @@ -842,6 +854,7 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> { } } +#[must_use] pub struct ParticleDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } @@ -863,6 +876,7 @@ impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> { } } +#[must_use] pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, globals: &'pass GlobalsBindGroup, @@ -895,6 +909,7 @@ impl<'pass_ref, 'pass: 'pass_ref> Drop for SpriteDrawer<'pass_ref, 'pass> { } } +#[must_use] pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } @@ -913,22 +928,52 @@ impl<'pass_ref, 'pass: 'pass_ref> FluidDrawer<'pass_ref, 'pass> { } // Second pass: clouds +#[must_use] pub struct SecondPassDrawer<'pass> { render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, borrow: &'pass RendererBorrow<'pass>, - pipeline: &'pass clouds::CloudsPipeline, + clouds_pipeline: &'pass clouds::CloudsPipeline, + trail_pipeline: &'pass trail::TrailPipeline, } impl<'pass> SecondPassDrawer<'pass> { pub fn draw_clouds(&mut self) { - self.render_pass.set_pipeline(&self.pipeline.pipeline); + self.render_pass + .set_pipeline(&self.clouds_pipeline.pipeline); self.render_pass .set_bind_group(1, &self.borrow.locals.clouds_bind.bind_group, &[]); self.render_pass.draw(0..3, 0..1); } + + pub fn draw_trails(&mut self) -> Option> { + let shadow = &self.borrow.shadow?; + + let mut render_pass = self.render_pass.scope("trails", self.borrow.device); + + render_pass.set_pipeline(&self.trail_pipeline.pipeline); + set_quad_index_buffer::(&mut render_pass, self.borrow); + + render_pass.set_bind_group(1, &shadow.bind.bind_group, &[]); + + Some(TrailDrawer { render_pass }) + } +} + +#[must_use] +pub struct TrailDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> TrailDrawer<'pass_ref, 'pass> { + pub fn draw(&mut self, submodel: SubModel<'pass, trail::Vertex>) { + self.render_pass.set_vertex_buffer(0, submodel.buf()); + self.render_pass + .draw_indexed(0..submodel.len() / 4 * 6, 0, 0..1); + } } /// Third pass: postprocess + ui +#[must_use] pub struct ThirdPassDrawer<'pass> { render_pass: OwningScope<'pass, wgpu::RenderPass<'pass>>, borrow: &'pass RendererBorrow<'pass>, @@ -961,10 +1006,12 @@ impl<'pass> ThirdPassDrawer<'pass> { } } +#[must_use] pub struct UiDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, } +#[must_use] pub struct PreparedUiDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: &'pass_ref mut wgpu::RenderPass<'pass>, } diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index f28834a1fa..a4e8e34126 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -2,7 +2,7 @@ use super::{ super::{ pipelines::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, - skybox, sprite, terrain, ui, + skybox, sprite, terrain, trail, ui, }, AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, ShadowMode, @@ -20,6 +20,7 @@ pub struct Pipelines { pub fluid: fluid::FluidPipeline, pub lod_terrain: lod_terrain::LodTerrainPipeline, pub particle: particle::ParticlePipeline, + pub trail: trail::TrailPipeline, pub clouds: clouds::CloudsPipeline, pub bloom: Option, pub postprocess: postprocess::PostProcessPipeline, @@ -40,6 +41,7 @@ pub struct IngamePipelines { fluid: fluid::FluidPipeline, lod_terrain: lod_terrain::LodTerrainPipeline, particle: particle::ParticlePipeline, + trail: trail::TrailPipeline, clouds: clouds::CloudsPipeline, pub bloom: Option, postprocess: postprocess::PostProcessPipeline, @@ -76,6 +78,7 @@ impl Pipelines { fluid: ingame.fluid, lod_terrain: ingame.lod_terrain, particle: ingame.particle, + trail: ingame.trail, clouds: ingame.clouds, bloom: ingame.bloom, postprocess: ingame.postprocess, @@ -105,6 +108,8 @@ struct ShaderModules { sprite_frag: wgpu::ShaderModule, particle_vert: wgpu::ShaderModule, particle_frag: wgpu::ShaderModule, + trail_vert: wgpu::ShaderModule, + trail_frag: wgpu::ShaderModule, ui_vert: wgpu::ShaderModule, ui_frag: wgpu::ShaderModule, lod_terrain_vert: wgpu::ShaderModule, @@ -290,6 +295,8 @@ impl ShaderModules { sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?, particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?, particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?, + trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?, + trail_frag: create_shader("trail-frag", ShaderKind::Fragment)?, ui_vert: create_shader("ui-vert", ShaderKind::Vertex)?, ui_frag: create_shader("ui-frag", ShaderKind::Fragment)?, lod_terrain_vert: create_shader("lod-terrain-vert", ShaderKind::Vertex)?, @@ -407,7 +414,8 @@ fn create_interface_pipelines( fn create_ingame_and_shadow_pipelines( needs: PipelineNeeds, pool: &rayon::ThreadPool, - tasks: [Task; 14], + // TODO: Reduce the boilerplate in this file + tasks: [Task; 15], ) -> IngameAndShadowPipelines { prof_span!(_guard, "create_ingame_and_shadow_pipelines"); @@ -427,6 +435,7 @@ fn create_ingame_and_shadow_pipelines( fluid_task, sprite_task, particle_task, + trail_task, lod_terrain_task, clouds_task, bloom_task, @@ -552,6 +561,21 @@ fn create_ingame_and_shadow_pipelines( "particle pipeline creation", ) }; + // Pipeline for rendering weapon trails + let create_trail = || { + trail_task.run( + || { + trail::TrailPipeline::new( + device, + &shaders.trail_vert, + &shaders.trail_frag, + &layouts.global, + pipeline_modes.aa, + ) + }, + "trail pipeline creation", + ) + }; // Pipeline for rendering terrain let create_lod_terrain = || { lod_terrain_task.run( @@ -696,7 +720,11 @@ fn create_ingame_and_shadow_pipelines( let j1 = || pool.join(create_debug, || pool.join(create_skybox, create_figure)); let j2 = || pool.join(create_terrain, || pool.join(create_fluid, create_bloom)); let j3 = || pool.join(create_sprite, create_particle); - let j4 = || pool.join(create_lod_terrain, create_clouds); + let j4 = || { + pool.join(create_lod_terrain, || { + pool.join(create_clouds, create_trail) + }) + }; let j5 = || pool.join(create_postprocess, create_point_shadow); let j6 = || { pool.join( @@ -709,7 +737,7 @@ fn create_ingame_and_shadow_pipelines( let ( ( ((debug, (skybox, figure)), (terrain, (fluid, bloom))), - ((sprite, particle), (lod_terrain, clouds)), + ((sprite, particle), (lod_terrain, (clouds, trail))), ), ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), ) = pool.join( @@ -724,6 +752,7 @@ fn create_ingame_and_shadow_pipelines( fluid, lod_terrain, particle, + trail, clouds, bloom, postprocess, diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index 0904d37c66..3a1bb00cfd 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -63,6 +63,8 @@ impl assets::Compound for Shaders { "sprite-frag", "particle-vert", "particle-frag", + "trail-vert", + "trail-frag", "ui-vert", "ui-frag", "lod-terrain-vert", diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index ea0689c14e..34efadb2ac 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -9,15 +9,15 @@ pub use volume::VolumeKey; use crate::{ ecs::comp::Interpolated, render::{ - pipelines::{self, ColLights}, + pipelines::{self, trail, ColLights}, ColLightInfo, FigureBoneData, FigureDrawer, FigureLocals, FigureModel, FigureShadowDrawer, - Mesh, RenderError, Renderer, SubModel, TerrainVertex, + Mesh, Quad, RenderError, Renderer, SubModel, TerrainVertex, }, scene::{ camera::{Camera, CameraMode, Dependents}, math, terrain::Terrain, - SceneData, + SceneData, TrailMgr, }, }; use anim::{ @@ -571,6 +571,7 @@ impl FigureMgr { pub fn maintain( &mut self, renderer: &mut Renderer, + trail_mgr: &mut TrailMgr, scene_data: &SceneData, // Visible chunk data. visible_psr_bounds: math::Aabr, @@ -814,7 +815,7 @@ impl FigureMgr { .unwrap_or_else(|| vek::Rgba::broadcast(1.0)) // Highlight targeted collectible entities * if item.is_some() && scene_data.target_entity.map_or(false, |e| e == entity) { - vek::Rgba::new(5.0, 5.0, 5.0, 1.0) + vek::Rgba::new(1.5, 1.5, 1.5, 1.0) } else { vek::Rgba::one() }; @@ -854,12 +855,17 @@ impl FigureMgr { let body = *body; + // Only use trail manager when trails are enabled + let trail_mgr = scene_data.weapon_trails_enabled.then(|| &mut *trail_mgr); + let common_params = FigureUpdateCommonParameters { + entity: Some(entity), pos: pos.0, ori, scale, mount_transform_pos, body: Some(body), + tools: (active_tool_kind, second_tool_kind), col, dt, _lpindex: lpindex, @@ -1759,6 +1765,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -1958,6 +1965,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -2274,6 +2282,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -2631,6 +2640,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -2734,6 +2744,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -2816,6 +2827,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_base, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -3342,6 +3354,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -3428,6 +3441,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_base, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -3606,6 +3620,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -3897,6 +3912,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -4219,6 +4235,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -4301,6 +4318,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_base, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -4922,6 +4940,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -5162,6 +5181,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -5289,6 +5309,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -5351,6 +5372,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -5385,6 +5407,7 @@ impl FigureMgr { state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -5478,6 +5501,7 @@ impl FigureMgr { state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, + trail_mgr, &mut update_buf, &common_params, state_animation_rate, @@ -6170,6 +6194,8 @@ impl FigureColLights { pub struct FigureStateMeta { lantern_offset: Option>, + main_abs_trail_points: Option<(anim::vek::Vec3, anim::vek::Vec3)>, + off_abs_trail_points: Option<(anim::vek::Vec3, anim::vek::Vec3)>, // Animation to be applied to rider of this entity mount_transform: anim::vek::Transform, // Contains the position of this figure or if it is a rider it will contain the mount's @@ -6218,11 +6244,13 @@ impl DerefMut for FigureState { /// Parameters that don't depend on the body variant or animation results and /// are also not mutable pub struct FigureUpdateCommonParameters<'a> { + pub entity: Option, pub pos: anim::vek::Vec3, pub ori: anim::vek::Quaternion, pub scale: f32, pub mount_transform_pos: Option<(anim::vek::Transform, anim::vek::Vec3)>, pub body: Option, + pub tools: (Option, Option), pub col: vek::Rgba, pub dt: f32, // TODO: evaluate unused variable @@ -6245,6 +6273,8 @@ impl FigureState { Self { meta: FigureStateMeta { lantern_offset: offsets.lantern, + main_abs_trail_points: None, + off_abs_trail_points: None, mount_transform: offsets.mount_bone, mount_world_pos: anim::vek::Vec3::zero(), state_time: 0.0, @@ -6266,13 +6296,16 @@ impl FigureState { pub fn update( &mut self, renderer: &mut Renderer, + trail_mgr: Option<&mut TrailMgr>, buf: &mut [anim::FigureBoneData; anim::MAX_BONE_COUNT], FigureUpdateCommonParameters { + entity, pos, ori, scale, mount_transform_pos, body, + tools, col, dt, _lpindex, @@ -6410,6 +6443,55 @@ impl FigureState { renderer.update_consts(&mut self.meta.bound.1, &new_bone_consts[0..S::BONE_COUNT]); self.lantern_offset = offsets.lantern; + // Handle weapon trails + fn handle_weapon_trails( + trail_mgr: &mut TrailMgr, + new_weapon_trail_mat: Option<(anim::vek::Mat4, anim::TrailSource)>, + old_abs_trail_points: &mut Option<(anim::vek::Vec3, anim::vek::Vec3)>, + entity: EcsEntity, + is_main_weapon: bool, + pos: anim::vek::Vec3, + tool: Option, + ) { + let weapon_offsets = new_weapon_trail_mat.map(|(mat, trail)| { + let (trail_start, trail_end) = trail.relative_offsets(tool); + ((mat * trail_start).xyz(), (mat * trail_end).xyz()) + }); + let new_abs_trail_points = weapon_offsets.map(|(a, b)| (a + pos, b + pos)); + if let (Some((p1, p2)), Some((p4, p3))) = (&old_abs_trail_points, new_abs_trail_points) + { + let trail_mgr_offset = trail_mgr.offset(); + let quad_mesh = trail_mgr.entity_mesh_or_insert(entity, is_main_weapon); + let vertex = |p: anim::vek::Vec3| trail::Vertex { + pos: p.into_array(), + }; + let quad = Quad::new(vertex(*p1), vertex(*p2), vertex(p3), vertex(p4)); + quad_mesh.replace_quad(trail_mgr_offset * 4, quad); + } + *old_abs_trail_points = new_abs_trail_points; + } + + if let (Some(trail_mgr), Some(entity)) = (trail_mgr, entity) { + handle_weapon_trails( + trail_mgr, + offsets.primary_trail_mat, + &mut self.main_abs_trail_points, + *entity, + true, + *pos, + tools.0, + ); + handle_weapon_trails( + trail_mgr, + offsets.secondary_trail_mat, + &mut self.off_abs_trail_points, + *entity, + false, + *pos, + tools.1, + ); + } + // TODO: compute the mount bone only when it is needed self.mount_transform = offsets.mount_bone; self.mount_world_pos = mount_transform_pos.map_or(*pos, |(_, pos)| pos); diff --git a/voxygen/src/scene/figure/volume.rs b/voxygen/src/scene/figure/volume.rs index 92f1ced12b..ac31a55b57 100644 --- a/voxygen/src/scene/figure/volume.rs +++ b/voxygen/src/scene/figure/volume.rs @@ -49,6 +49,8 @@ impl anim::Skeleton for VolumeKey { anim::Offsets { lantern: None, mount_bone: anim::vek::Transform::default(), + primary_trail_mat: None, + secondary_trail_mat: None, } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 336356d9d5..36b81468ae 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -6,6 +6,7 @@ pub mod math; pub mod particle; pub mod simple; pub mod terrain; +pub mod trail; pub use self::{ camera::{Camera, CameraMode}, @@ -14,6 +15,7 @@ pub use self::{ lod::Lod, particle::ParticleMgr, terrain::{SpriteRenderContextLazy, Terrain}, + trail::TrailMgr, }; use crate::{ audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend}, @@ -95,6 +97,7 @@ pub struct Scene { light_data: Vec, particle_mgr: ParticleMgr, + trail_mgr: TrailMgr, figure_mgr: FigureMgr, pub sfx_mgr: SfxMgr, music_mgr: MusicMgr, @@ -115,6 +118,7 @@ pub struct SceneData<'a> { pub mouse_smoothing: bool, pub sprite_render_distance: f32, pub particles_enabled: bool, + pub weapon_trails_enabled: bool, pub figure_lod_render_distance: f32, pub is_aiming: bool, } @@ -305,6 +309,7 @@ impl Scene { select_pos: None, light_data: Vec::new(), particle_mgr: ParticleMgr::new(renderer), + trail_mgr: TrailMgr::default(), figure_mgr: FigureMgr::new(renderer), sfx_mgr: SfxMgr::default(), music_mgr: MusicMgr::default(), @@ -327,6 +332,9 @@ impl Scene { /// Get a reference to the scene's particle manager. pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr } + /// Get a reference to the scene's trail manager. + pub fn trail_mgr(&self) -> &TrailMgr { &self.trail_mgr } + /// Get a reference to the scene's figure manager. pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr } @@ -547,6 +555,9 @@ impl Scene { self.particle_mgr .maintain(renderer, scene_data, &self.terrain, lights); + // Maintain the trails. + self.trail_mgr.maintain(renderer, scene_data); + // Update light constants lights.extend( ( @@ -687,6 +698,7 @@ impl Scene { // Maintain the figures. let _figure_bounds = self.figure_mgr.maintain( renderer, + &mut self.trail_mgr, scene_data, visible_psr_bounds, &self.camera, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index e4611f553a..44e52f0326 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -325,12 +325,6 @@ impl ParticleMgr { Body::Object(object::Body::CampfireLit) => { self.maintain_campfirelit_particles(scene_data, pos, vel) }, - Body::Object( - object::Body::Arrow - | object::Body::MultiArrow - | object::Body::ArrowSnake - | object::Body::ArrowTurret, - ) => self.maintain_arrow_particles(scene_data, pos, vel), Body::Object(object::Body::BoltFire) => { self.maintain_boltfire_particles(scene_data, pos, vel) }, @@ -390,33 +384,6 @@ impl ParticleMgr { } } - fn maintain_arrow_particles(&mut self, scene_data: &SceneData, pos: &Pos, vel: Option<&Vel>) { - const MIN_SPEED: f32 = 15.0; - // Don't emit particles for immobile arrows - if vel.map_or(true, |v| v.0.magnitude_squared() < MIN_SPEED.powi(2)) { - return; - } - - span!( - _guard, - "arrow_particles", - "ParticleMgr::maintain_arrow_particles" - ); - let time = scene_data.state.get_time(); - let dt = scene_data.state.get_delta_time(); - - let count = self.scheduler.heartbeats(Duration::from_millis(2)); - for i in 0..count { - let proportion = i as f32 / count as f32; - self.particles.push(Particle::new( - Duration::from_millis(200), - time, - ParticleMode::StaticSmoke, - pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * proportion), - )); - } - } - fn maintain_boltfire_particles( &mut self, scene_data: &SceneData, diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index f6fc3b6d28..e8989ac394 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -148,6 +148,7 @@ impl Scene { .create_figure(renderer, greedy.finalize(), (opaque_mesh, bounds), [range]); let mut buf = [Default::default(); anim::MAX_BONE_COUNT]; let common_params = FigureUpdateCommonParameters { + entity: None, pos: anim::vek::Vec3::zero(), ori: anim::vek::Quaternion::rotation_from_to_3d( anim::vek::Vec3::unit_y(), @@ -156,6 +157,7 @@ impl Scene { scale: 1.0, mount_transform_pos: None, body: None, + tools: (None, None), col: Rgba::broadcast(1.0), dt: 15.0, // Want to get there immediately. _lpindex: 0, @@ -165,7 +167,15 @@ impl Scene { terrain: None, ground_vel: Vec3::zero(), }; - state.update(renderer, &mut buf, &common_params, 1.0, Some(&model), ()); + state.update( + renderer, + None, + &mut buf, + &common_params, + 1.0, + Some(&model), + (), + ); (model, state) }), col_lights, @@ -330,6 +340,7 @@ impl Scene { .0; let mut buf = [Default::default(); anim::MAX_BONE_COUNT]; let common_params = FigureUpdateCommonParameters { + entity: None, pos: anim::vek::Vec3::zero(), ori: anim::vek::Quaternion::rotation_from_to_3d( anim::vek::Vec3::unit_y(), @@ -338,6 +349,7 @@ impl Scene { scale: 1.0, mount_transform_pos: None, body: None, + tools: (None, None), col: Rgba::broadcast(1.0), dt: scene_data.delta_time, _lpindex: 0, @@ -348,7 +360,7 @@ impl Scene { ground_vel: Vec3::zero(), }; - figure_state.update(renderer, &mut buf, &common_params, 1.0, model, body); + figure_state.update(renderer, None, &mut buf, &common_params, 1.0, model, body); } } diff --git a/voxygen/src/scene/trail.rs b/voxygen/src/scene/trail.rs new file mode 100644 index 0000000000..67b5f3c74b --- /dev/null +++ b/voxygen/src/scene/trail.rs @@ -0,0 +1,186 @@ +use super::SceneData; +use crate::render::{DynamicModel, Mesh, Quad, Renderer, TrailDrawer, TrailVertex}; +use common::comp::{object, Body, Pos, Vel}; +use common_base::span; +use specs::{Entity as EcsEntity, Join, WorldExt}; +use std::collections::HashMap; +use vek::*; + +#[derive(Clone, Copy, Eq, PartialEq, Hash)] +struct MeshKey { + entity: EcsEntity, + is_main_weapon: bool, +} + +#[derive(Default)] +pub struct TrailMgr { + /// Meshes for each entity, usize is the last offset tick it was updated + entity_meshes: HashMap, usize)>, + + /// Position cache for things like projectiles + pos_cache: HashMap, + + /// Offset + offset: usize, + + /// Dynamic model to upload to GPU + dynamic_model: Option>, + + /// Used to create sub model from dynamic model + model_len: u32, +} + +const TRAIL_DYNAMIC_MODEL_SIZE: usize = 15; +const TRAIL_SHRINKAGE: f32 = 0.8; + +impl TrailMgr { + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + span!(_guard, "maintain", "TrailMgr::maintain"); + + if scene_data.weapon_trails_enabled { + // Hack to shove trails in for projectiles + let ecs = scene_data.state.ecs(); + for (entity, body, vel, pos) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() + { + const MIN_SPEED: f32 = 15.0; + if vel.0.magnitude_squared() > MIN_SPEED.powi(2) + && matches!( + body, + Body::Object( + object::Body::Arrow + | object::Body::MultiArrow + | object::Body::ArrowSnake + | object::Body::ArrowTurret, + ) + ) + { + let last_pos = *self.pos_cache.entry(entity).or_insert(*pos); + let offset = self.offset; + let quad_mesh = self.entity_mesh_or_insert(entity, true); + const THICKNESS: f32 = 0.05; + let p1 = pos.0; + let p2 = p1 + Vec3::unit_z() * THICKNESS; + let p4 = last_pos.0; + let p3 = p4 + Vec3::unit_z() * THICKNESS; + let vertex = |p: Vec3| TrailVertex { + pos: p.into_array(), + }; + let quad = Quad::new(vertex(p1), vertex(p2), vertex(p3), vertex(p4)); + quad_mesh.replace_quad(offset * 4, quad); + self.pos_cache.insert(entity, *pos); + } + } + + // Update offset + self.offset = (self.offset + 1) % TRAIL_DYNAMIC_MODEL_SIZE; + + self.entity_meshes.values_mut().for_each(|(mesh, _)| { + // TODO: Figure out how to do this in shader files instead + // Shrink size of each quad over time + let vertices = mesh.vertices_mut_vec(); + let last_offset = + (self.offset + TRAIL_DYNAMIC_MODEL_SIZE - 1) % TRAIL_DYNAMIC_MODEL_SIZE; + let next_offset = (self.offset + 1) % TRAIL_DYNAMIC_MODEL_SIZE; + for i in 0..TRAIL_DYNAMIC_MODEL_SIZE { + // Verts per quad are in b, c, a, d order + let [b, c, a, d] = [0, 1, 2, 3].map(|offset| i * 4 + offset); + vertices[a] = if i == next_offset { + vertices[b] + } else { + vertices[a] * TRAIL_SHRINKAGE + vertices[b] * (1.0 - TRAIL_SHRINKAGE) + }; + if i != last_offset { + // Avoid shrinking edge of most recent quad so that edges of quads align + vertices[d] = + vertices[d] * TRAIL_SHRINKAGE + vertices[c] * (1.0 - TRAIL_SHRINKAGE); + } + } + + // Reset quad for each entity mesh at new offset + let zero = TrailVertex::zero(); + mesh.replace_quad(self.offset * 4, Quad::new(zero, zero, zero, zero)); + }); + + // Clear meshes for entities that only have zero quads in mesh + self.entity_meshes + .retain(|_, (_mesh, last_updated)| *last_updated != self.offset); + + // TODO: as an optimization we can keep this big mesh around between frames and + // write directly to it for each entity. + // Create big mesh from currently existing meshes that is used to update dynamic + // model + let mut big_mesh = Mesh::new(); + self.entity_meshes + .values() + // If any of the vertices in a mesh are non-zero, upload the entire mesh for the entity + .filter(|(mesh, _)| mesh.iter().any(|vert| *vert != TrailVertex::zero())) + .for_each(|(mesh, _)| big_mesh.push_mesh(mesh)); + + // To avoid empty mesh + if big_mesh.is_empty() { + let zero = TrailVertex::zero(); + big_mesh.push_quad(Quad::new(zero, zero, zero, zero)); + } + + // If dynamic model too small, resize, with room for 10 additional entities to + // avoid needing to resize frequently + if self.dynamic_model.as_ref().map_or(0, |model| model.len()) < big_mesh.len() { + self.dynamic_model = Some( + renderer + .create_dynamic_model(big_mesh.len() + TRAIL_DYNAMIC_MODEL_SIZE * 4 * 10), + ); + } + if let Some(dynamic_model) = &self.dynamic_model { + renderer.update_model(dynamic_model, &big_mesh, 0); + } + self.model_len = big_mesh.len() as u32; + } else { + self.entity_meshes.clear(); + // Clear dynamic model to free memory + self.dynamic_model = None; + } + } + + pub fn render<'a>(&'a self, drawer: &mut TrailDrawer<'_, 'a>, scene_data: &SceneData) { + span!(_guard, "render", "TrailMgr::render"); + if scene_data.weapon_trails_enabled { + if let Some(dynamic_model) = &self.dynamic_model { + drawer.draw(dynamic_model.submodel(0..self.model_len)) + } + } + } + + pub fn entity_mesh_or_insert( + &mut self, + entity: EcsEntity, + is_main_weapon: bool, + ) -> &mut Mesh { + let key = MeshKey { + entity, + is_main_weapon, + }; + &mut self + .entity_meshes + .entry(key) + .and_modify(|(_mesh, offset)| *offset = self.offset) + .or_insert((Self::default_trail_mesh(), self.offset)) + .0 + } + + fn default_trail_mesh() -> Mesh { + let mut mesh = Mesh::new(); + let zero = TrailVertex::zero(); + for _ in 0..TRAIL_DYNAMIC_MODEL_SIZE { + mesh.push_quad(Quad::new(zero, zero, zero, zero)); + } + mesh + } + + pub fn offset(&self) -> usize { self.offset } +} diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index dabbf889d0..495aa31209 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -1474,6 +1474,7 @@ impl PlayState for SessionState { sprite_render_distance: global_state.settings.graphics.sprite_render_distance as f32, particles_enabled: global_state.settings.graphics.particles_enabled, + weapon_trails_enabled: global_state.settings.graphics.weapon_trails_enabled, figure_lod_render_distance: global_state .settings .graphics @@ -1527,44 +1528,52 @@ impl PlayState for SessionState { fn render<'a>(&'a self, drawer: &mut Drawer<'a>, settings: &Settings) { span!(_guard, "render", "::render"); + let client = self.client.borrow(); + + let scene_data = SceneData { + client: &client, + state: client.state(), + player_entity: client.entity(), + // Only highlight if interactable + target_entity: self.interactable.and_then(Interactable::entity), + loaded_distance: client.loaded_distance(), + view_distance: client.view_distance().unwrap_or(1), + tick: client.get_tick(), + gamma: settings.graphics.gamma, + exposure: settings.graphics.exposure, + ambiance: settings.graphics.ambiance, + mouse_smoothing: settings.gameplay.smooth_pan_enable, + sprite_render_distance: settings.graphics.sprite_render_distance as f32, + figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32, + particles_enabled: settings.graphics.particles_enabled, + weapon_trails_enabled: settings.graphics.weapon_trails_enabled, + is_aiming: self.is_aiming, + }; + // Render world - { - let client = self.client.borrow(); + self.scene.render( + drawer, + client.state(), + client.entity(), + client.get_tick(), + &scene_data, + ); - let scene_data = SceneData { - client: &client, - state: client.state(), - player_entity: client.entity(), - // Only highlight if interactable - target_entity: self.interactable.and_then(Interactable::entity), - loaded_distance: client.loaded_distance(), - view_distance: client.view_distance().unwrap_or(1), - tick: client.get_tick(), - gamma: settings.graphics.gamma, - exposure: settings.graphics.exposure, - ambiance: settings.graphics.ambiance, - mouse_smoothing: settings.gameplay.smooth_pan_enable, - sprite_render_distance: settings.graphics.sprite_render_distance as f32, - figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32, - particles_enabled: settings.graphics.particles_enabled, - is_aiming: self.is_aiming, - }; - - self.scene.render( - drawer, - client.state(), - client.entity(), - client.get_tick(), - &scene_data, - ); - } - - // Clouds - { - prof_span!("clouds"); - if let Some(mut second_pass) = drawer.second_pass() { + if let Some(mut second_pass) = drawer.second_pass() { + // Clouds + { + prof_span!("clouds"); second_pass.draw_clouds(); } + // Trails + { + prof_span!("trails"); + if let Some(mut trail_drawer) = second_pass.draw_trails() { + self.scene + .trail_mgr() + .render(&mut trail_drawer, &scene_data); + } + } } // Bloom (call does nothing if bloom is off) { diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index b164acade5..79608fab23 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -86,6 +86,7 @@ pub enum Graphics { ChangeFullscreenMode(FullScreenSettings), ToggleParticlesEnabled(bool), ToggleLossyTerrainCompression(bool), + ToggleWeaponTrailsEnabled(bool), AdjustWindowSize([u16; 2]), ResetGraphicsSettings, @@ -402,6 +403,9 @@ impl SettingsChange { .borrow_mut() .request_lossy_terrain_compression(lossy_terrain_compression); }, + Graphics::ToggleWeaponTrailsEnabled(weapon_trails_enabled) => { + settings.graphics.weapon_trails_enabled = weapon_trails_enabled; + }, Graphics::AdjustWindowSize(new_size) => { global_state.window.set_size(new_size.into()); settings.graphics.window_size = new_size; diff --git a/voxygen/src/settings/graphics.rs b/voxygen/src/settings/graphics.rs index 2ef28c494a..5c9b8b5f4a 100644 --- a/voxygen/src/settings/graphics.rs +++ b/voxygen/src/settings/graphics.rs @@ -33,6 +33,7 @@ pub struct GraphicsSettings { pub sprite_render_distance: u32, pub particles_enabled: bool, pub lossy_terrain_compression: bool, + pub weapon_trails_enabled: bool, pub figure_lod_render_distance: u32, pub max_fps: Fps, pub max_background_fps: Fps, @@ -53,6 +54,7 @@ impl Default for GraphicsSettings { sprite_render_distance: 100, particles_enabled: true, lossy_terrain_compression: false, + weapon_trails_enabled: true, figure_lod_render_distance: 300, max_fps: Fps::Max(60), max_background_fps: Fps::Max(30),