Merge branch 'sam/weapon-trails' into 'master'

Weapon trails

See merge request veloren/veloren!3143
This commit is contained in:
Samuel Keiffer 2022-03-03 06:53:40 +00:00
commit 31062c3761
55 changed files with 897 additions and 110 deletions

View File

@ -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

View File

@ -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",
],

View File

@ -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",

View File

@ -0,0 +1,44 @@
#version 420 core
#include <constants.glsl>
#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 <globals.glsl>
layout(location = 0) in vec3 f_pos;
layout(location = 0) out vec4 tgt_color;
#include <sky.glsl>
#include <light.glsl>
#include <lod.glsl>
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);
}

View File

@ -0,0 +1,13 @@
#version 420 core
#include <globals.glsl>
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);
}

View File

@ -344,9 +344,13 @@ impl From<AuxiliaryAbility> 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,
}
}
}

View File

@ -113,6 +113,8 @@ impl Skeleton for ArthropodSkeleton {
orientation: mount_orientation,
scale: Vec3::one(),
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -137,6 +137,8 @@ impl Skeleton for BipedLargeSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -83,6 +83,8 @@ impl Skeleton for BipedSmallSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -106,6 +106,8 @@ impl Skeleton for BirdLargeSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -60,6 +60,8 @@ impl Skeleton for BirdMediumSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -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) => {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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::<f32>::from(self.lantern);
let main_mat = control_l_mat * Mat4::<f32>::from(self.main);
let second_mat = control_r_mat * Mat4::<f32>::from(self.second);
let glider_mat = chest_mat * Mat4::<f32>::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::<f32>::from(self.foot_r)),
make_bone(chest_mat * Mat4::<f32>::from(self.shoulder_l)),
make_bone(chest_mat * Mat4::<f32>::from(self.shoulder_r)),
make_bone(chest_mat * Mat4::<f32>::from(self.glider)),
make_bone(control_l_mat * Mat4::<f32>::from(self.main)),
make_bone(control_r_mat * Mat4::<f32>::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::<f32>::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))
},
}
}
}

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -81,6 +81,8 @@ impl Skeleton for DragonSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -60,6 +60,8 @@ impl Skeleton for FishMediumSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -51,6 +51,8 @@ impl Skeleton for FishSmallSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -37,6 +37,8 @@ impl Skeleton for FixtureSkeleton {
Offsets {
lantern: None,
mount_bone: Transform::default(),
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -83,6 +83,8 @@ impl Skeleton for GolemSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -43,6 +43,8 @@ impl Skeleton for ItemDropSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -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<Vec3<f32>>,
pub mount_bone: Transform<f32, f32, f32>,
pub primary_trail_mat: Option<(Mat4<f32>, TrailSource)>,
pub secondary_trail_mat: Option<(Mat4<f32>, TrailSource)>,
}
#[derive(Clone, Copy)]
pub enum TrailSource {
Weapon,
GliderLeft,
GliderRight,
}
impl TrailSource {
pub fn relative_offsets(&self, tool: Option<ToolKind>) -> (Vec4<f32>, Vec4<f32>) {
// 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 {

View File

@ -49,6 +49,8 @@ impl Skeleton for ObjectSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -97,6 +97,8 @@ impl Skeleton for QuadrupedLowSkeleton {
orientation: mount_orientation,
scale: Vec3::one(),
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -109,6 +109,8 @@ impl Skeleton for QuadrupedMediumSkeleton {
orientation: mount_orientation,
scale: Vec3::one(),
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -74,6 +74,8 @@ impl Skeleton for QuadrupedSmallSkeleton {
orientation: mount_orientation,
scale: Vec3::one(),
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -53,6 +53,8 @@ impl Skeleton for ShipSkeleton {
),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -84,6 +84,8 @@ impl Skeleton for TheropodSkeleton {
.into(),
..Default::default()
},
primary_trail_mat: None,
secondary_trail_mat: None,
}
}
}

View File

@ -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
)
);
}

View File

@ -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

View File

@ -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"]

View File

@ -109,6 +109,10 @@ impl<V: Vertex> Mesh<V> {
pub fn iter_mut(&mut self, vertex_range: Range<usize>) -> std::slice::IterMut<V> {
self.verts[vertex_range].iter_mut()
}
pub fn len(&self) -> usize { self.verts.len() }
pub fn is_empty(&self) -> bool { self.len() == 0 }
}
impl<V: Vertex> IntoIterator for Mesh<V> {

View File

@ -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,
},

View File

@ -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,

View File

@ -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};

View File

@ -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<f32> for Vertex {
type Output = Self;
fn mul(self, val: f32) -> Self::Output {
Self {
pos: self.pos.map(|a| a * val),
}
}
}
impl Add<Vertex> 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<wgpu::IndexFormat> = Some(wgpu::IndexFormat::Uint16);
const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() 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,
}
}
}

View File

@ -1232,6 +1232,7 @@ impl Renderer {
/// Create a new dynamic model with the specified size.
pub fn create_dynamic_model<V: Vertex>(&mut self, size: usize) -> DynamicModel<V> {
self.ensure_sufficient_index_length::<V>(size);
DynamicModel::new(&self.device, size)
}

View File

@ -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<SecondPassDrawer> {
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<ColLights<terrain::Locals>>>,
@ -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<TrailDrawer<'_, 'pass>> {
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::<trail::Vertex>(&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>,
}

View File

@ -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<bloom::BloomPipelines>,
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<bloom::BloomPipelines>,
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,

View File

@ -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",

View File

@ -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<f32>,
@ -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<anim::vek::Vec3<f32>>,
main_abs_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
off_abs_trail_points: Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
// Animation to be applied to rider of this entity
mount_transform: anim::vek::Transform<f32, f32, f32>,
// Contains the position of this figure or if it is a rider it will contain the mount's
@ -6218,11 +6244,13 @@ impl<S> DerefMut for FigureState<S> {
/// Parameters that don't depend on the body variant or animation results and
/// are also not mutable
pub struct FigureUpdateCommonParameters<'a> {
pub entity: Option<EcsEntity>,
pub pos: anim::vek::Vec3<f32>,
pub ori: anim::vek::Quaternion<f32>,
pub scale: f32,
pub mount_transform_pos: Option<(anim::vek::Transform<f32, f32, f32>, anim::vek::Vec3<f32>)>,
pub body: Option<Body>,
pub tools: (Option<ToolKind>, Option<ToolKind>),
pub col: vek::Rgba<f32>,
pub dt: f32,
// TODO: evaluate unused variable
@ -6245,6 +6273,8 @@ impl<S: Skeleton> FigureState<S> {
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<S: Skeleton> FigureState<S> {
pub fn update<const N: usize>(
&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<S: Skeleton> FigureState<S> {
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<f32>, anim::TrailSource)>,
old_abs_trail_points: &mut Option<(anim::vek::Vec3<f32>, anim::vek::Vec3<f32>)>,
entity: EcsEntity,
is_main_weapon: bool,
pos: anim::vek::Vec3<f32>,
tool: Option<ToolKind>,
) {
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<f32>| 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);

View File

@ -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,
}
}

View File

@ -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<Light>,
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,

View File

@ -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,

View File

@ -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);
}
}

186
voxygen/src/scene/trail.rs Normal file
View File

@ -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<MeshKey, (Mesh<TrailVertex>, usize)>,
/// Position cache for things like projectiles
pos_cache: HashMap<EcsEntity, Pos>,
/// Offset
offset: usize,
/// Dynamic model to upload to GPU
dynamic_model: Option<DynamicModel<TrailVertex>>,
/// 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::<Body>(),
&ecs.read_storage::<Vel>(),
&ecs.read_storage::<Pos>(),
)
.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<f32>| 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<TrailVertex> {
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<TrailVertex> {
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 }
}

View File

@ -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,8 +1528,6 @@ impl PlayState for SessionState {
fn render<'a>(&'a self, drawer: &mut Drawer<'a>, settings: &Settings) {
span!(_guard, "render", "<Session as PlayState>::render");
// Render world
{
let client = self.client.borrow();
let scene_data = SceneData {
@ -1547,9 +1546,11 @@ impl PlayState for SessionState {
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
self.scene.render(
drawer,
client.state(),
@ -1557,14 +1558,22 @@ impl PlayState for SessionState {
client.get_tick(),
&scene_data,
);
}
if let Some(mut second_pass) = drawer.second_pass() {
// Clouds
{
prof_span!("clouds");
if let Some(mut second_pass) = drawer.second_pass() {
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)
{

View File

@ -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;

View File

@ -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),