From 42a10a6059524c439a4c524111a595f13db097b7 Mon Sep 17 00:00:00 2001 From: scott-c Date: Mon, 29 Jun 2020 19:18:19 +0800 Subject: [PATCH 01/28] Add ParticleEmitter Component --- common/src/comp/ability.rs | 6 +- common/src/comp/inventory/item/tool.rs | 140 ++++++++++++++----------- common/src/comp/mod.rs | 2 +- common/src/comp/visual.rs | 34 ++++++ common/src/event.rs | 1 + common/src/msg/ecs_packet.rs | 7 ++ common/src/state.rs | 1 + common/src/states/basic_ranged.rs | 7 +- server/src/cmd.rs | 1 + server/src/events/entity_creation.rs | 9 +- server/src/events/mod.rs | 3 +- server/src/sys/sentinel.rs | 22 +++- 12 files changed, 161 insertions(+), 72 deletions(-) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 79292b797e..e37e6bf26d 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2,7 +2,8 @@ use crate::{ comp::{ ability::Stage, item::{armor::Protection, Item, ItemKind}, - Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate, + Body, CharacterState, EnergySource, Gravity, LightEmitter, ParticleEmitter, Projectile, + StateUpdate, }, states::{triple_strike::*, *}, sys::character_behavior::JoinData, @@ -61,6 +62,7 @@ pub enum CharacterAbility { projectile: Projectile, projectile_body: Body, projectile_light: Option, + projectile_particles: Option, projectile_gravity: Option, }, Boost { @@ -247,6 +249,7 @@ impl From<&CharacterAbility> for CharacterState { projectile, projectile_body, projectile_light, + projectile_particles, projectile_gravity, energy_cost: _, } => CharacterState::BasicRanged(basic_ranged::Data { @@ -258,6 +261,7 @@ impl From<&CharacterAbility> for CharacterState { projectile: projectile.clone(), projectile_body: *projectile_body, projectile_light: *projectile_light, + projectile_particles: *projectile_particles, projectile_gravity: *projectile_gravity, }), CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data { diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 401f9925c7..d7b4524243 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,7 +2,8 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, + body::object, projectile, Body, CharacterAbility, Gravity, HealthChange, HealthSource, + LightEmitter, Projectile, ParticleEmitter, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -179,6 +180,7 @@ impl Tool { }, projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, + projectile_particles: None, projectile_gravity: Some(Gravity(0.2)), }, ChargedRanged { @@ -193,6 +195,8 @@ impl Tool { recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, + projectile_particles: None, + projectile_gravity: Some(Gravity(0.05)), }, ], Dagger(_) => vec![ @@ -261,40 +265,68 @@ impl Tool { col: (0.85, 0.5, 0.11).into(), ..Default::default() }), - projectile_gravity: None, - }, - BasicRanged { - energy_cost: 400, - holdable: true, - prepare_duration: Duration::from_millis(800), - recover_duration: Duration::from_millis(50), - projectile: Projectile { - hit_solid: vec![ - projectile::Effect::Explode { - power: 1.4 * self.base_power(), - }, - projectile::Effect::Vanish, - ], - hit_entity: vec![ - projectile::Effect::Explode { - power: 1.4 * self.base_power(), - }, - projectile::Effect::Vanish, - ], - time_left: Duration::from_secs(20), - owner: None, - }, - projectile_body: Body::Object(object::Body::BoltFireBig), - projectile_light: Some(LightEmitter { - col: (1.0, 0.75, 0.11).into(), - ..Default::default() - }), - - projectile_gravity: None, - }, - ] - } - }, + projectile::Effect::RewardEnergy(150), + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(20), + owner: None, + }, + projectile_body: Body::Object(object::Body::BoltFire), + projectile_light: Some(LightEmitter { + col: (0.85, 0.5, 0.11).into(), + ..Default::default() + }), + projectile_particles: Some(ParticleEmitter { + mode: 0, + }), + projectile_gravity: None, + }, + BasicRanged { + energy_cost: 400, + holdable: true, + prepare_duration: Duration::from_millis(800), + recover_duration: Duration::from_millis(50), + projectile: Projectile { + hit_solid: vec![ + projectile::Effect::Explode { power: 1.4 }, + projectile::Effect::Vanish, + ], + hit_entity: vec![ + projectile::Effect::Explode { power: 1.4 }, + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(20), + owner: None, + }, + projectile_body: Body::Object(object::Body::BoltFireBig), + projectile_light: Some(LightEmitter { + col: (1.0, 0.75, 0.11).into(), + ..Default::default() + }), + projectile_particles: Some(ParticleEmitter { + mode: 0, + }), + projectile_gravity: None, + }, + ], + Staff(StaffKind::Sceptre) => vec![ + BasicMelee { + energy_cost: 0, + buildup_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(300), + base_healthchange: -1, + range: 10.0, + max_angle: 45.0, + }, + BasicMelee { + energy_cost: 350, + buildup_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(1000), + base_healthchange: 15, + range: 10.0, + max_angle: 45.0, + }, + ], Shield(_) => vec![ BasicMelee { energy_cost: 0, @@ -313,35 +345,15 @@ impl Tool { duration: Duration::from_millis(50), only_up: false, }, - CharacterAbility::Boost { - duration: Duration::from_millis(50), - only_up: true, - }, - BasicRanged { - energy_cost: 0, - holdable: false, - prepare_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(10), - projectile: Projectile { - hit_solid: vec![projectile::Effect::Stick], - hit_entity: vec![ - projectile::Effect::Stick, - projectile::Effect::Possess, - ], - time_left: Duration::from_secs(10), - owner: None, - }, - projectile_body: Body::Object(object::Body::ArrowSnake), - projectile_light: Some(LightEmitter { - col: (0.0, 1.0, 0.33).into(), - ..Default::default() - }), - projectile_gravity: None, - }, - ] - } else { - vec![] - } + projectile_body: Body::Object(object::Body::ArrowSnake), + projectile_light: Some(LightEmitter { + col: (0.0, 1.0, 0.33).into(), + ..Default::default() + }), + projectile_particles: None, + projectile_gravity: None, + }, + ], }, Empty => vec![BasicMelee { energy_cost: 0, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index ccef80dc8b..a17da95189 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -53,4 +53,4 @@ pub use player::{Player, MAX_MOUNT_RANGE_SQR}; pub use projectile::Projectile; pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet}; pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; -pub use visual::{LightAnimation, LightEmitter}; +pub use visual::{LightAnimation, LightEmitter, ParticleEmitter}; diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index 013f27aa2c..b1d0f29cae 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -46,3 +46,37 @@ impl Default for LightAnimation { impl Component for LightAnimation { type Storage = FlaggedStorage>; } + + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ParticleEmitter { + + /// Mode 1: sprinkler (inital_velocity, lifespan) + /// Mode 2: smoke (initial_position, boyancy_const, wind, lifespan) + pub mode: u8, // enum? + + // pub vertices: Vec, + // pub texture: RasterFooBar, + + // // mode 1 -- sprinkler. + // pub initial_position: [i8; 3], + // pub initial_velocity: [i8; 3], + // pub lifespan: u32, // in ticks? + + // // mode 2 -- smoke + // pub initial_position: [i8; 3], + // pub boyancy_const: [i8; 3], + // pub wind_sway: [i8; 3], +} + +impl Default for ParticleEmitter { + fn default() -> Self { + Self { + mode: 0, + } + } +} + +impl Component for ParticleEmitter { + type Storage = FlaggedStorage>; +} \ No newline at end of file diff --git a/common/src/event.rs b/common/src/event.rs index 943757a2a7..f291971c25 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -43,6 +43,7 @@ pub enum ServerEvent { dir: Dir, body: comp::Body, light: Option, + particles: Option, projectile: comp::Projectile, gravity: Option, }, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 726d67321a..e8fa12c33d 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -15,6 +15,7 @@ sum_type! { Stats(comp::Stats), Energy(comp::Energy), LightEmitter(comp::LightEmitter), + ParticleEmitter(comp::ParticleEmitter), Item(comp::Item), Scale(comp::Scale), Group(comp::Group), @@ -42,6 +43,7 @@ sum_type! { Stats(PhantomData), Energy(PhantomData), LightEmitter(PhantomData), + ParticleEmitter(PhantomData), Item(PhantomData), Scale(PhantomData), Group(PhantomData), @@ -69,6 +71,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::ParticleEmitter(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world), @@ -94,6 +97,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::ParticleEmitter(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world), @@ -121,6 +125,9 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::LightEmitter(_) => { sync::handle_remove::(entity, world) }, + EcsCompPhantom::ParticleEmitter(_) => { + sync::handle_remove::(entity, world) + }, EcsCompPhantom::Item(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Scale(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Group(_) => sync::handle_remove::(entity, world), diff --git a/common/src/state.rs b/common/src/state.rs index 3d003b7a4f..c4798c5813 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -113,6 +113,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index b41bd6c208..647b166230 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Body, CharacterState, Gravity, LightEmitter, Projectile, StateUpdate}, + comp::{Body, CharacterState, Gravity, LightEmitter, ParticleEmitter, Projectile, StateUpdate}, event::ServerEvent, states::utils::*, sys::character_behavior::*, @@ -20,6 +20,7 @@ pub struct Data { pub projectile: Projectile, pub projectile_body: Body, pub projectile_light: Option, + pub projectile_particles: Option, pub projectile_gravity: Option, /// Whether the attack fired already pub exhausted: bool, @@ -48,6 +49,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, + projectile_particles: self.projectile_particles, projectile_gravity: self.projectile_gravity, exhausted: false, }); @@ -61,6 +63,7 @@ impl CharacterBehavior for Data { body: self.projectile_body, projectile, light: self.projectile_light, + particles: self.projectile_particles, gravity: self.projectile_gravity, }); @@ -72,6 +75,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, + projectile_particles: self.projectile_particles, projectile_gravity: self.projectile_gravity, exhausted: true, }); @@ -88,6 +92,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, + projectile_particles: self.projectile_particles, projectile_gravity: self.projectile_gravity, exhausted: true, }); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b580cfbd98..14c7b39338 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -910,6 +910,7 @@ fn handle_light( .create_entity_synced() .with(pos) .with(comp::ForceUpdate) + .with(comp::ParticleEmitter { mode: 0 }) .with(light_emitter); if let Some(light_offset) = light_offset_opt { builder.with(light_offset).build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index b2924d9d15..d2f3b9d774 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,8 +1,8 @@ use crate::{sys, Server, StateExt}; use common::{ comp::{ - self, group, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos, - Projectile, Scale, Stats, Vel, WaypointArea, + self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, + ParticleEmitter, Pos, Projectile, Scale, Stats, Vel, WaypointArea, }, util::Dir, }; @@ -77,6 +77,7 @@ pub fn handle_shoot( dir: Dir, body: Body, light: Option, + particles: Option, projectile: Projectile, gravity: Option, ) { @@ -96,6 +97,9 @@ pub fn handle_shoot( if let Some(light) = light { builder = builder.with(light) } + if let Some(particles) = particles { + builder = builder.with(particles) + } if let Some(gravity) = gravity { builder = builder.with(gravity) } @@ -113,6 +117,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { flicker: 1.0, animated: true, }) + .with(ParticleEmitter { mode: 0 }) .with(WaypointArea::default()) .build(); } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 412d9a536a..9761e1d869 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -61,9 +61,10 @@ impl Server { dir, body, light, + particles, projectile, gravity, - } => handle_shoot(self, entity, dir, body, light, projectile, gravity), + } => handle_shoot(self, entity, dir, body, light, particles, projectile, gravity), ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change), ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index adcc76bd30..0fe4af0a3d 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,8 +1,9 @@ use super::SysTimer; use common::{ comp::{ - Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter, - Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, + Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, + Loadout, Mass, MountState, Mounting, Ori, ParticleEmitter, Player, Pos, Scale, Stats, + Sticky, Vel, }, msg::EcsCompPacket, sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, Uid, UpdateTracker, WorldSyncExt}, @@ -44,6 +45,7 @@ pub struct TrackedComps<'a> { pub energy: ReadStorage<'a, Energy>, pub can_build: ReadStorage<'a, CanBuild>, pub light_emitter: ReadStorage<'a, LightEmitter>, + pub particle_emitter: ReadStorage<'a, ParticleEmitter>, pub item: ReadStorage<'a, Item>, pub scale: ReadStorage<'a, Scale>, pub mounting: ReadStorage<'a, Mounting>, @@ -92,6 +94,10 @@ impl<'a> TrackedComps<'a> { .get(entity) .copied() .map(|c| comps.push(c.into())); + self.particle_emitter + .get(entity) + .copied() + .map(|c| comps.push(c.into())); self.item.get(entity).cloned().map(|c| comps.push(c.into())); self.scale .get(entity) @@ -147,6 +153,7 @@ pub struct ReadTrackers<'a> { pub energy: ReadExpect<'a, UpdateTracker>, pub can_build: ReadExpect<'a, UpdateTracker>, pub light_emitter: ReadExpect<'a, UpdateTracker>, + pub particle_emitter: ReadExpect<'a, UpdateTracker>, pub item: ReadExpect<'a, UpdateTracker>, pub scale: ReadExpect<'a, UpdateTracker>, pub mounting: ReadExpect<'a, UpdateTracker>, @@ -180,6 +187,12 @@ impl<'a> ReadTrackers<'a> { &comps.light_emitter, filter, ) + .with_component( + &comps.uid, + &*self.particle_emitter, + &comps.particle_emitter, + filter, + ) .with_component(&comps.uid, &*self.item, &comps.item, filter) .with_component(&comps.uid, &*self.scale, &comps.scale, filter) .with_component(&comps.uid, &*self.mounting, &comps.mounting, filter) @@ -210,6 +223,7 @@ pub struct WriteTrackers<'a> { energy: WriteExpect<'a, UpdateTracker>, can_build: WriteExpect<'a, UpdateTracker>, light_emitter: WriteExpect<'a, UpdateTracker>, + particle_emitter: WriteExpect<'a, UpdateTracker>, item: WriteExpect<'a, UpdateTracker>, scale: WriteExpect<'a, UpdateTracker>, mounting: WriteExpect<'a, UpdateTracker>, @@ -232,6 +246,9 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.energy.record_changes(&comps.energy); trackers.can_build.record_changes(&comps.can_build); trackers.light_emitter.record_changes(&comps.light_emitter); + trackers + .particle_emitter + .record_changes(&comps.particle_emitter); trackers.item.record_changes(&comps.item); trackers.scale.record_changes(&comps.scale); trackers.mounting.record_changes(&comps.mounting); @@ -287,6 +304,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); + world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); From 7e35617f591fbb014694f81445698150b2496647 Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 30 Jun 2020 22:29:35 +0800 Subject: [PATCH 02/28] Add particle pipeline --- assets/voxygen/shaders/include/globals.glsl | 1 + assets/voxygen/shaders/particle-frag.glsl | 38 +++++++++ assets/voxygen/shaders/particle-vert.glsl | 62 ++++++++++++++ voxygen/src/hud/mod.rs | 1 + voxygen/src/hud/settings_window.rs | 3 + voxygen/src/render/pipelines/mod.rs | 5 ++ voxygen/src/render/pipelines/particle.rs | 90 +++++++++++++++++++++ voxygen/src/render/renderer.rs | 52 +++++++++++- voxygen/src/scene/mod.rs | 2 + voxygen/src/scene/simple.rs | 1 + voxygen/src/session.rs | 9 ++- voxygen/src/settings.rs | 2 + 12 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 assets/voxygen/shaders/particle-frag.glsl create mode 100644 assets/voxygen/shaders/particle-vert.glsl create mode 100644 voxygen/src/render/pipelines/particle.rs diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index 895492a23a..7436e5c036 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -17,6 +17,7 @@ uniform u_globals { // 1 - ThirdPerson uint cam_mode; float sprite_render_distance; + float particle_render_distance; }; // Specifies the pattern used in the player dithering diff --git a/assets/voxygen/shaders/particle-frag.glsl b/assets/voxygen/shaders/particle-frag.glsl new file mode 100644 index 0000000000..3e2ef6b9e6 --- /dev/null +++ b/assets/voxygen/shaders/particle-frag.glsl @@ -0,0 +1,38 @@ +#version 330 core + +#include + +in vec3 f_pos; +flat in vec3 f_norm; +in vec3 f_col; +in float f_ao; +in float f_light; + +out vec4 tgt_color; + +#include +#include + +const float FADE_DIST = 32.0; + +void main() { + vec3 light, diffuse_light, ambient_light; + get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0); + float point_shadow = shadow_at(f_pos, f_norm); + diffuse_light *= f_light * point_shadow; + ambient_light *= f_light, point_shadow; + vec3 point_light = light_at(f_pos, f_norm); + light += point_light; + diffuse_light += point_light; + float ao = pow(f_ao, 0.5) * 0.85 + 0.15; + ambient_light *= ao; + diffuse_light *= ao; + vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light); + + float fog_level = fog(f_pos.xyz, focus_pos.xyz, medium.x); + vec4 clouds; + vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, cam_pos.xyz, f_pos, 0.5, true, clouds); + vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a); + + tgt_color = vec4(color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (particle_render_distance - FADE_DIST)) / FADE_DIST, 0, 1)); +} diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl new file mode 100644 index 0000000000..86f00c3aad --- /dev/null +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -0,0 +1,62 @@ +#version 330 core + +#include +#include + +in vec3 v_pos; +in uint v_col; +in uint v_norm_ao; +in vec4 inst_mat0; +in vec4 inst_mat1; +in vec4 inst_mat2; +in vec4 inst_mat3; +in vec3 inst_col; +in float inst_wind_sway; + +out vec3 f_pos; +flat out vec3 f_norm; +out vec3 f_col; +out float f_ao; +out float f_light; + +const float SCALE = 1.0 / 11.0; + +void main() { + mat4 inst_mat; + inst_mat[0] = inst_mat0; + inst_mat[1] = inst_mat1; + inst_mat[2] = inst_mat2; + inst_mat[3] = inst_mat3; + + vec3 particle_pos = (inst_mat * vec4(0, 0, 0, 1)).xyz; + + f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz; + f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); + + // Wind waving + f_pos += inst_wind_sway * vec3( + sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), + sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), + 0.0 + ) * pow(abs(v_pos.z) * SCALE, 1.3) * 0.2; + + // First 3 normals are negative, next 3 are positive + vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); + f_norm = (inst_mat * vec4(normals[(v_norm_ao >> 0) & 0x7u], 0)).xyz; + + vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; + f_col = srgb_to_linear(col) * srgb_to_linear(inst_col); + f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; + + // Select glowing + if (select_pos.w > 0 && select_pos.xyz == floor(particle_pos)) { + f_col *= 4.0; + } + + f_light = 1.0; + + gl_Position = + all_mat * + vec4(f_pos, 1); + gl_Position.z = -1000.0 / (gl_Position.z + 10000.0); +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 33d400735e..7f8242af23 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -266,6 +266,7 @@ pub enum Event { ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustSpriteRenderDistance(u32), + AdjustParticleRenderDistance(u32), AdjustFigureLoDRenderDistance(u32), AdjustMusicVolume(f32), AdjustSfxVolume(f32), diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 142b4d0984..c0b8e42fff 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -1845,6 +1845,7 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.sprite_dist_text, ui); + Text::new(&format!( "{}", self.global_state.settings.graphics.sprite_render_distance @@ -1898,6 +1899,8 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.figure_dist_value, ui); + // TODO: Particle View Distance slider. + // AaMode Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode")) .down_from(state.ids.gamma_slider, 8.0) diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 81ef76456f..a3e978a70c 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -3,6 +3,7 @@ pub mod fluid; pub mod postprocess; pub mod skybox; pub mod sprite; +pub mod particle; pub mod terrain; pub mod ui; @@ -29,6 +30,7 @@ gfx_defines! { gamma: [f32; 4] = "gamma", cam_mode: u32 = "cam_mode", sprite_render_distance: f32 = "sprite_render_distance", + particle_render_distance: f32 = "particle_render_distance", } constant Light { @@ -61,6 +63,7 @@ impl Globals { gamma: f32, cam_mode: CameraMode, sprite_render_distance: f32, + particle_render_distance: f32, ) -> Self { Self { view_mat: view_mat.into_col_arrays(), @@ -81,6 +84,7 @@ impl Globals { gamma: [gamma; 4], cam_mode: cam_mode as u32, sprite_render_distance, + particle_render_distance, } } } @@ -103,6 +107,7 @@ impl Default for Globals { 1.0, CameraMode::ThirdPerson, 250.0, + 250.0, ) } } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs new file mode 100644 index 0000000000..89fde89543 --- /dev/null +++ b/voxygen/src/render/pipelines/particle.rs @@ -0,0 +1,90 @@ +use super::{ + super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, + Globals, Light, Shadow, +}; +use gfx::{ + self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, + gfx_vertex_struct_meta, + state::{ColorMask, Comparison, Stencil, StencilOp}, +}; +use vek::*; + +gfx_defines! { + vertex Vertex { + pos: [f32; 3] = "v_pos", + // ____BBBBBBBBGGGGGGGGRRRRRRRR + col: u32 = "v_col", + // ...AANNN + // A = AO + // N = Normal + norm_ao: u32 = "v_norm_ao", + } + + vertex Instance { + inst_mat0: [f32; 4] = "inst_mat0", + inst_mat1: [f32; 4] = "inst_mat1", + inst_mat2: [f32; 4] = "inst_mat2", + inst_mat3: [f32; 4] = "inst_mat3", + inst_col: [f32; 3] = "inst_col", + inst_wind_sway: f32 = "inst_wind_sway", + } + + pipeline pipe { + vbuf: gfx::VertexBuffer = (), + ibuf: gfx::InstanceBuffer = (), + + globals: gfx::ConstantBuffer = "u_globals", + lights: gfx::ConstantBuffer = "u_lights", + shadows: gfx::ConstantBuffer = "u_shadows", + + noise: gfx::TextureSampler = "t_noise", + + tgt_color: gfx::BlendTarget = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA), + tgt_depth_stencil: gfx::DepthStencilTarget = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))), + } +} + +impl Vertex { + #[allow(clippy::collapsible_if)] + pub fn new(pos: Vec3, norm: Vec3, col: Rgb, ao: f32) -> Self { + let norm_bits = if norm.x != 0.0 { + if norm.x < 0.0 { 0 } else { 1 } + } else if norm.y != 0.0 { + if norm.y < 0.0 { 2 } else { 3 } + } else { + if norm.z < 0.0 { 4 } else { 5 } + }; + + Self { + pos: pos.into_array(), + col: col + .map2(Rgb::new(0, 8, 16), |e, shift| ((e * 255.0) as u32) << shift) + .reduce_bitor(), + norm_ao: norm_bits | (((ao * 3.9999) as u32) << 3), + } + } +} + +impl Instance { + pub fn new(mat: Mat4, col: Rgb, wind_sway: f32) -> Self { + let mat_arr = mat.into_col_arrays(); + Self { + inst_mat0: mat_arr[0], + inst_mat1: mat_arr[1], + inst_mat2: mat_arr[2], + inst_mat3: mat_arr[3], + inst_col: col.into_array(), + inst_wind_sway: wind_sway, + } + } +} + +impl Default for Instance { + fn default() -> Self { Self::new(Mat4::identity(), Rgb::broadcast(1.0), 0.0) } +} + +pub struct ParticlePipeline; + +impl Pipeline for ParticlePipeline { + type Vertex = Vertex; +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index a6e37ff192..80bdb65ba5 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -4,7 +4,7 @@ use super::{ instances::Instances, mesh::Mesh, model::{DynamicModel, Model}, - pipelines::{figure, fluid, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow}, + pipelines::{figure, fluid, postprocess, skybox, sprite, particle, terrain, ui, Globals, Light, Shadow}, texture::Texture, AaMode, CloudMode, FluidMode, Pipeline, RenderError, }; @@ -70,6 +70,7 @@ pub struct Renderer { terrain_pipeline: GfxPipeline>, fluid_pipeline: GfxPipeline>, sprite_pipeline: GfxPipeline>, + particle_pipeline: GfxPipeline>, ui_pipeline: GfxPipeline>, postprocess_pipeline: GfxPipeline>, player_shadow_pipeline: GfxPipeline>, @@ -103,6 +104,7 @@ impl Renderer { terrain_pipeline, fluid_pipeline, sprite_pipeline, + particle_pipeline, ui_pipeline, postprocess_pipeline, player_shadow_pipeline, @@ -146,6 +148,7 @@ impl Renderer { terrain_pipeline, fluid_pipeline, sprite_pipeline, + particle_pipeline, ui_pipeline, postprocess_pipeline, player_shadow_pipeline, @@ -341,6 +344,7 @@ impl Renderer { terrain_pipeline, fluid_pipeline, sprite_pipeline, + particle_pipeline, ui_pipeline, postprocess_pipeline, player_shadow_pipeline, @@ -350,6 +354,7 @@ impl Renderer { self.terrain_pipeline = terrain_pipeline; self.fluid_pipeline = fluid_pipeline; self.sprite_pipeline = sprite_pipeline; + self.particle_pipeline = particle_pipeline; self.ui_pipeline = ui_pipeline; self.postprocess_pipeline = postprocess_pipeline; self.player_shadow_pipeline = player_shadow_pipeline; @@ -711,6 +716,37 @@ impl Renderer { ); } + /// Queue the rendering of the provided particle in the upcoming frame. + pub fn render_particles( + &mut self, + model: &Model, + globals: &Consts, + instances: &Instances, + lights: &Consts, + shadows: &Consts, + ) { + self.encoder.draw( + &gfx::Slice { + start: model.vertex_range().start, + end: model.vertex_range().end, + base_vertex: 0, + instances: Some((instances.count() as u32, 0)), + buffer: gfx::IndexBuffer::Auto, + }, + &self.particle_pipeline.pso, + &particle::pipe::Data { + vbuf: model.vbuf.clone(), + ibuf: instances.ibuf.clone(), + globals: globals.buf.clone(), + lights: lights.buf.clone(), + shadows: shadows.buf.clone(), + noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), + tgt_color: self.tgt_color_view.clone(), + tgt_depth_stencil: (self.tgt_depth_stencil_view.clone(), (1, 1)), + }, + ); + } + /// Queue the rendering of the provided UI element in the upcoming frame. pub fn render_ui_element( &mut self, @@ -793,6 +829,7 @@ fn create_pipelines( GfxPipeline>, GfxPipeline>, GfxPipeline>, + GfxPipeline>, GfxPipeline>, GfxPipeline>, GfxPipeline>, @@ -914,6 +951,18 @@ fn create_pipelines( gfx::state::CullFace::Back, )?; + // Construct a pipeline for rendering particles + let particle_pipeline = create_pipeline( + factory, + particle::pipe::new(), + &assets::load_watched::("voxygen.shaders.particle-vert", shader_reload_indicator) + .unwrap(), + &assets::load_watched::("voxygen.shaders.particle-frag", shader_reload_indicator) + .unwrap(), + &include_ctx, + gfx::state::CullFace::Back, + )?; + // Construct a pipeline for rendering UI elements let ui_pipeline = create_pipeline( factory, @@ -975,6 +1024,7 @@ fn create_pipelines( terrain_pipeline, fluid_pipeline, sprite_pipeline, + particle_pipeline, ui_pipeline, postprocess_pipeline, player_shadow_pipeline, diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 4c18a92207..19b6144760 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -78,6 +78,7 @@ pub struct SceneData<'a> { pub gamma: f32, pub mouse_smoothing: bool, pub sprite_render_distance: f32, + pub particle_render_distance: f32, pub figure_lod_render_distance: f32, pub is_aiming: bool, } @@ -369,6 +370,7 @@ impl Scene { scene_data.gamma, self.camera.get_mode(), scene_data.sprite_render_distance as f32 - 20.0, + scene_data.particle_render_distance as f32 - 20.0, )]) .expect("Failed to update global constants"); diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index c89ebc6511..fb620aa0f1 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -190,6 +190,7 @@ impl Scene { scene_data.gamma, self.camera.get_mode(), 250.0, + 250.0, )]) { error!(?e, "Renderer failed to update"); } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 3c5f266164..fb44b952a4 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -785,6 +785,11 @@ impl PlayState for SessionState { sprite_render_distance; global_state.settings.save_to_file_warn(); }, + HudEvent::AdjustParticleRenderDistance(particle_render_distance) => { + global_state.settings.graphics.particle_render_distance = + particle_render_distance; + global_state.settings.save_to_file_warn(); + }, HudEvent::AdjustFigureLoDRenderDistance(figure_lod_render_distance) => { global_state.settings.graphics.figure_lod_render_distance = figure_lod_render_distance; @@ -997,7 +1002,9 @@ impl PlayState for SessionState { gamma: global_state.settings.graphics.gamma, mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable, sprite_render_distance: global_state.settings.graphics.sprite_render_distance - as f32, + as f32, + particle_render_distance: global_state.settings.graphics.particle_render_distance + as f32, figure_lod_render_distance: global_state .settings .graphics diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 441108bdb7..ef1184c8ee 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -603,6 +603,7 @@ impl Default for Log { pub struct GraphicsSettings { pub view_distance: u32, pub sprite_render_distance: u32, + pub particle_render_distance: u32, pub figure_lod_render_distance: u32, pub max_fps: u32, pub fov: u16, @@ -619,6 +620,7 @@ impl Default for GraphicsSettings { Self { view_distance: 10, sprite_render_distance: 150, + particle_render_distance: 150, figure_lod_render_distance: 250, max_fps: 60, fov: 50, From 39b676cd8f436555ec79424cea5f5f4637cf6c53 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sun, 5 Jul 2020 20:10:58 +0800 Subject: [PATCH 03/28] Add ParticleMgr --- assets/voxygen/shaders/particle-vert.glsl | 15 +- common/src/comp/inventory/item/tool.rs | 8 +- common/src/comp/mod.rs | 2 +- common/src/comp/visual.rs | 27 +--- server/src/cmd.rs | 5 +- server/src/events/entity_creation.rs | 5 +- voxygen/src/mesh/segment.rs | 70 ++++++++- voxygen/src/render/mod.rs | 1 + voxygen/src/render/pipelines/particle.rs | 29 +++- voxygen/src/scene/mod.rs | 25 ++++ voxygen/src/scene/particle.rs | 170 ++++++++++++++++++++++ 11 files changed, 322 insertions(+), 35 deletions(-) create mode 100644 voxygen/src/scene/particle.rs diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 86f00c3aad..a50cc9a6a9 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -11,6 +11,8 @@ in vec4 inst_mat1; in vec4 inst_mat2; in vec4 inst_mat3; in vec3 inst_col; +in vec3 inst_vel; +in vec4 inst_tick; in float inst_wind_sway; out vec3 f_pos; @@ -34,11 +36,14 @@ void main() { f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); // Wind waving - f_pos += inst_wind_sway * vec3( - sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), - sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), - 0.0 - ) * pow(abs(v_pos.z) * SCALE, 1.3) * 0.2; + //f_pos += inst_wind_sway * vec3( + // sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), + // sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), + // 0.0 + //) * pow(abs(v_pos.z) * SCALE, 1.3) * 0.2; + + float elapsed = (tick.x - inst_tick.x) / 100000.0; + f_pos += (inst_vel * elapsed); // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index d7b4524243..14a1c37d41 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,8 +2,8 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, Body, CharacterAbility, Gravity, HealthChange, HealthSource, - LightEmitter, Projectile, ParticleEmitter, + body::object, projectile, visual::ParticleEmitterMode, Body, CharacterAbility, Gravity, + HealthChange, HealthSource, LightEmitter, ParticleEmitter, Projectile, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -277,7 +277,7 @@ impl Tool { ..Default::default() }), projectile_particles: Some(ParticleEmitter { - mode: 0, + mode: ParticleEmitterMode::Sprinkler, }), projectile_gravity: None, }, @@ -304,7 +304,7 @@ impl Tool { ..Default::default() }), projectile_particles: Some(ParticleEmitter { - mode: 0, + mode: ParticleEmitterMode::Sprinkler, }), projectile_gravity: None, }, diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index a17da95189..4126c1a9df 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -18,7 +18,7 @@ mod player; pub mod projectile; pub mod skills; mod stats; -mod visual; +pub mod visual; // Reexports pub use ability::{CharacterAbility, CharacterAbilityType, ItemConfig, Loadout}; diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index b1d0f29cae..215ce17959 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -46,37 +46,24 @@ impl Default for LightAnimation { impl Component for LightAnimation { type Storage = FlaggedStorage>; } - +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ParticleEmitterMode { + Sprinkler, +} #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ParticleEmitter { - - /// Mode 1: sprinkler (inital_velocity, lifespan) - /// Mode 2: smoke (initial_position, boyancy_const, wind, lifespan) - pub mode: u8, // enum? - - // pub vertices: Vec, - // pub texture: RasterFooBar, - - // // mode 1 -- sprinkler. - // pub initial_position: [i8; 3], - // pub initial_velocity: [i8; 3], - // pub lifespan: u32, // in ticks? - - // // mode 2 -- smoke - // pub initial_position: [i8; 3], - // pub boyancy_const: [i8; 3], - // pub wind_sway: [i8; 3], + pub mode: ParticleEmitterMode, } impl Default for ParticleEmitter { fn default() -> Self { Self { - mode: 0, + mode: ParticleEmitterMode::Sprinkler, } } } impl Component for ParticleEmitter { type Storage = FlaggedStorage>; -} \ No newline at end of file +} diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 14c7b39338..090e587a40 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -25,6 +25,7 @@ use world::util::Sampler; use scan_fmt::{scan_fmt, scan_fmt_some}; use tracing::error; +use comp::visual::ParticleEmitterMode; pub trait ChatCommandExt { fn execute(&self, server: &mut Server, entity: EcsEntity, args: String); @@ -910,7 +911,9 @@ fn handle_light( .create_entity_synced() .with(pos) .with(comp::ForceUpdate) - .with(comp::ParticleEmitter { mode: 0 }) + .with(comp::ParticleEmitter { + mode: ParticleEmitterMode::Sprinkler, + }) .with(light_emitter); if let Some(light_offset) = light_offset_opt { builder.with(light_offset).build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index d2f3b9d774..fd25760dce 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -6,6 +6,7 @@ use common::{ }, util::Dir, }; +use comp::visual::ParticleEmitterMode; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; @@ -117,7 +118,9 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { flicker: 1.0, animated: true, }) - .with(ParticleEmitter { mode: 0 }) + .with(ParticleEmitter { + mode: ParticleEmitterMode::Sprinkler, + }) .with(WaypointArea::default()) .build(); } diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index c69d348b45..2909c51f35 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -1,6 +1,6 @@ use crate::{ mesh::{vol, Meshable}, - render::{self, FigurePipeline, Mesh, SpritePipeline}, + render::{self, FigurePipeline, Mesh, ParticlePipeline, SpritePipeline}, }; use common::{ figure::Cell, @@ -11,6 +11,7 @@ use vek::*; type FigureVertex = ::Vertex; type SpriteVertex = ::Vertex; +type ParticleVertex = ::Vertex; impl<'a, V: 'a> Meshable<'a, FigurePipeline, FigurePipeline> for V where @@ -147,6 +148,73 @@ where } } +impl<'a, V: 'a> Meshable<'a, ParticlePipeline, ParticlePipeline> for V +where + V: BaseVol + ReadVol + SizedVol, + /* TODO: Use VolIterator instead of manually iterating + * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, + * &'a V: BaseVol, */ +{ + type Pipeline = ParticlePipeline; + type Supplement = (Vec3, Vec3); + type TranslucentPipeline = ParticlePipeline; + + #[allow(clippy::needless_range_loop)] // TODO: Pending review in #587 + #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 + fn generate_mesh( + &'a self, + (offs, scale): Self::Supplement, + ) -> (Mesh, Mesh) { + let mut mesh = Mesh::new(); + + let vol_iter = (self.lower_bound().x..self.upper_bound().x) + .map(|i| { + (self.lower_bound().y..self.upper_bound().y).map(move |j| { + (self.lower_bound().z..self.upper_bound().z).map(move |k| Vec3::new(i, j, k)) + }) + }) + .flatten() + .flatten() + .map(|pos| (pos, self.get(pos).map(|x| *x).unwrap_or(Vox::empty()))); + + for (pos, vox) in vol_iter { + if let Some(col) = vox.get_color() { + vol::push_vox_verts( + &mut mesh, + faces_to_make(self, pos, true, |vox| vox.is_empty()), + offs + pos.map(|e| e as f32), + &[[[Rgba::from_opaque(col); 3]; 3]; 3], + |origin, norm, col, light, ao| { + ParticleVertex::new( + origin * scale, + norm, + linear_to_srgb(srgb_to_linear(col) * light), + ao, + ) + }, + &{ + let mut ls = [[[None; 3]; 3]; 3]; + for x in 0..3 { + for y in 0..3 { + for z in 0..3 { + ls[z][y][x] = self + .get(pos + Vec3::new(x as i32, y as i32, z as i32) - 1) + .map(|v| v.is_empty()) + .unwrap_or(true) + .then_some(1.0); + } + } + } + ls + }, + ); + } + } + + (mesh, Mesh::new()) + } +} + /// Use the 6 voxels/blocks surrounding the one at the specified position /// to detemine which faces should be drawn fn faces_to_make( diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 9bf74d5aa9..408251d9ad 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -22,6 +22,7 @@ pub use self::{ create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline, }, skybox::{create_mesh as create_skybox_mesh, Locals as SkyboxLocals, SkyboxPipeline}, + particle::{Instance as ParticleInstance, ParticlePipeline}, sprite::{Instance as SpriteInstance, SpritePipeline}, terrain::{Locals as TerrainLocals, TerrainPipeline}, ui::{ diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 89fde89543..1352549054 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -2,6 +2,7 @@ use super::{ super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; +use common::comp::visual::ParticleEmitterMode; use gfx::{ self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, @@ -26,7 +27,10 @@ gfx_defines! { inst_mat2: [f32; 4] = "inst_mat2", inst_mat3: [f32; 4] = "inst_mat3", inst_col: [f32; 3] = "inst_col", + inst_vel: [f32; 3] = "inst_vel", + inst_tick: [f32; 4] = "inst_tick", inst_wind_sway: f32 = "inst_wind_sway", + mode: u8 = "mode", } pipeline pipe { @@ -66,7 +70,14 @@ impl Vertex { } impl Instance { - pub fn new(mat: Mat4, col: Rgb, wind_sway: f32) -> Self { + pub fn new( + mat: Mat4, + col: Rgb, + vel: Vec3, + tick: u64, + wind_sway: f32, + mode: ParticleEmitterMode, + ) -> Self { let mat_arr = mat.into_col_arrays(); Self { inst_mat0: mat_arr[0], @@ -74,13 +85,27 @@ impl Instance { inst_mat2: mat_arr[2], inst_mat3: mat_arr[3], inst_col: col.into_array(), + inst_vel: vel.into_array(), + inst_tick: [tick as f32; 4], + inst_wind_sway: wind_sway, + + mode: mode as u8, } } } impl Default for Instance { - fn default() -> Self { Self::new(Mat4::identity(), Rgb::broadcast(1.0), 0.0) } + fn default() -> Self { + Self::new( + Mat4::identity(), + Rgb::broadcast(1.0), + Vec3::zero(), + 0, + 0.0, + ParticleEmitterMode::Sprinkler, + ) + } } pub struct ParticlePipeline; diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 19b6144760..9b0f6c3fe5 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -1,11 +1,13 @@ pub mod camera; pub mod figure; +pub mod particle; pub mod simple; pub mod terrain; use self::{ camera::{Camera, CameraMode}, figure::FigureMgr, + particle::ParticleMgr, terrain::Terrain, }; use crate::{ @@ -62,6 +64,7 @@ pub struct Scene { loaded_distance: f32, select_pos: Option>, + particle_mgr: ParticleMgr, figure_mgr: FigureMgr, sfx_mgr: SfxMgr, music_mgr: MusicMgr, @@ -113,6 +116,7 @@ impl Scene { loaded_distance: 0.0, select_pos: None, + particle_mgr: ParticleMgr::new(renderer), figure_mgr: FigureMgr::new(), sfx_mgr: SfxMgr::new(), music_mgr: MusicMgr::new(), @@ -128,6 +132,9 @@ impl Scene { /// Get a reference to the scene's terrain. pub fn terrain(&self) -> &Terrain { &self.terrain } + /// 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 figure manager. pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr } @@ -390,6 +397,16 @@ impl Scene { // Remove unused figures. self.figure_mgr.clean(scene_data.tick); + // Maintain the particles. + self.particle_mgr.maintain( + renderer, + &scene_data, + self.camera.get_focus_pos(), + self.loaded_distance, + view_mat, + proj_mat, + ); + // Maintain audio self.sfx_mgr .maintain(audio, scene_data.state, scene_data.player_entity); @@ -425,6 +442,14 @@ impl Scene { scene_data.figure_lod_render_distance, ); + self.particle_mgr.render( + renderer, + &self.globals, + &self.lights, + &self.shadows, + self.camera.get_focus_pos(), + ); + // Render the skybox. renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs new file mode 100644 index 0000000000..c0cd9cff1d --- /dev/null +++ b/voxygen/src/scene/particle.rs @@ -0,0 +1,170 @@ +use super::SceneData; +use crate::{ + mesh::Meshable, + render::{ + mesh::Quad, Consts, Globals, Instances, Light, Mesh, Model, ParticleInstance, + ParticlePipeline, Renderer, Shadow, + }, +}; +use common::{ + assets, + comp::{visual::ParticleEmitterMode, Ori, ParticleEmitter, Pos, Vel}, + figure::Segment, + vol::BaseVol, +}; +use dot_vox::DotVoxData; +use hashbrown::HashMap; +use rand::Rng; +use specs::{Entity as EcsEntity, Join, WorldExt}; +use tracing::{debug, error, warn}; +use vek::{Mat4, Rgb, Vec3}; +struct Particles { + // this is probably nieve, + // could cache and re-use between particles, + // should be a cache key? + // model: Model, + instances: Instances, +} + +pub struct ParticleMgr { + entity_particles: HashMap>, + model_cache: Model, +} + +impl ParticleMgr { + pub fn new(renderer: &mut Renderer) -> Self { + let offset = Vec3::zero(); + let lod_scale = Vec3::one(); + + // TODO: from cache + let vox = assets::load_expect::("voxygen.voxel.not_found"); + + // TODO: from cache + let mesh = &Meshable::::generate_mesh( + &Segment::from(vox.as_ref()), + (offset * lod_scale, Vec3::one() / lod_scale), + ) + .0; + + // TODO: from cache + let model = renderer + .create_model(mesh) + .expect("Failed to create particle model"); + + Self { + entity_particles: HashMap::new(), + model_cache: model, + } + } + + pub fn maintain( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + focus_pos: Vec3, + loaded_distance: f32, + view_mat: Mat4, + proj_mat: Mat4, + ) { + let state = scene_data.state; + let ecs = state.ecs(); + + let tick = scene_data.tick; + + // remove dead particles + + // remove dead entities, with dead particles + self.entity_particles.retain(|k, v| ecs.is_alive(*k)); + + // add living entities particles + for (_i, (entity, particle_emitter, pos, ori, vel)) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), + ) + .join() + .enumerate() + { + let entry = self + .entity_particles + .entry(entity) + .or_insert_with(|| into_particles(renderer, tick, particle_emitter, pos, ori, vel)); + } + } + + pub fn render( + &self, + renderer: &mut Renderer, + globals: &Consts, + lights: &Consts, + shadows: &Consts, + focus_pos: Vec3, + ) { + for particles in self.entity_particles.values() { + for particle in particles { + renderer.render_particles( + &self.model_cache, + globals, + &particle.instances, + lights, + shadows, + ); + } + } + } +} + +fn into_particles( + renderer: &mut Renderer, + tick: u64, + particle_emitter: &ParticleEmitter, + pos: &Pos, + ori: Option<&Ori>, + vel: Option<&Vel>, +) -> Vec { + let mut rng = rand::thread_rng(); + + let desired_instance_count = 100; + + // let ori_default = Ori::default(); + let vel_default = Vel::default(); + + // let ori2 = ori.unwrap_or_else(|| &ori_default); + let vel2 = vel.unwrap_or_else(|| &vel_default).0; + let mut instances_vec = Vec::new(); + + for x in 0..desired_instance_count { + // how does ParticleEmitterMode fit in here? + // can we have a ParticleInstance type per ParticleEmitterMode? + // can we mix and match instance types in the same instances_vec? + instances_vec.push(ParticleInstance::new( + Mat4::identity() + // initial rotation + .rotated_x(rng.gen_range(0.0, 3.14 * 2.0)) + .rotated_y(rng.gen_range(0.0, 3.14 * 2.0)) + .rotated_z(rng.gen_range(0.0, 3.14 * 2.0)) + // inition position + .translated_3d( + pos.0 + + Vec3::new( + rng.gen_range(-5.0, 5.0), + rng.gen_range(-5.0, 5.0), + rng.gen_range(0.0, 10.0), + ), + ), + Rgb::broadcast(1.0), // instance color + vel2 + Vec3::broadcast(rng.gen_range(-5.0, 5.0)), + tick, + rng.gen_range(0.0, 20.0), // wind sway + ParticleEmitterMode::Sprinkler, // particle_emitter.mode */ + )); + } + + let instances = renderer + .create_instances(&instances_vec) + .expect("Failed to upload particle instances to the GPU!"); + + vec![Particles { instances }] +} From da5f4828a5237c88df2a1f7585e9f2887eceef6e Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 11 Jul 2020 18:37:19 +0800 Subject: [PATCH 04/28] Add particle lifespan --- common/src/comp/inventory/item/tool.rs | 14 ++- common/src/comp/visual.rs | 22 +++- server/src/cmd.rs | 6 +- server/src/events/entity_creation.rs | 4 +- voxygen/src/scene/particle.rs | 156 ++++++++++++++++--------- 5 files changed, 133 insertions(+), 69 deletions(-) diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 14a1c37d41..fc594bd7df 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -278,6 +278,16 @@ impl Tool { }), projectile_particles: Some(ParticleEmitter { mode: ParticleEmitterMode::Sprinkler, + // model_key: "voxygen.voxel.not_found", + count: (2, 3), + frequency: Duration::from_millis(50), + initial_lifespan: Duration::from_millis(500), + initial_offset: (vek::Vec3::broadcast(-1.0), vek::Vec3::broadcast(1.0)), + initial_orientation: ( + vek::Vec3::broadcast(-1.0), + vek::Vec3::broadcast(1.0), + ), + initial_scale: (0.1, 0.3), }), projectile_gravity: None, }, @@ -303,9 +313,7 @@ impl Tool { col: (1.0, 0.75, 0.11).into(), ..Default::default() }), - projectile_particles: Some(ParticleEmitter { - mode: ParticleEmitterMode::Sprinkler, - }), + projectile_particles: Some(ParticleEmitter::default()), projectile_gravity: None, }, ], diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index 215ce17959..39f3f07054 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; +use std::time::Duration; use vek::*; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -54,16 +55,35 @@ pub enum ParticleEmitterMode { #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ParticleEmitter { pub mode: ParticleEmitterMode, + + // spawn X particles per Y, that live for Z + // pub model_ref: &str, // can we have some kind of stack based key like a u8? + pub count: (u8, u8), + pub frequency: Duration, + + // relative to Pos, Ori components? + // can these be functions that returns a Vec3? + pub initial_lifespan: Duration, + pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, + pub initial_scale: (f32, f32), // fn() -> Vec3, + pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, } impl Default for ParticleEmitter { fn default() -> Self { Self { mode: ParticleEmitterMode::Sprinkler, + // model_key: "voxygen.voxel.not_found", + count: (2, 5), + frequency: Duration::from_millis(500), + initial_lifespan: Duration::from_secs(2), + initial_offset: (vek::Vec3::broadcast(-5.0), vek::Vec3::broadcast(5.0)), + initial_orientation: (vek::Vec3::broadcast(-5.0), vek::Vec3::broadcast(5.0)), + initial_scale: (0.1, 2.0), } } } impl Component for ParticleEmitter { - type Storage = FlaggedStorage>; + type Storage = FlaggedStorage>; } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 090e587a40..c6942b4090 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -23,9 +23,9 @@ use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use vek::*; use world::util::Sampler; +use comp::visual::ParticleEmitterMode; use scan_fmt::{scan_fmt, scan_fmt_some}; use tracing::error; -use comp::visual::ParticleEmitterMode; pub trait ChatCommandExt { fn execute(&self, server: &mut Server, entity: EcsEntity, args: String); @@ -911,9 +911,7 @@ fn handle_light( .create_entity_synced() .with(pos) .with(comp::ForceUpdate) - .with(comp::ParticleEmitter { - mode: ParticleEmitterMode::Sprinkler, - }) + .with(comp::ParticleEmitter::default()) .with(light_emitter); if let Some(light_offset) = light_offset_opt { builder.with(light_offset).build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index fd25760dce..83ff38a94c 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -118,9 +118,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { flicker: 1.0, animated: true, }) - .with(ParticleEmitter { - mode: ParticleEmitterMode::Sprinkler, - }) + .with(ParticleEmitter::default()) .with(WaypointArea::default()) .build(); } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index c0cd9cff1d..75b41cd8d0 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -16,44 +16,71 @@ use dot_vox::DotVoxData; use hashbrown::HashMap; use rand::Rng; use specs::{Entity as EcsEntity, Join, WorldExt}; +use std::time::{Duration, Instant}; use tracing::{debug, error, warn}; use vek::{Mat4, Rgb, Vec3}; + struct Particles { // this is probably nieve, // could cache and re-use between particles, // should be a cache key? // model: Model, + // created_at: Instant, + // lifespan: Duration, + alive_until: Instant, // created_at + lifespan + instances: Instances, } -pub struct ParticleMgr { - entity_particles: HashMap>, - model_cache: Model, +struct Emitter { + last_emit: Instant, } +pub struct ParticleMgr { + // to keep track of spawn intervals + emitters: HashMap, + + // to keep track of lifespans + particles: Vec, + + model_cache: HashMap<&'static str, Model>, + + beginning_of_time: Instant, +} + +const MODEL_KEY: &str = "voxygen.voxel.not_found"; + impl ParticleMgr { pub fn new(renderer: &mut Renderer) -> Self { - let offset = Vec3::zero(); - let lod_scale = Vec3::one(); + let mut model_cache = HashMap::new(); - // TODO: from cache - let vox = assets::load_expect::("voxygen.voxel.not_found"); + let model = model_cache.entry(MODEL_KEY).or_insert_with(|| { + let offset = Vec3::zero(); + let lod_scale = Vec3::one(); - // TODO: from cache - let mesh = &Meshable::::generate_mesh( - &Segment::from(vox.as_ref()), - (offset * lod_scale, Vec3::one() / lod_scale), - ) - .0; + // TODO: from cache + let vox = assets::load_expect::(MODEL_KEY); - // TODO: from cache - let model = renderer - .create_model(mesh) - .expect("Failed to create particle model"); + // TODO: from cache + let mesh = &Meshable::::generate_mesh( + &Segment::from(vox.as_ref()), + (offset * lod_scale, Vec3::one() / lod_scale), + ) + .0; + + // TODO: from cache + let model = renderer + .create_model(mesh) + .expect("Failed to create particle model"); + + model + }); Self { - entity_particles: HashMap::new(), - model_cache: model, + emitters: HashMap::new(), + particles: Vec::new(), + model_cache, + beginning_of_time: Instant::now(), } } @@ -71,10 +98,14 @@ impl ParticleMgr { let tick = scene_data.tick; - // remove dead particles + let now = Instant::now(); + let beginning_of_time1 = self.beginning_of_time.clone(); - // remove dead entities, with dead particles - self.entity_particles.retain(|k, v| ecs.is_alive(*k)); + // remove dead emitters + self.emitters.retain(|k, _v| ecs.is_alive(*k)); + + // remove dead particles + self.particles.retain(|p| p.alive_until > now); // add living entities particles for (_i, (entity, particle_emitter, pos, ori, vel)) in ( @@ -87,10 +118,25 @@ impl ParticleMgr { .join() .enumerate() { - let entry = self - .entity_particles - .entry(entity) - .or_insert_with(|| into_particles(renderer, tick, particle_emitter, pos, ori, vel)); + let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { + last_emit: beginning_of_time1, // self.beginning_of_time.clone() + }); + + if emitter.last_emit + particle_emitter.frequency < now { + emitter.last_emit = Instant::now(); + + let cpu_insts = + into_particle_instances(particle_emitter, renderer, tick, pos, ori, vel); + + let gpu_insts = renderer + .create_instances(&cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + let entry = self.particles.push(Particles { + alive_until: now + particle_emitter.initial_lifespan, + instances: gpu_insts, + }); + } } } @@ -102,56 +148,54 @@ impl ParticleMgr { shadows: &Consts, focus_pos: Vec3, ) { - for particles in self.entity_particles.values() { - for particle in particles { - renderer.render_particles( - &self.model_cache, - globals, - &particle.instances, - lights, - shadows, - ); - } + for particle in &self.particles { + renderer.render_particles( + &self + .model_cache + .get(MODEL_KEY) + .expect("Expected particle model in cache"), + globals, + &particle.instances, + lights, + shadows, + ); } } } -fn into_particles( +fn into_particle_instances( + particle_emitter: &ParticleEmitter, renderer: &mut Renderer, tick: u64, - particle_emitter: &ParticleEmitter, pos: &Pos, ori: Option<&Ori>, vel: Option<&Vel>, -) -> Vec { +) -> Vec { let mut rng = rand::thread_rng(); - - let desired_instance_count = 100; - - // let ori_default = Ori::default(); let vel_default = Vel::default(); - - // let ori2 = ori.unwrap_or_else(|| &ori_default); let vel2 = vel.unwrap_or_else(|| &vel_default).0; + let mut instances_vec = Vec::new(); - for x in 0..desired_instance_count { + for x in 0..rng.gen_range(particle_emitter.count.0, particle_emitter.count.1) { // how does ParticleEmitterMode fit in here? // can we have a ParticleInstance type per ParticleEmitterMode? // can we mix and match instance types in the same instances_vec? instances_vec.push(ParticleInstance::new( Mat4::identity() // initial rotation - .rotated_x(rng.gen_range(0.0, 3.14 * 2.0)) - .rotated_y(rng.gen_range(0.0, 3.14 * 2.0)) - .rotated_z(rng.gen_range(0.0, 3.14 * 2.0)) + .rotated_x(rng.gen_range(particle_emitter.initial_orientation.0.x * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.x * std::f32::consts::PI * 2.0)) + .rotated_y(rng.gen_range(particle_emitter.initial_orientation.0.y * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.y * std::f32::consts::PI * 2.0)) + .rotated_z(rng.gen_range(particle_emitter.initial_orientation.0.z * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.z * std::f32::consts::PI * 2.0)) + // initial scale + .scaled_3d(rng.gen_range(particle_emitter.initial_scale.0, particle_emitter.initial_scale.1)) // inition position .translated_3d( - pos.0 + pos.0 // relative + Vec3::new( - rng.gen_range(-5.0, 5.0), - rng.gen_range(-5.0, 5.0), - rng.gen_range(0.0, 10.0), + rng.gen_range(particle_emitter.initial_offset.0.x, particle_emitter.initial_offset.1.x), + rng.gen_range(particle_emitter.initial_offset.0.y, particle_emitter.initial_offset.1.y), + rng.gen_range(particle_emitter.initial_offset.0.z, particle_emitter.initial_offset.1.z), ), ), Rgb::broadcast(1.0), // instance color @@ -162,9 +206,5 @@ fn into_particles( )); } - let instances = renderer - .create_instances(&instances_vec) - .expect("Failed to upload particle instances to the GPU!"); - - vec![Particles { instances }] + instances_vec } From 803677f0fb877ca5aea513881b376f5468f65d5d Mon Sep 17 00:00:00 2001 From: scott-c Date: Mon, 13 Jul 2020 21:44:28 +0800 Subject: [PATCH 05/28] Add particle velocity and ability particle emitter --- assets/voxygen/shaders/particle-vert.glsl | 21 +--- assets/voxygen/voxel/particle.vox | 3 + common/src/comp/ability.rs | 4 +- common/src/comp/inventory/item/tool.rs | 13 +-- common/src/comp/mod.rs | 2 +- common/src/comp/visual.rs | 20 +++- common/src/event.rs | 2 +- common/src/msg/ecs_packet.rs | 6 +- common/src/state.rs | 2 +- common/src/states/basic_ranged.rs | 10 +- server/src/cmd.rs | 3 +- server/src/events/entity_creation.rs | 11 +- server/src/sys/sentinel.rs | 14 +-- voxygen/src/render/pipelines/particle.rs | 8 +- voxygen/src/scene/particle.rs | 125 ++++++++++++++++++---- 15 files changed, 160 insertions(+), 84 deletions(-) create mode 100644 assets/voxygen/voxel/particle.vox diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index a50cc9a6a9..fcd4616023 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -12,7 +12,7 @@ in vec4 inst_mat2; in vec4 inst_mat3; in vec3 inst_col; in vec3 inst_vel; -in vec4 inst_tick; +in vec4 inst_time; in float inst_wind_sway; out vec3 f_pos; @@ -30,20 +30,10 @@ void main() { inst_mat[2] = inst_mat2; inst_mat[3] = inst_mat3; - vec3 particle_pos = (inst_mat * vec4(0, 0, 0, 1)).xyz; - f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz; - f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); + //f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); - // Wind waving - //f_pos += inst_wind_sway * vec3( - // sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), - // sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), - // 0.0 - //) * pow(abs(v_pos.z) * SCALE, 1.3) * 0.2; - - float elapsed = (tick.x - inst_tick.x) / 100000.0; - f_pos += (inst_vel * elapsed); + f_pos += inst_vel * (tick.x - inst_time.x); // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); @@ -53,11 +43,6 @@ void main() { f_col = srgb_to_linear(col) * srgb_to_linear(inst_col); f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; - // Select glowing - if (select_pos.w > 0 && select_pos.xyz == floor(particle_pos)) { - f_col *= 4.0; - } - f_light = 1.0; gl_Position = diff --git a/assets/voxygen/voxel/particle.vox b/assets/voxygen/voxel/particle.vox new file mode 100644 index 0000000000..1f8fde6d26 --- /dev/null +++ b/assets/voxygen/voxel/particle.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba07287771bbfa8f369d0d634a6f847138b605962362517af16bec1e2a7c7951 +size 64 diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index e37e6bf26d..d656e8d4e6 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -62,7 +62,7 @@ pub enum CharacterAbility { projectile: Projectile, projectile_body: Body, projectile_light: Option, - projectile_particles: Option, + projectile_particles: Vec, projectile_gravity: Option, }, Boost { @@ -261,7 +261,7 @@ impl From<&CharacterAbility> for CharacterState { projectile: projectile.clone(), projectile_body: *projectile_body, projectile_light: *projectile_light, - projectile_particles: *projectile_particles, + projectile_particles: projectile_particles.clone().to_vec(), projectile_gravity: *projectile_gravity, }), CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data { diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index fc594bd7df..e86dc1a34b 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -180,7 +180,7 @@ impl Tool { }, projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_particles: None, + projectile_particles: vec![], projectile_gravity: Some(Gravity(0.2)), }, ChargedRanged { @@ -195,7 +195,7 @@ impl Tool { recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_particles: None, + projectile_particles: vec![], projectile_gravity: Some(Gravity(0.05)), }, ], @@ -276,7 +276,7 @@ impl Tool { col: (0.85, 0.5, 0.11).into(), ..Default::default() }), - projectile_particles: Some(ParticleEmitter { + projectile_particles: vec![ParticleEmitter { mode: ParticleEmitterMode::Sprinkler, // model_key: "voxygen.voxel.not_found", count: (2, 3), @@ -288,7 +288,8 @@ impl Tool { vek::Vec3::broadcast(1.0), ), initial_scale: (0.1, 0.3), - }), + initial_velocity: (vek::Vec3::zero(), vek::Vec3::one()), + }], projectile_gravity: None, }, BasicRanged { @@ -313,7 +314,7 @@ impl Tool { col: (1.0, 0.75, 0.11).into(), ..Default::default() }), - projectile_particles: Some(ParticleEmitter::default()), + projectile_particles: vec![ParticleEmitter::default()], projectile_gravity: None, }, ], @@ -358,7 +359,7 @@ impl Tool { col: (0.0, 1.0, 0.33).into(), ..Default::default() }), - projectile_particles: None, + projectile_particles: vec![], projectile_gravity: None, }, ], diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 4126c1a9df..3326cf2288 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -53,4 +53,4 @@ pub use player::{Player, MAX_MOUNT_RANGE_SQR}; pub use projectile::Projectile; pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet}; pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; -pub use visual::{LightAnimation, LightEmitter, ParticleEmitter}; +pub use visual::{LightAnimation, LightEmitter, ParticleEmitter, ParticleEmitters}; diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index 39f3f07054..fd3904dc0a 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -52,6 +52,13 @@ pub enum ParticleEmitterMode { Sprinkler, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy +pub struct ParticleEmitters(pub Vec); + +impl Default for ParticleEmitters { + fn default() -> Self { Self(vec![ParticleEmitter::default()]) } +} + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ParticleEmitter { pub mode: ParticleEmitterMode, @@ -67,6 +74,7 @@ pub struct ParticleEmitter { pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, pub initial_scale: (f32, f32), // fn() -> Vec3, pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, + pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, } impl Default for ParticleEmitter { @@ -75,11 +83,15 @@ impl Default for ParticleEmitter { mode: ParticleEmitterMode::Sprinkler, // model_key: "voxygen.voxel.not_found", count: (2, 5), - frequency: Duration::from_millis(500), - initial_lifespan: Duration::from_secs(2), - initial_offset: (vek::Vec3::broadcast(-5.0), vek::Vec3::broadcast(5.0)), - initial_orientation: (vek::Vec3::broadcast(-5.0), vek::Vec3::broadcast(5.0)), + frequency: Duration::from_millis(100), + initial_lifespan: Duration::from_secs(20), + initial_offset: (vek::Vec3::broadcast(-0.1), vek::Vec3::broadcast(0.1)), + initial_orientation: (vek::Vec3::broadcast(0.0), vek::Vec3::broadcast(1.0)), initial_scale: (0.1, 2.0), + initial_velocity: ( + vek::Vec3::new(0.0, 0.0, 0.2), + vek::Vec3::new(0.01, 0.01, 1.0), + ), } } } diff --git a/common/src/event.rs b/common/src/event.rs index f291971c25..36e595154e 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -43,7 +43,7 @@ pub enum ServerEvent { dir: Dir, body: comp::Body, light: Option, - particles: Option, + particles: Vec, projectile: comp::Projectile, gravity: Option, }, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index e8fa12c33d..2b7512e234 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -15,7 +15,7 @@ sum_type! { Stats(comp::Stats), Energy(comp::Energy), LightEmitter(comp::LightEmitter), - ParticleEmitter(comp::ParticleEmitter), + ParticleEmitter(comp::ParticleEmitters), Item(comp::Item), Scale(comp::Scale), Group(comp::Group), @@ -43,7 +43,7 @@ sum_type! { Stats(PhantomData), Energy(PhantomData), LightEmitter(PhantomData), - ParticleEmitter(PhantomData), + ParticleEmitter(PhantomData), Item(PhantomData), Scale(PhantomData), Group(PhantomData), @@ -126,7 +126,7 @@ impl sync::CompPacket for EcsCompPacket { sync::handle_remove::(entity, world) }, EcsCompPhantom::ParticleEmitter(_) => { - sync::handle_remove::(entity, world) + sync::handle_remove::(entity, world) }, EcsCompPhantom::Item(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Scale(_) => sync::handle_remove::(entity, world), diff --git a/common/src/state.rs b/common/src/state.rs index c4798c5813..a06ee25dce 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -113,7 +113,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index 647b166230..8e5acaa967 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -20,7 +20,7 @@ pub struct Data { pub projectile: Projectile, pub projectile_body: Body, pub projectile_light: Option, - pub projectile_particles: Option, + pub projectile_particles: Vec, pub projectile_gravity: Option, /// Whether the attack fired already pub exhausted: bool, @@ -49,7 +49,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles, + projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: false, }); @@ -63,7 +63,7 @@ impl CharacterBehavior for Data { body: self.projectile_body, projectile, light: self.projectile_light, - particles: self.projectile_particles, + particles: self.projectile_particles.clone(), gravity: self.projectile_gravity, }); @@ -75,7 +75,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles, + projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: true, }); @@ -92,7 +92,7 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles, + projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: true, }); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index c6942b4090..c96265e33c 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -23,7 +23,6 @@ use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; use vek::*; use world::util::Sampler; -use comp::visual::ParticleEmitterMode; use scan_fmt::{scan_fmt, scan_fmt_some}; use tracing::error; @@ -911,7 +910,7 @@ fn handle_light( .create_entity_synced() .with(pos) .with(comp::ForceUpdate) - .with(comp::ParticleEmitter::default()) + .with(comp::ParticleEmitters::default()) .with(light_emitter); if let Some(light_offset) = light_offset_opt { builder.with(light_offset).build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 83ff38a94c..d6725fc87f 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -2,11 +2,10 @@ use crate::{sys, Server, StateExt}; use common::{ comp::{ self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, - ParticleEmitter, Pos, Projectile, Scale, Stats, Vel, WaypointArea, + ParticleEmitter, ParticleEmitters, Pos, Projectile, Scale, Stats, Vel, WaypointArea, }, util::Dir, }; -use comp::visual::ParticleEmitterMode; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; @@ -78,7 +77,7 @@ pub fn handle_shoot( dir: Dir, body: Body, light: Option, - particles: Option, + particles: Vec, projectile: Projectile, gravity: Option, ) { @@ -98,9 +97,7 @@ pub fn handle_shoot( if let Some(light) = light { builder = builder.with(light) } - if let Some(particles) = particles { - builder = builder.with(particles) - } + builder = builder.with(ParticleEmitters(particles)); if let Some(gravity) = gravity { builder = builder.with(gravity) } @@ -118,7 +115,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { flicker: 1.0, animated: true, }) - .with(ParticleEmitter::default()) + .with(ParticleEmitters::default()) .with(WaypointArea::default()) .build(); } diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 0fe4af0a3d..97e86c3480 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -2,8 +2,8 @@ use super::SysTimer; use common::{ comp::{ Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, - Loadout, Mass, MountState, Mounting, Ori, ParticleEmitter, Player, Pos, Scale, Stats, - Sticky, Vel, + Loadout, Mass, MountState, Mounting, Ori, ParticleEmitter, ParticleEmitters, Player, Pos, + Scale, Stats, Sticky, Vel, }, msg::EcsCompPacket, sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, Uid, UpdateTracker, WorldSyncExt}, @@ -45,7 +45,7 @@ pub struct TrackedComps<'a> { pub energy: ReadStorage<'a, Energy>, pub can_build: ReadStorage<'a, CanBuild>, pub light_emitter: ReadStorage<'a, LightEmitter>, - pub particle_emitter: ReadStorage<'a, ParticleEmitter>, + pub particle_emitter: ReadStorage<'a, ParticleEmitters>, pub item: ReadStorage<'a, Item>, pub scale: ReadStorage<'a, Scale>, pub mounting: ReadStorage<'a, Mounting>, @@ -96,7 +96,7 @@ impl<'a> TrackedComps<'a> { .map(|c| comps.push(c.into())); self.particle_emitter .get(entity) - .copied() + .cloned() .map(|c| comps.push(c.into())); self.item.get(entity).cloned().map(|c| comps.push(c.into())); self.scale @@ -153,7 +153,7 @@ pub struct ReadTrackers<'a> { pub energy: ReadExpect<'a, UpdateTracker>, pub can_build: ReadExpect<'a, UpdateTracker>, pub light_emitter: ReadExpect<'a, UpdateTracker>, - pub particle_emitter: ReadExpect<'a, UpdateTracker>, + pub particle_emitter: ReadExpect<'a, UpdateTracker>, pub item: ReadExpect<'a, UpdateTracker>, pub scale: ReadExpect<'a, UpdateTracker>, pub mounting: ReadExpect<'a, UpdateTracker>, @@ -223,7 +223,7 @@ pub struct WriteTrackers<'a> { energy: WriteExpect<'a, UpdateTracker>, can_build: WriteExpect<'a, UpdateTracker>, light_emitter: WriteExpect<'a, UpdateTracker>, - particle_emitter: WriteExpect<'a, UpdateTracker>, + particle_emitter: WriteExpect<'a, UpdateTracker>, item: WriteExpect<'a, UpdateTracker>, scale: WriteExpect<'a, UpdateTracker>, mounting: WriteExpect<'a, UpdateTracker>, @@ -304,7 +304,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); - world.register_tracker::(); + world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 1352549054..37c15a4577 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -28,7 +28,7 @@ gfx_defines! { inst_mat3: [f32; 4] = "inst_mat3", inst_col: [f32; 3] = "inst_col", inst_vel: [f32; 3] = "inst_vel", - inst_tick: [f32; 4] = "inst_tick", + inst_time: [f32; 4] = "inst_time", inst_wind_sway: f32 = "inst_wind_sway", mode: u8 = "mode", } @@ -74,7 +74,7 @@ impl Instance { mat: Mat4, col: Rgb, vel: Vec3, - tick: u64, + time: f64, wind_sway: f32, mode: ParticleEmitterMode, ) -> Self { @@ -86,7 +86,7 @@ impl Instance { inst_mat3: mat_arr[3], inst_col: col.into_array(), inst_vel: vel.into_array(), - inst_tick: [tick as f32; 4], + inst_time: [time as f32; 4], inst_wind_sway: wind_sway, @@ -101,7 +101,7 @@ impl Default for Instance { Mat4::identity(), Rgb::broadcast(1.0), Vec3::zero(), - 0, + 0.0, 0.0, ParticleEmitterMode::Sprinkler, ) diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 75b41cd8d0..3ed56be91a 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -2,22 +2,23 @@ use super::SceneData; use crate::{ mesh::Meshable, render::{ - mesh::Quad, Consts, Globals, Instances, Light, Mesh, Model, ParticleInstance, - ParticlePipeline, Renderer, Shadow, + mesh::Quad, Consts, Globals, Instances, Light, Model, ParticleInstance, ParticlePipeline, + Renderer, Shadow, }, }; use common::{ assets, - comp::{visual::ParticleEmitterMode, Ori, ParticleEmitter, Pos, Vel}, + comp::{ + visual::ParticleEmitterMode, CharacterState, Ori, ParticleEmitter, ParticleEmitters, Pos, + Vel, + }, figure::Segment, - vol::BaseVol, }; use dot_vox::DotVoxData; use hashbrown::HashMap; use rand::Rng; use specs::{Entity as EcsEntity, Join, WorldExt}; use std::time::{Duration, Instant}; -use tracing::{debug, error, warn}; use vek::{Mat4, Rgb, Vec3}; struct Particles { @@ -48,7 +49,7 @@ pub struct ParticleMgr { beginning_of_time: Instant, } -const MODEL_KEY: &str = "voxygen.voxel.not_found"; +const MODEL_KEY: &str = "voxygen.voxel.particle"; impl ParticleMgr { pub fn new(renderer: &mut Renderer) -> Self { @@ -93,24 +94,34 @@ impl ParticleMgr { view_mat: Mat4, proj_mat: Mat4, ) { + let now = Instant::now(); let state = scene_data.state; let ecs = state.ecs(); - let tick = scene_data.tick; - - let now = Instant::now(); - let beginning_of_time1 = self.beginning_of_time.clone(); - // remove dead emitters self.emitters.retain(|k, _v| ecs.is_alive(*k)); // remove dead particles self.particles.retain(|p| p.alive_until > now); - // add living entities particles - for (_i, (entity, particle_emitter, pos, ori, vel)) in ( + // add ParticleEmitter particles + self.maintain_particle_emitter(renderer, scene_data); + + self.maintain_ability_particles(renderer, scene_data); + } + + fn maintain_particle_emitter(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + let state = scene_data.state; + let ecs = state.ecs(); + + let time = state.get_time(); + + let now = Instant::now(); + let beginning_of_time1 = self.beginning_of_time.clone(); + + for (_i, (entity, particle_emitters, pos, ori, vel)) in ( &ecs.entities(), - &ecs.read_storage::(), + &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), @@ -118,15 +129,78 @@ impl ParticleMgr { .join() .enumerate() { - let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { - last_emit: beginning_of_time1, // self.beginning_of_time.clone() - }); + for particle_emitter in &particle_emitters.0 { + // TODO: track multiple particle_emitter last_emit + let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { + last_emit: beginning_of_time1, // self.beginning_of_time.clone() + }); - if emitter.last_emit + particle_emitter.frequency < now { - emitter.last_emit = Instant::now(); + if emitter.last_emit + particle_emitter.frequency < now { + emitter.last_emit = Instant::now(); + + let cpu_insts = + into_particle_instances(&particle_emitter, renderer, time, pos, ori, vel); + + let gpu_insts = renderer + .create_instances(&cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + let entry = self.particles.push(Particles { + alive_until: now + particle_emitter.initial_lifespan, + instances: gpu_insts, + }); + } + } + } + } + + fn maintain_ability_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + let state = scene_data.state; + let ecs = state.ecs(); + + let time = state.get_time(); + + let now = Instant::now(); + let beginning_of_time1 = self.beginning_of_time.clone(); + + for (_i, (entity, pos, character_state)) in ( + &ecs.entities(), + //&ecs.read_storage::(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() + .enumerate() + { + // let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { + // last_emit: beginning_of_time1, // self.beginning_of_time.clone() + // }); + + // if emitter.last_emit + particle_emitter.frequency < now { + // emitter.last_emit = Instant::now(); + // } + + if let CharacterState::BasicMelee(melee_data) = character_state { + // TODO: configure the emitter on the ability instead. + let particle_emitter = ParticleEmitter { + count: (30, 50), + frequency: Duration::from_millis(1000), // doesn't matter + initial_lifespan: Duration::from_millis(1000), + initial_offset: ( + Vec3::new(1.0, -1.0, 0.0), + Vec3::new(1.01, 1.0, 2.0), /* TODO: cone // melee_data.max_angle */ + ), + initial_orientation: (Vec3::zero(), Vec3::one()), + initial_scale: (1.0, 3.0), + mode: ParticleEmitterMode::Sprinkler, + initial_velocity: ( + Vec3::new(1.0, 0.0, 0.0), + Vec3::new(10.0, 0.01, 0.01), /* TODO: cone // melee_data.max_angle */ + ), + }; let cpu_insts = - into_particle_instances(particle_emitter, renderer, tick, pos, ori, vel); + into_particle_instances(&particle_emitter, renderer, time, pos, None, None); let gpu_insts = renderer .create_instances(&cpu_insts) @@ -166,7 +240,7 @@ impl ParticleMgr { fn into_particle_instances( particle_emitter: &ParticleEmitter, renderer: &mut Renderer, - tick: u64, + time: f64, pos: &Pos, ori: Option<&Ori>, vel: Option<&Vel>, @@ -199,8 +273,13 @@ fn into_particle_instances( ), ), Rgb::broadcast(1.0), // instance color - vel2 + Vec3::broadcast(rng.gen_range(-5.0, 5.0)), - tick, + vel2 // relative + + Vec3::new( + rng.gen_range(particle_emitter.initial_velocity.0.x, particle_emitter.initial_velocity.1.x), + rng.gen_range(particle_emitter.initial_velocity.0.y, particle_emitter.initial_velocity.1.y), + rng.gen_range(particle_emitter.initial_velocity.0.z, particle_emitter.initial_velocity.1.z), + ), + time, rng.gen_range(0.0, 20.0), // wind sway ParticleEmitterMode::Sprinkler, // particle_emitter.mode */ )); From 3139e85dff04818e4c785c9d9652518c0eab3e7e Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 14 Jul 2020 23:08:05 +0800 Subject: [PATCH 06/28] allow for col particles --- common/src/comp/inventory/item/tool.rs | 1 + common/src/comp/visual.rs | 26 +++++++++++++++++++------- voxygen/src/scene/particle.rs | 7 ++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index e86dc1a34b..86746daf2f 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -289,6 +289,7 @@ impl Tool { ), initial_scale: (0.1, 0.3), initial_velocity: (vek::Vec3::zero(), vek::Vec3::one()), + initial_col: (vek::Rgb::zero(), vek::Rgb::one()), }], projectile_gravity: None, }, diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index fd3904dc0a..0159c2945e 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -56,7 +56,20 @@ pub enum ParticleEmitterMode { pub struct ParticleEmitters(pub Vec); impl Default for ParticleEmitters { - fn default() -> Self { Self(vec![ParticleEmitter::default()]) } + fn default() -> Self { + Self(vec![ParticleEmitter::default(), ParticleEmitter { + mode: ParticleEmitterMode::Sprinkler, + // model_key: "voxygen.voxel.not_found", + count: (7, 10), + frequency: Duration::from_millis(100), + initial_lifespan: Duration::from_secs(500), + initial_offset: (Vec3::broadcast(-0.2), Vec3::broadcast(0.2)), + initial_orientation: (Vec3::broadcast(0.0), Vec3::broadcast(1.0)), + initial_scale: (1.0, 2.0), + initial_velocity: (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.01, 0.01, 3.0)), + initial_col: (Rgb::new(0.999, 0.0, 0.0), Rgb::new(1.0, 1.0, 0.001)), + }]) + } } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -75,6 +88,7 @@ pub struct ParticleEmitter { pub initial_scale: (f32, f32), // fn() -> Vec3, pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, + pub initial_col: (Rgb, Rgb), // fn() -> Vec3, } impl Default for ParticleEmitter { @@ -85,13 +99,11 @@ impl Default for ParticleEmitter { count: (2, 5), frequency: Duration::from_millis(100), initial_lifespan: Duration::from_secs(20), - initial_offset: (vek::Vec3::broadcast(-0.1), vek::Vec3::broadcast(0.1)), - initial_orientation: (vek::Vec3::broadcast(0.0), vek::Vec3::broadcast(1.0)), + initial_offset: (Vec3::broadcast(-0.1), Vec3::broadcast(0.1)), + initial_orientation: (Vec3::broadcast(0.0), Vec3::broadcast(1.0)), initial_scale: (0.1, 2.0), - initial_velocity: ( - vek::Vec3::new(0.0, 0.0, 0.2), - vek::Vec3::new(0.01, 0.01, 1.0), - ), + initial_velocity: (Vec3::new(0.0, 0.0, 0.2), Vec3::new(0.01, 0.01, 1.0)), + initial_col: (Rgb::new(0.999, 0.999, 0.999), Rgb::new(1.0, 1.0, 1.0)), } } } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 3ed56be91a..96af3493a7 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -197,6 +197,7 @@ impl ParticleMgr { Vec3::new(1.0, 0.0, 0.0), Vec3::new(10.0, 0.01, 0.01), /* TODO: cone // melee_data.max_angle */ ), + initial_col: (Rgb::zero(), Rgb::one()), }; let cpu_insts = @@ -272,7 +273,11 @@ fn into_particle_instances( rng.gen_range(particle_emitter.initial_offset.0.z, particle_emitter.initial_offset.1.z), ), ), - Rgb::broadcast(1.0), // instance color + Rgb::new( + rng.gen_range(particle_emitter.initial_col.0.r, particle_emitter.initial_col.1.r), + rng.gen_range(particle_emitter.initial_col.0.g, particle_emitter.initial_col.1.g), + rng.gen_range(particle_emitter.initial_col.0.b, particle_emitter.initial_col.1.b), + ), // instance color vel2 // relative + Vec3::new( rng.gen_range(particle_emitter.initial_velocity.0.x, particle_emitter.initial_velocity.1.x), From 4bc373a832e438573fc4d1e873ed41ae1e278ad5 Mon Sep 17 00:00:00 2001 From: scott-c Date: Wed, 15 Jul 2020 23:45:47 +0800 Subject: [PATCH 07/28] remove particle emitter component --- assets/voxygen/shaders/particle-vert.glsl | 53 +++++- common/src/comp/ability.rs | 6 +- common/src/comp/inventory/item/tool.rs | 23 +-- common/src/comp/mod.rs | 2 +- common/src/comp/phys.rs | 4 + common/src/comp/visual.rs | 115 ++++++------ common/src/event.rs | 1 - common/src/msg/ecs_packet.rs | 7 - common/src/state.rs | 1 - common/src/states/basic_ranged.rs | 7 +- common/src/util/dir.rs | 2 + server/src/cmd.rs | 1 - server/src/events/entity_creation.rs | 7 +- server/src/events/mod.rs | 3 +- server/src/sys/sentinel.rs | 19 +- voxygen/src/render/pipelines/particle.rs | 71 +++---- voxygen/src/scene/mod.rs | 18 +- voxygen/src/scene/particle.rs | 219 ++++++---------------- voxygen/src/session.rs | 12 +- 19 files changed, 220 insertions(+), 351 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index fcd4616023..c51431b5d5 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -10,10 +10,9 @@ in vec4 inst_mat0; in vec4 inst_mat1; in vec4 inst_mat2; in vec4 inst_mat3; -in vec3 inst_col; -in vec3 inst_vel; -in vec4 inst_time; -in float inst_wind_sway; +in float inst_time; +in float inst_entropy; +in int inst_mode; out vec3 f_pos; flat out vec3 f_norm; @@ -23,6 +22,17 @@ out float f_light; const float SCALE = 1.0 / 11.0; +float PHI = 1.61803398874989484820459; // Φ = Golden Ratio + +float gold_noise(in vec2 xy, in float seed){ + return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); +} + +// Modes +const int SMOKE = 0; +const int FIRE = 1; +const int FLAMETHROWER = 2; + void main() { mat4 inst_mat; inst_mat[0] = inst_mat0; @@ -30,10 +40,39 @@ void main() { inst_mat[2] = inst_mat2; inst_mat[3] = inst_mat3; - f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz; - //f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); + float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy); + float rand2 = gold_noise(vec2(1.0, 1.0), inst_entropy); + float rand3 = gold_noise(vec2(2.0, 2.0), inst_entropy); + float rand4 = gold_noise(vec2(3.0, 3.0), inst_entropy); + float rand5 = gold_noise(vec2(4.0, 4.0), inst_entropy); + float rand6 = gold_noise(vec2(5.0, 5.0), inst_entropy); - f_pos += inst_vel * (tick.x - inst_time.x); + vec3 inst_vel = vec3(0.0, 0.0, 0.0); + vec3 inst_pos = vec3(0.0, 0.0, 0.0); + vec3 inst_col = vec3(1.0, 1.0, 1.0); + + if (inst_mode == SMOKE) { + inst_col = vec3(1.0, 1.0, 1.0); + inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 1.0 + rand3); + inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + } else if (inst_mode == FIRE) { + inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); + inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 4.0 + rand3); + inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + } else if (inst_mode == FLAMETHROWER) { + // TODO: velocity based on attack range, angle and parent orientation. + inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); + inst_vel = vec3(rand1 * 0.1, rand2 * 0.1, 3.0 + rand3); + inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + } else { + inst_col = vec3(rand1, rand2, rand3); + inst_vel = vec3(rand4, rand5, rand6); + inst_pos = vec3(rand1, rand2, rand3); + } + + f_pos = (inst_mat * vec4((v_pos + inst_pos) * SCALE, 1)).xyz; + + f_pos += inst_vel * (tick.x - inst_time); // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index d656e8d4e6..79292b797e 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2,8 +2,7 @@ use crate::{ comp::{ ability::Stage, item::{armor::Protection, Item, ItemKind}, - Body, CharacterState, EnergySource, Gravity, LightEmitter, ParticleEmitter, Projectile, - StateUpdate, + Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate, }, states::{triple_strike::*, *}, sys::character_behavior::JoinData, @@ -62,7 +61,6 @@ pub enum CharacterAbility { projectile: Projectile, projectile_body: Body, projectile_light: Option, - projectile_particles: Vec, projectile_gravity: Option, }, Boost { @@ -249,7 +247,6 @@ impl From<&CharacterAbility> for CharacterState { projectile, projectile_body, projectile_light, - projectile_particles, projectile_gravity, energy_cost: _, } => CharacterState::BasicRanged(basic_ranged::Data { @@ -261,7 +258,6 @@ impl From<&CharacterAbility> for CharacterState { projectile: projectile.clone(), projectile_body: *projectile_body, projectile_light: *projectile_light, - projectile_particles: projectile_particles.clone().to_vec(), projectile_gravity: *projectile_gravity, }), CharacterAbility::Boost { duration, only_up } => CharacterState::Boost(boost::Data { diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 86746daf2f..8b09cc7324 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,8 +2,8 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, visual::ParticleEmitterMode, Body, CharacterAbility, Gravity, - HealthChange, HealthSource, LightEmitter, ParticleEmitter, Projectile, + body::object, projectile, Body, CharacterAbility, Gravity, HealthChange, HealthSource, + LightEmitter, Projectile, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -180,7 +180,6 @@ impl Tool { }, projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_particles: vec![], projectile_gravity: Some(Gravity(0.2)), }, ChargedRanged { @@ -195,7 +194,6 @@ impl Tool { recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_particles: vec![], projectile_gravity: Some(Gravity(0.05)), }, ], @@ -276,21 +274,6 @@ impl Tool { col: (0.85, 0.5, 0.11).into(), ..Default::default() }), - projectile_particles: vec![ParticleEmitter { - mode: ParticleEmitterMode::Sprinkler, - // model_key: "voxygen.voxel.not_found", - count: (2, 3), - frequency: Duration::from_millis(50), - initial_lifespan: Duration::from_millis(500), - initial_offset: (vek::Vec3::broadcast(-1.0), vek::Vec3::broadcast(1.0)), - initial_orientation: ( - vek::Vec3::broadcast(-1.0), - vek::Vec3::broadcast(1.0), - ), - initial_scale: (0.1, 0.3), - initial_velocity: (vek::Vec3::zero(), vek::Vec3::one()), - initial_col: (vek::Rgb::zero(), vek::Rgb::one()), - }], projectile_gravity: None, }, BasicRanged { @@ -315,7 +298,6 @@ impl Tool { col: (1.0, 0.75, 0.11).into(), ..Default::default() }), - projectile_particles: vec![ParticleEmitter::default()], projectile_gravity: None, }, ], @@ -360,7 +342,6 @@ impl Tool { col: (0.0, 1.0, 0.33).into(), ..Default::default() }), - projectile_particles: vec![], projectile_gravity: None, }, ], diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 3326cf2288..963f3c95a6 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -53,4 +53,4 @@ pub use player::{Player, MAX_MOUNT_RANGE_SQR}; pub use projectile::Projectile; pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet}; pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; -pub use visual::{LightAnimation, LightEmitter, ParticleEmitter, ParticleEmitters}; +pub use visual::{LightAnimation, LightEmitter}; diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index e9496f047c..b6fdb18d73 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -24,6 +24,10 @@ impl Component for Vel { #[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct Ori(pub Dir); +impl Ori { + pub fn vec(&self) -> &Vec3 { &self.0.vec() } +} + impl Component for Ori { type Storage = IdvStorage; } diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index 0159c2945e..b8f463d593 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; -use std::time::Duration; use vek::*; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -47,67 +46,67 @@ impl Default for LightAnimation { impl Component for LightAnimation { type Storage = FlaggedStorage>; } -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ParticleEmitterMode { - Sprinkler, -} +// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +// pub enum ParticleEmitterMode { +// Sprinkler, +// } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy -pub struct ParticleEmitters(pub Vec); +// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy +// pub struct ParticleEmitters(pub Vec); -impl Default for ParticleEmitters { - fn default() -> Self { - Self(vec![ParticleEmitter::default(), ParticleEmitter { - mode: ParticleEmitterMode::Sprinkler, - // model_key: "voxygen.voxel.not_found", - count: (7, 10), - frequency: Duration::from_millis(100), - initial_lifespan: Duration::from_secs(500), - initial_offset: (Vec3::broadcast(-0.2), Vec3::broadcast(0.2)), - initial_orientation: (Vec3::broadcast(0.0), Vec3::broadcast(1.0)), - initial_scale: (1.0, 2.0), - initial_velocity: (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.01, 0.01, 3.0)), - initial_col: (Rgb::new(0.999, 0.0, 0.0), Rgb::new(1.0, 1.0, 0.001)), - }]) - } -} +// impl Default for ParticleEmitters { +// fn default() -> Self { +// Self(vec![ParticleEmitter::default(), ParticleEmitter { +// mode: ParticleEmitterMode::Sprinkler, +// // model_key: "voxygen.voxel.not_found", +// count: (7, 10), +// frequency: Duration::from_millis(100), +// initial_lifespan: Duration::from_millis(500), +// initial_offset: (Vec3::broadcast(-0.2), Vec3::broadcast(0.2)), +// initial_orientation: (Vec3::broadcast(0.0), +// Vec3::broadcast(1.0)), initial_scale: (1.0, 2.5), +// initial_velocity: (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.01, +// 0.01, 3.0)), initial_col: (Rgb::new(0.999, 0.0, 0.0), +// Rgb::new(1.0, 1.0, 0.001)), }]) +// } +// } -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct ParticleEmitter { - pub mode: ParticleEmitterMode, +// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +// pub struct ParticleEmitter { +// pub mode: ParticleEmitterMode, - // spawn X particles per Y, that live for Z - // pub model_ref: &str, // can we have some kind of stack based key like a u8? - pub count: (u8, u8), - pub frequency: Duration, +// // spawn X particles per Y, that live for Z +// // pub model_ref: &str, // can we have some kind of stack based key like +// a u8? pub count: (u8, u8), +// pub frequency: Duration, - // relative to Pos, Ori components? - // can these be functions that returns a Vec3? - pub initial_lifespan: Duration, - pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, - pub initial_scale: (f32, f32), // fn() -> Vec3, - pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, - pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, - pub initial_col: (Rgb, Rgb), // fn() -> Vec3, -} +// // relative to Pos, Ori components? +// // can these be functions that returns a Vec3? +// pub initial_lifespan: Duration, +// pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, +// pub initial_scale: (f32, f32), // fn() -> Vec3, +// pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, +// pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, +// pub initial_col: (Rgb, Rgb), // fn() -> Vec3, +// } -impl Default for ParticleEmitter { - fn default() -> Self { - Self { - mode: ParticleEmitterMode::Sprinkler, - // model_key: "voxygen.voxel.not_found", - count: (2, 5), - frequency: Duration::from_millis(100), - initial_lifespan: Duration::from_secs(20), - initial_offset: (Vec3::broadcast(-0.1), Vec3::broadcast(0.1)), - initial_orientation: (Vec3::broadcast(0.0), Vec3::broadcast(1.0)), - initial_scale: (0.1, 2.0), - initial_velocity: (Vec3::new(0.0, 0.0, 0.2), Vec3::new(0.01, 0.01, 1.0)), - initial_col: (Rgb::new(0.999, 0.999, 0.999), Rgb::new(1.0, 1.0, 1.0)), - } - } -} +// impl Default for ParticleEmitter { +// fn default() -> Self { +// Self { +// mode: ParticleEmitterMode::Sprinkler, +// // model_key: "voxygen.voxel.not_found", +// count: (2, 5), +// frequency: Duration::from_millis(100), +// initial_lifespan: Duration::from_secs(20), +// initial_offset: (Vec3::broadcast(-0.1), Vec3::broadcast(0.1)), +// initial_orientation: (Vec3::broadcast(0.0), +// Vec3::broadcast(1.0)), initial_scale: (0.1, 2.0), +// initial_velocity: (Vec3::new(0.0, 0.0, 0.2), Vec3::new(0.01, +// 0.01, 1.0)), initial_col: (Rgb::new(0.999, 0.999, 0.999), +// Rgb::new(1.0, 1.0, 1.0)), } +// } +// } -impl Component for ParticleEmitter { - type Storage = FlaggedStorage>; -} +// impl Component for ParticleEmitter { +// type Storage = FlaggedStorage>; +// } diff --git a/common/src/event.rs b/common/src/event.rs index 36e595154e..943757a2a7 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -43,7 +43,6 @@ pub enum ServerEvent { dir: Dir, body: comp::Body, light: Option, - particles: Vec, projectile: comp::Projectile, gravity: Option, }, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 2b7512e234..726d67321a 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -15,7 +15,6 @@ sum_type! { Stats(comp::Stats), Energy(comp::Energy), LightEmitter(comp::LightEmitter), - ParticleEmitter(comp::ParticleEmitters), Item(comp::Item), Scale(comp::Scale), Group(comp::Group), @@ -43,7 +42,6 @@ sum_type! { Stats(PhantomData), Energy(PhantomData), LightEmitter(PhantomData), - ParticleEmitter(PhantomData), Item(PhantomData), Scale(PhantomData), Group(PhantomData), @@ -71,7 +69,6 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::ParticleEmitter(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Group(comp) => sync::handle_insert(comp, entity, world), @@ -97,7 +94,6 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::ParticleEmitter(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Group(comp) => sync::handle_modify(comp, entity, world), @@ -125,9 +121,6 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::LightEmitter(_) => { sync::handle_remove::(entity, world) }, - EcsCompPhantom::ParticleEmitter(_) => { - sync::handle_remove::(entity, world) - }, EcsCompPhantom::Item(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Scale(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Group(_) => sync::handle_remove::(entity, world), diff --git a/common/src/state.rs b/common/src/state.rs index a06ee25dce..3d003b7a4f 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -113,7 +113,6 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); - ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index 8e5acaa967..b41bd6c208 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Body, CharacterState, Gravity, LightEmitter, ParticleEmitter, Projectile, StateUpdate}, + comp::{Body, CharacterState, Gravity, LightEmitter, Projectile, StateUpdate}, event::ServerEvent, states::utils::*, sys::character_behavior::*, @@ -20,7 +20,6 @@ pub struct Data { pub projectile: Projectile, pub projectile_body: Body, pub projectile_light: Option, - pub projectile_particles: Vec, pub projectile_gravity: Option, /// Whether the attack fired already pub exhausted: bool, @@ -49,7 +48,6 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: false, }); @@ -63,7 +61,6 @@ impl CharacterBehavior for Data { body: self.projectile_body, projectile, light: self.projectile_light, - particles: self.projectile_particles.clone(), gravity: self.projectile_gravity, }); @@ -75,7 +72,6 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: true, }); @@ -92,7 +88,6 @@ impl CharacterBehavior for Data { projectile: self.projectile.clone(), projectile_body: self.projectile_body, projectile_light: self.projectile_light, - projectile_particles: self.projectile_particles.clone(), projectile_gravity: self.projectile_gravity, exhausted: true, }); diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index 22aa1b0fe6..96c085479d 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -87,6 +87,8 @@ impl Dir { } pub fn is_valid(&self) -> bool { !self.0.map(f32::is_nan).reduce_or() && self.is_normalized() } + + pub fn vec(&self) -> &Vec3 { &self.0 } } impl std::ops::Deref for Dir { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index c96265e33c..b580cfbd98 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -910,7 +910,6 @@ fn handle_light( .create_entity_synced() .with(pos) .with(comp::ForceUpdate) - .with(comp::ParticleEmitters::default()) .with(light_emitter); if let Some(light_offset) = light_offset_opt { builder.with(light_offset).build(); diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index d6725fc87f..a62c0b342c 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -1,8 +1,8 @@ use crate::{sys, Server, StateExt}; use common::{ comp::{ - self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, - ParticleEmitter, ParticleEmitters, Pos, Projectile, Scale, Stats, Vel, WaypointArea, + self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos, + Projectile, Scale, Stats, Vel, WaypointArea, }, util::Dir, }; @@ -77,7 +77,6 @@ pub fn handle_shoot( dir: Dir, body: Body, light: Option, - particles: Vec, projectile: Projectile, gravity: Option, ) { @@ -97,7 +96,6 @@ pub fn handle_shoot( if let Some(light) = light { builder = builder.with(light) } - builder = builder.with(ParticleEmitters(particles)); if let Some(gravity) = gravity { builder = builder.with(gravity) } @@ -115,7 +113,6 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { flicker: 1.0, animated: true, }) - .with(ParticleEmitters::default()) .with(WaypointArea::default()) .build(); } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 9761e1d869..412d9a536a 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -61,10 +61,9 @@ impl Server { dir, body, light, - particles, projectile, gravity, - } => handle_shoot(self, entity, dir, body, light, particles, projectile, gravity), + } => handle_shoot(self, entity, dir, body, light, projectile, gravity), ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change), ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 97e86c3480..6121dbb4a9 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -2,7 +2,7 @@ use super::SysTimer; use common::{ comp::{ Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, - Loadout, Mass, MountState, Mounting, Ori, ParticleEmitter, ParticleEmitters, Player, Pos, + Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, }, msg::EcsCompPacket, @@ -45,7 +45,6 @@ pub struct TrackedComps<'a> { pub energy: ReadStorage<'a, Energy>, pub can_build: ReadStorage<'a, CanBuild>, pub light_emitter: ReadStorage<'a, LightEmitter>, - pub particle_emitter: ReadStorage<'a, ParticleEmitters>, pub item: ReadStorage<'a, Item>, pub scale: ReadStorage<'a, Scale>, pub mounting: ReadStorage<'a, Mounting>, @@ -94,10 +93,6 @@ impl<'a> TrackedComps<'a> { .get(entity) .copied() .map(|c| comps.push(c.into())); - self.particle_emitter - .get(entity) - .cloned() - .map(|c| comps.push(c.into())); self.item.get(entity).cloned().map(|c| comps.push(c.into())); self.scale .get(entity) @@ -153,7 +148,6 @@ pub struct ReadTrackers<'a> { pub energy: ReadExpect<'a, UpdateTracker>, pub can_build: ReadExpect<'a, UpdateTracker>, pub light_emitter: ReadExpect<'a, UpdateTracker>, - pub particle_emitter: ReadExpect<'a, UpdateTracker>, pub item: ReadExpect<'a, UpdateTracker>, pub scale: ReadExpect<'a, UpdateTracker>, pub mounting: ReadExpect<'a, UpdateTracker>, @@ -187,12 +181,6 @@ impl<'a> ReadTrackers<'a> { &comps.light_emitter, filter, ) - .with_component( - &comps.uid, - &*self.particle_emitter, - &comps.particle_emitter, - filter, - ) .with_component(&comps.uid, &*self.item, &comps.item, filter) .with_component(&comps.uid, &*self.scale, &comps.scale, filter) .with_component(&comps.uid, &*self.mounting, &comps.mounting, filter) @@ -223,7 +211,6 @@ pub struct WriteTrackers<'a> { energy: WriteExpect<'a, UpdateTracker>, can_build: WriteExpect<'a, UpdateTracker>, light_emitter: WriteExpect<'a, UpdateTracker>, - particle_emitter: WriteExpect<'a, UpdateTracker>, item: WriteExpect<'a, UpdateTracker>, scale: WriteExpect<'a, UpdateTracker>, mounting: WriteExpect<'a, UpdateTracker>, @@ -246,9 +233,6 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.energy.record_changes(&comps.energy); trackers.can_build.record_changes(&comps.can_build); trackers.light_emitter.record_changes(&comps.light_emitter); - trackers - .particle_emitter - .record_changes(&comps.particle_emitter); trackers.item.record_changes(&comps.item); trackers.scale.record_changes(&comps.scale); trackers.mounting.record_changes(&comps.mounting); @@ -304,7 +288,6 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); - world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 37c15a4577..9b4a94606f 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -2,7 +2,6 @@ use super::{ super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt}, Globals, Light, Shadow, }; -use common::comp::visual::ParticleEmitterMode; use gfx::{ self, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, @@ -22,15 +21,26 @@ gfx_defines! { } vertex Instance { + // created_at time, so we can calculate time relativity, needed for relative animation. + // can save 32 bits per instance, for particles that are not relatively animated. + inst_time: f32 = "inst_time", + + // a seed value for randomness + inst_entropy: f32 = "inst_entropy", + + // modes should probably be seperate shaders, as a part of scaling and optimisation efforts + inst_mode: i32 = "inst_mode", + + // a triangle is: f32 x 3 x 3 x 1 = 288 bits + // a quad is: f32 x 3 x 3 x 2 = 576 bits + // a cube is: f32 x 3 x 3 x 12 = 3456 bits + // this matrix is: f32 x 4 x 4 x 1 = 512 bits (per instance!) + // consider using vertex postion & entropy instead; + // to determine initial offset, scale, orientation etc. inst_mat0: [f32; 4] = "inst_mat0", inst_mat1: [f32; 4] = "inst_mat1", inst_mat2: [f32; 4] = "inst_mat2", inst_mat3: [f32; 4] = "inst_mat3", - inst_col: [f32; 3] = "inst_col", - inst_vel: [f32; 3] = "inst_vel", - inst_time: [f32; 4] = "inst_time", - inst_wind_sway: f32 = "inst_wind_sway", - mode: u8 = "mode", } pipeline pipe { @@ -69,43 +79,38 @@ impl Vertex { } } +pub enum ParticleMode { + CampfireSmoke, + CampfireFire, +} + +impl ParticleMode { + pub fn into_uint(self) -> u32 { self as u32 } +} + impl Instance { pub fn new( - mat: Mat4, - col: Rgb, - vel: Vec3, - time: f64, - wind_sway: f32, - mode: ParticleEmitterMode, + inst_time: f64, + inst_entropy: f32, + inst_mode: ParticleMode, + inst_mat: Mat4, ) -> Self { - let mat_arr = mat.into_col_arrays(); + let inst_mat_col = inst_mat.into_col_arrays(); Self { - inst_mat0: mat_arr[0], - inst_mat1: mat_arr[1], - inst_mat2: mat_arr[2], - inst_mat3: mat_arr[3], - inst_col: col.into_array(), - inst_vel: vel.into_array(), - inst_time: [time as f32; 4], + inst_time: inst_time as f32, + inst_entropy, + inst_mode: inst_mode as i32, - inst_wind_sway: wind_sway, - - mode: mode as u8, + inst_mat0: inst_mat_col[0], + inst_mat1: inst_mat_col[1], + inst_mat2: inst_mat_col[2], + inst_mat3: inst_mat_col[3], } } } impl Default for Instance { - fn default() -> Self { - Self::new( - Mat4::identity(), - Rgb::broadcast(1.0), - Vec3::zero(), - 0.0, - 0.0, - ParticleEmitterMode::Sprinkler, - ) - } + fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Mat4::identity()) } } pub struct ParticlePipeline; diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 9b0f6c3fe5..8e7186cfb6 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -398,14 +398,7 @@ impl Scene { self.figure_mgr.clean(scene_data.tick); // Maintain the particles. - self.particle_mgr.maintain( - renderer, - &scene_data, - self.camera.get_focus_pos(), - self.loaded_distance, - view_mat, - proj_mat, - ); + self.particle_mgr.maintain(renderer, &scene_data); // Maintain audio self.sfx_mgr @@ -442,13 +435,8 @@ impl Scene { scene_data.figure_lod_render_distance, ); - self.particle_mgr.render( - renderer, - &self.globals, - &self.lights, - &self.shadows, - self.camera.get_focus_pos(), - ); + self.particle_mgr + .render(renderer, &self.globals, &self.lights, &self.shadows); // Render the skybox. renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 96af3493a7..3477f52d87 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -2,51 +2,31 @@ use super::SceneData; use crate::{ mesh::Meshable, render::{ - mesh::Quad, Consts, Globals, Instances, Light, Model, ParticleInstance, ParticlePipeline, - Renderer, Shadow, + pipelines::particle::ParticleMode, Consts, Globals, Instances, Light, Model, + ParticleInstance, ParticlePipeline, Renderer, Shadow, }, }; use common::{ assets, - comp::{ - visual::ParticleEmitterMode, CharacterState, Ori, ParticleEmitter, ParticleEmitters, Pos, - Vel, - }, + comp::{object, Body, CharacterState, Pos}, figure::Segment, }; use dot_vox::DotVoxData; use hashbrown::HashMap; use rand::Rng; -use specs::{Entity as EcsEntity, Join, WorldExt}; +use specs::{Join, WorldExt}; use std::time::{Duration, Instant}; -use vek::{Mat4, Rgb, Vec3}; +use vek::{Mat4, Vec3}; struct Particles { - // this is probably nieve, - // could cache and re-use between particles, - // should be a cache key? - // model: Model, - // created_at: Instant, - // lifespan: Duration, alive_until: Instant, // created_at + lifespan - instances: Instances, } -struct Emitter { - last_emit: Instant, -} - pub struct ParticleMgr { - // to keep track of spawn intervals - emitters: HashMap, - - // to keep track of lifespans + // keep track of lifespans particles: Vec, - model_cache: HashMap<&'static str, Model>, - - beginning_of_time: Instant, } const MODEL_KEY: &str = "voxygen.voxel.particle"; @@ -55,21 +35,18 @@ impl ParticleMgr { pub fn new(renderer: &mut Renderer) -> Self { let mut model_cache = HashMap::new(); - let model = model_cache.entry(MODEL_KEY).or_insert_with(|| { + model_cache.entry(MODEL_KEY).or_insert_with(|| { let offset = Vec3::zero(); let lod_scale = Vec3::one(); - // TODO: from cache let vox = assets::load_expect::(MODEL_KEY); - // TODO: from cache let mesh = &Meshable::::generate_mesh( &Segment::from(vox.as_ref()), (offset * lod_scale, Vec3::one() / lod_scale), ) .0; - // TODO: from cache let model = renderer .create_model(mesh) .expect("Failed to create particle model"); @@ -78,137 +55,103 @@ impl ParticleMgr { }); Self { - emitters: HashMap::new(), particles: Vec::new(), model_cache, - beginning_of_time: Instant::now(), } } - pub fn maintain( - &mut self, - renderer: &mut Renderer, - scene_data: &SceneData, - focus_pos: Vec3, - loaded_distance: f32, - view_mat: Mat4, - proj_mat: Mat4, - ) { + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let now = Instant::now(); - let state = scene_data.state; - let ecs = state.ecs(); - - // remove dead emitters - self.emitters.retain(|k, _v| ecs.is_alive(*k)); // remove dead particles self.particles.retain(|p| p.alive_until > now); - // add ParticleEmitter particles - self.maintain_particle_emitter(renderer, scene_data); + self.maintain_waypoint_particles(renderer, scene_data); - self.maintain_ability_particles(renderer, scene_data); + self.maintain_boost_particles(renderer, scene_data); } - fn maintain_particle_emitter(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + fn maintain_waypoint_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); - let time = state.get_time(); - let now = Instant::now(); - let beginning_of_time1 = self.beginning_of_time.clone(); + let mut rng = rand::thread_rng(); - for (_i, (entity, particle_emitters, pos, ori, vel)) in ( + for (_i, (_entity, pos, body)) in ( &ecs.entities(), - &ecs.read_storage::(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), - ecs.read_storage::().maybe(), + &ecs.read_storage::(), ) .join() .enumerate() { - for particle_emitter in &particle_emitters.0 { - // TODO: track multiple particle_emitter last_emit - let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { - last_emit: beginning_of_time1, // self.beginning_of_time.clone() - }); + match body { + Body::Object(object::Body::CampfireLit) => { + let fire_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireFire, + Mat4::identity().translated_3d(pos.0), + )]; - if emitter.last_emit + particle_emitter.frequency < now { - emitter.last_emit = Instant::now(); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); - let cpu_insts = - into_particle_instances(&particle_emitter, renderer, time, pos, ori, vel); + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + Mat4::identity().translated_3d(pos.0), + )]; - let gpu_insts = renderer - .create_instances(&cpu_insts) + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) .expect("Failed to upload particle instances to the GPU!"); - let entry = self.particles.push(Particles { - alive_until: now + particle_emitter.initial_lifespan, - instances: gpu_insts, + self.particles.push(Particles { + alive_until: now + Duration::from_secs(10), + instances: smoke_cpu_insts, }); - } + }, + _ => {}, } } } - fn maintain_ability_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + fn maintain_boost_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); - let time = state.get_time(); - let now = Instant::now(); - let beginning_of_time1 = self.beginning_of_time.clone(); + let mut rng = rand::thread_rng(); - for (_i, (entity, pos, character_state)) in ( + for (_i, (_entity, pos, character_state)) in ( &ecs.entities(), - //&ecs.read_storage::(), &ecs.read_storage::(), &ecs.read_storage::(), ) .join() .enumerate() { - // let emitter = self.emitters.entry(entity).or_insert_with(|| Emitter { - // last_emit: beginning_of_time1, // self.beginning_of_time.clone() - // }); - - // if emitter.last_emit + particle_emitter.frequency < now { - // emitter.last_emit = Instant::now(); - // } - - if let CharacterState::BasicMelee(melee_data) = character_state { - // TODO: configure the emitter on the ability instead. - let particle_emitter = ParticleEmitter { - count: (30, 50), - frequency: Duration::from_millis(1000), // doesn't matter - initial_lifespan: Duration::from_millis(1000), - initial_offset: ( - Vec3::new(1.0, -1.0, 0.0), - Vec3::new(1.01, 1.0, 2.0), /* TODO: cone // melee_data.max_angle */ - ), - initial_orientation: (Vec3::zero(), Vec3::one()), - initial_scale: (1.0, 3.0), - mode: ParticleEmitterMode::Sprinkler, - initial_velocity: ( - Vec3::new(1.0, 0.0, 0.0), - Vec3::new(10.0, 0.01, 0.01), /* TODO: cone // melee_data.max_angle */ - ), - initial_col: (Rgb::zero(), Rgb::one()), - }; - - let cpu_insts = - into_particle_instances(&particle_emitter, renderer, time, pos, None, None); + if let CharacterState::Boost(_) = character_state { + let cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + Mat4::identity().translated_3d(pos.0), + )]; let gpu_insts = renderer .create_instances(&cpu_insts) .expect("Failed to upload particle instances to the GPU!"); - let entry = self.particles.push(Particles { - alive_until: now + particle_emitter.initial_lifespan, + self.particles.push(Particles { + alive_until: now + Duration::from_secs(15), instances: gpu_insts, }); } @@ -221,7 +164,6 @@ impl ParticleMgr { globals: &Consts, lights: &Consts, shadows: &Consts, - focus_pos: Vec3, ) { for particle in &self.particles { renderer.render_particles( @@ -237,58 +179,3 @@ impl ParticleMgr { } } } - -fn into_particle_instances( - particle_emitter: &ParticleEmitter, - renderer: &mut Renderer, - time: f64, - pos: &Pos, - ori: Option<&Ori>, - vel: Option<&Vel>, -) -> Vec { - let mut rng = rand::thread_rng(); - let vel_default = Vel::default(); - let vel2 = vel.unwrap_or_else(|| &vel_default).0; - - let mut instances_vec = Vec::new(); - - for x in 0..rng.gen_range(particle_emitter.count.0, particle_emitter.count.1) { - // how does ParticleEmitterMode fit in here? - // can we have a ParticleInstance type per ParticleEmitterMode? - // can we mix and match instance types in the same instances_vec? - instances_vec.push(ParticleInstance::new( - Mat4::identity() - // initial rotation - .rotated_x(rng.gen_range(particle_emitter.initial_orientation.0.x * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.x * std::f32::consts::PI * 2.0)) - .rotated_y(rng.gen_range(particle_emitter.initial_orientation.0.y * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.y * std::f32::consts::PI * 2.0)) - .rotated_z(rng.gen_range(particle_emitter.initial_orientation.0.z * std::f32::consts::PI * 2.0, particle_emitter.initial_orientation.1.z * std::f32::consts::PI * 2.0)) - // initial scale - .scaled_3d(rng.gen_range(particle_emitter.initial_scale.0, particle_emitter.initial_scale.1)) - // inition position - .translated_3d( - pos.0 // relative - + Vec3::new( - rng.gen_range(particle_emitter.initial_offset.0.x, particle_emitter.initial_offset.1.x), - rng.gen_range(particle_emitter.initial_offset.0.y, particle_emitter.initial_offset.1.y), - rng.gen_range(particle_emitter.initial_offset.0.z, particle_emitter.initial_offset.1.z), - ), - ), - Rgb::new( - rng.gen_range(particle_emitter.initial_col.0.r, particle_emitter.initial_col.1.r), - rng.gen_range(particle_emitter.initial_col.0.g, particle_emitter.initial_col.1.g), - rng.gen_range(particle_emitter.initial_col.0.b, particle_emitter.initial_col.1.b), - ), // instance color - vel2 // relative - + Vec3::new( - rng.gen_range(particle_emitter.initial_velocity.0.x, particle_emitter.initial_velocity.1.x), - rng.gen_range(particle_emitter.initial_velocity.0.y, particle_emitter.initial_velocity.1.y), - rng.gen_range(particle_emitter.initial_velocity.0.z, particle_emitter.initial_velocity.1.z), - ), - time, - rng.gen_range(0.0, 20.0), // wind sway - ParticleEmitterMode::Sprinkler, // particle_emitter.mode */ - )); - } - - instances_vec -} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index fb44b952a4..37d3a7e637 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -787,7 +787,7 @@ impl PlayState for SessionState { }, HudEvent::AdjustParticleRenderDistance(particle_render_distance) => { global_state.settings.graphics.particle_render_distance = - particle_render_distance; + particle_render_distance; global_state.settings.save_to_file_warn(); }, HudEvent::AdjustFigureLoDRenderDistance(figure_lod_render_distance) => { @@ -1002,9 +1002,12 @@ impl PlayState for SessionState { gamma: global_state.settings.graphics.gamma, mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable, sprite_render_distance: global_state.settings.graphics.sprite_render_distance - as f32, - particle_render_distance: global_state.settings.graphics.particle_render_distance - as f32, + as f32, + particle_render_distance: global_state + .settings + .graphics + .particle_render_distance + as f32, figure_lod_render_distance: global_state .settings .graphics @@ -1060,6 +1063,7 @@ impl PlayState for SessionState { 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, + particle_render_distance: settings.graphics.particle_render_distance as f32, is_aiming: self.is_aiming, }; self.scene.render( From bb2a5c885b37dda4a93195d3b44e45ae2fb3f832 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sun, 19 Jul 2020 15:16:06 +0800 Subject: [PATCH 08/28] Add fireball and bomb particle effects --- assets/voxygen/shaders/particle-vert.glsl | 58 ++--- common/src/comp/visual.rs | 64 ------ voxygen/src/render/pipelines/particle.rs | 37 ++- voxygen/src/scene/particle.rs | 266 +++++++++++++++++----- 4 files changed, 265 insertions(+), 160 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index c51431b5d5..54fac86749 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -6,10 +6,7 @@ in vec3 v_pos; in uint v_col; in uint v_norm_ao; -in vec4 inst_mat0; -in vec4 inst_mat1; -in vec4 inst_mat2; -in vec4 inst_mat3; +in vec3 inst_pos; in float inst_time; in float inst_entropy; in int inst_mode; @@ -22,7 +19,8 @@ out float f_light; const float SCALE = 1.0 / 11.0; -float PHI = 1.61803398874989484820459; // Φ = Golden Ratio +// Φ = Golden Ratio +float PHI = 1.61803398874989484820459; float gold_noise(in vec2 xy, in float seed){ return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); @@ -31,46 +29,54 @@ float gold_noise(in vec2 xy, in float seed){ // Modes const int SMOKE = 0; const int FIRE = 1; -const int FLAMETHROWER = 2; +const int GUN_POWDER_SPARK = 2; + +// meters per second +const float earth_gravity = 9.807; + +mat4 translate(vec3 vec){ + return mat4( + vec4(1.0, 0.0, 0.0, 0.0), + vec4(0.0, 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(vec.x, vec.y, vec.z, 1.0) + ); +} void main() { - mat4 inst_mat; - inst_mat[0] = inst_mat0; - inst_mat[1] = inst_mat1; - inst_mat[2] = inst_mat2; - inst_mat[3] = inst_mat3; + mat4 inst_mat = translate(inst_pos); float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy); - float rand2 = gold_noise(vec2(1.0, 1.0), inst_entropy); - float rand3 = gold_noise(vec2(2.0, 2.0), inst_entropy); - float rand4 = gold_noise(vec2(3.0, 3.0), inst_entropy); - float rand5 = gold_noise(vec2(4.0, 4.0), inst_entropy); - float rand6 = gold_noise(vec2(5.0, 5.0), inst_entropy); + float rand2 = gold_noise(vec2(10.0, 10.0), inst_entropy); + float rand3 = gold_noise(vec2(20.0, 20.0), inst_entropy); + float rand4 = gold_noise(vec2(30.0, 30.0), inst_entropy); + float rand5 = gold_noise(vec2(40.0, 40.0), inst_entropy); + float rand6 = gold_noise(vec2(50.0, 50.0), inst_entropy); vec3 inst_vel = vec3(0.0, 0.0, 0.0); - vec3 inst_pos = vec3(0.0, 0.0, 0.0); + vec3 inst_pos2 = vec3(0.0, 0.0, 0.0); vec3 inst_col = vec3(1.0, 1.0, 1.0); if (inst_mode == SMOKE) { inst_col = vec3(1.0, 1.0, 1.0); inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 1.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); } else if (inst_mode == FIRE) { inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 4.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); - } else if (inst_mode == FLAMETHROWER) { - // TODO: velocity based on attack range, angle and parent orientation. - inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); - inst_vel = vec3(rand1 * 0.1, rand2 * 0.1, 3.0 + rand3); - inst_pos = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + } else if (inst_mode == GUN_POWDER_SPARK) { + inst_col = vec3(1.0, 1.0, 0.0); + inst_vel = vec3(rand2 * 2.0 - 1.0, rand1 * 2.0 - 1.0, 5.0 + rand3); + inst_vel -= vec3(0.0, 0.0, earth_gravity * (tick.x - inst_time)); + inst_pos2 = vec3(0.0, 0.0, 0.0); } else { inst_col = vec3(rand1, rand2, rand3); inst_vel = vec3(rand4, rand5, rand6); - inst_pos = vec3(rand1, rand2, rand3); + inst_pos2 = vec3(rand1, rand2, rand3); } - f_pos = (inst_mat * vec4((v_pos + inst_pos) * SCALE, 1)).xyz; + f_pos = (inst_mat * vec4((v_pos + inst_pos2) * SCALE, 1)).xyz; f_pos += inst_vel * (tick.x - inst_time); diff --git a/common/src/comp/visual.rs b/common/src/comp/visual.rs index b8f463d593..013f27aa2c 100644 --- a/common/src/comp/visual.rs +++ b/common/src/comp/visual.rs @@ -46,67 +46,3 @@ impl Default for LightAnimation { impl Component for LightAnimation { type Storage = FlaggedStorage>; } -// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -// pub enum ParticleEmitterMode { -// Sprinkler, -// } - -// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // Copy -// pub struct ParticleEmitters(pub Vec); - -// impl Default for ParticleEmitters { -// fn default() -> Self { -// Self(vec![ParticleEmitter::default(), ParticleEmitter { -// mode: ParticleEmitterMode::Sprinkler, -// // model_key: "voxygen.voxel.not_found", -// count: (7, 10), -// frequency: Duration::from_millis(100), -// initial_lifespan: Duration::from_millis(500), -// initial_offset: (Vec3::broadcast(-0.2), Vec3::broadcast(0.2)), -// initial_orientation: (Vec3::broadcast(0.0), -// Vec3::broadcast(1.0)), initial_scale: (1.0, 2.5), -// initial_velocity: (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.01, -// 0.01, 3.0)), initial_col: (Rgb::new(0.999, 0.0, 0.0), -// Rgb::new(1.0, 1.0, 0.001)), }]) -// } -// } - -// #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -// pub struct ParticleEmitter { -// pub mode: ParticleEmitterMode, - -// // spawn X particles per Y, that live for Z -// // pub model_ref: &str, // can we have some kind of stack based key like -// a u8? pub count: (u8, u8), -// pub frequency: Duration, - -// // relative to Pos, Ori components? -// // can these be functions that returns a Vec3? -// pub initial_lifespan: Duration, -// pub initial_offset: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_scale: (f32, f32), // fn() -> Vec3, -// pub initial_orientation: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_velocity: (Vec3, Vec3), // fn() -> Vec3, -// pub initial_col: (Rgb, Rgb), // fn() -> Vec3, -// } - -// impl Default for ParticleEmitter { -// fn default() -> Self { -// Self { -// mode: ParticleEmitterMode::Sprinkler, -// // model_key: "voxygen.voxel.not_found", -// count: (2, 5), -// frequency: Duration::from_millis(100), -// initial_lifespan: Duration::from_secs(20), -// initial_offset: (Vec3::broadcast(-0.1), Vec3::broadcast(0.1)), -// initial_orientation: (Vec3::broadcast(0.0), -// Vec3::broadcast(1.0)), initial_scale: (0.1, 2.0), -// initial_velocity: (Vec3::new(0.0, 0.0, 0.2), Vec3::new(0.01, -// 0.01, 1.0)), initial_col: (Rgb::new(0.999, 0.999, 0.999), -// Rgb::new(1.0, 1.0, 1.0)), } -// } -// } - -// impl Component for ParticleEmitter { -// type Storage = FlaggedStorage>; -// } diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 9b4a94606f..1f467c638e 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -26,21 +26,24 @@ gfx_defines! { inst_time: f32 = "inst_time", // a seed value for randomness + // can save 32 bits per instance, for particles that don't need randomness/uniqueness. inst_entropy: f32 = "inst_entropy", - // modes should probably be seperate shaders, as a part of scaling and optimisation efforts + // modes should probably be seperate shaders, as a part of scaling and optimisation efforts. + // can save 32 bits per instance, and have cleaner tailor made code. inst_mode: i32 = "inst_mode", - // a triangle is: f32 x 3 x 3 x 1 = 288 bits - // a quad is: f32 x 3 x 3 x 2 = 576 bits - // a cube is: f32 x 3 x 3 x 12 = 3456 bits - // this matrix is: f32 x 4 x 4 x 1 = 512 bits (per instance!) - // consider using vertex postion & entropy instead; - // to determine initial offset, scale, orientation etc. - inst_mat0: [f32; 4] = "inst_mat0", - inst_mat1: [f32; 4] = "inst_mat1", - inst_mat2: [f32; 4] = "inst_mat2", - inst_mat3: [f32; 4] = "inst_mat3", + // a triangle is: f32 x 3 x 3 x 1 = 288 bits + // a quad is: f32 x 3 x 3 x 2 = 576 bits + // a cube is: f32 x 3 x 3 x 12 = 3456 bits + // this vec is: f32 x 3 x 1 x 1 = 96 bits (per instance!) + // consider using a throw-away mesh and + // positioning the vertex verticies instead, + // if we have: + // - a triangle mesh, and 3 or more instances. + // - a quad mesh, and 6 or more instances. + // - a cube mesh, and 36 or more instances. + inst_pos: [f32; 3] = "inst_pos", } pipeline pipe { @@ -82,6 +85,7 @@ impl Vertex { pub enum ParticleMode { CampfireSmoke, CampfireFire, + GunPowderSpark, } impl ParticleMode { @@ -93,24 +97,19 @@ impl Instance { inst_time: f64, inst_entropy: f32, inst_mode: ParticleMode, - inst_mat: Mat4, + inst_pos: Vec3, ) -> Self { - let inst_mat_col = inst_mat.into_col_arrays(); Self { inst_time: inst_time as f32, inst_entropy, inst_mode: inst_mode as i32, - - inst_mat0: inst_mat_col[0], - inst_mat1: inst_mat_col[1], - inst_mat2: inst_mat_col[2], - inst_mat3: inst_mat_col[3], + inst_pos: inst_pos.into_array(), } } } impl Default for Instance { - fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Mat4::identity()) } + fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) } } pub struct ParticlePipeline; diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 3477f52d87..5a0b299a7b 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -16,7 +16,7 @@ use hashbrown::HashMap; use rand::Rng; use specs::{Join, WorldExt}; use std::time::{Duration, Instant}; -use vek::{Mat4, Vec3}; +use vek::Vec3; struct Particles { alive_until: Instant, // created_at + lifespan @@ -66,63 +66,231 @@ impl ParticleMgr { // remove dead particles self.particles.retain(|p| p.alive_until > now); - self.maintain_waypoint_particles(renderer, scene_data); + self.maintain_body_particles(renderer, scene_data); self.maintain_boost_particles(renderer, scene_data); } - fn maintain_waypoint_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { - let state = scene_data.state; - let ecs = state.ecs(); - let time = state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); - - for (_i, (_entity, pos, body)) in ( + fn maintain_body_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + let ecs = scene_data.state.ecs(); + for (_i, (_entity, body, pos)) in ( &ecs.entities(), - &ecs.read_storage::(), &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() .enumerate() { match body { Body::Object(object::Body::CampfireLit) => { - let fire_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireFire, - Mat4::identity().translated_3d(pos.0), - )]; - - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), - }); - - let smoke_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - Mat4::identity().translated_3d(pos.0), - )]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - - self.particles.push(Particles { - alive_until: now + Duration::from_secs(10), - instances: smoke_cpu_insts, - }); + self.maintain_campfirelit_particles(renderer, scene_data, pos) }, + Body::Object(object::Body::BoltFire) => { + self.maintain_boltfire_particles(renderer, scene_data, pos) + }, + Body::Object(object::Body::BoltFireBig) => { + self.maintain_boltfirebig_particles(renderer, scene_data, pos) + }, + Body::Object(object::Body::Bomb) => { + self.maintain_bomb_particles(renderer, scene_data, pos) + }, + // Body::Object(object::Body::Pouch) => { + // self.maintain_pouch_particles(renderer, scene_data, pos) + // }, _ => {}, } } } + fn maintain_campfirelit_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireFire, + pos.0, + )]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(10), + instances: smoke_cpu_insts, + }); + } + + fn maintain_boltfire_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireFire, + pos.0, + )]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(1), + instances: smoke_cpu_insts, + }); + } + + fn maintain_boltfirebig_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), + ]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + ]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instances: smoke_cpu_insts, + }); + } + + fn maintain_bomb_particles( + &mut self, + renderer: &mut Renderer, + scene_data: &SceneData, + pos: &Pos, + ) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + let fire_cpu_insts = vec![ + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + ]; + + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instances: renderer + .create_instances(&fire_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"), + }); + + let smoke_cpu_insts = vec![ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + )]; + + let smoke_cpu_insts = renderer + .create_instances(&smoke_cpu_insts) + .expect("Failed to upload particle instances to the GPU!"); + + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instances: smoke_cpu_insts, + }); + } + + // fn maintain_pouch_particles( + // &mut self, + // renderer: &mut Renderer, + // scene_data: &SceneData, + // pos: &Pos, + // ) { + // let time = scene_data.state.get_time(); + // let now = Instant::now(); + // let mut rng = rand::thread_rng(); + + // let smoke_cpu_insts = vec![ParticleInstance::new( + // time, + // rng.gen(), + // ParticleMode::CampfireSmoke, + // pos.0, + // )]; + + // let smoke_cpu_insts = renderer + // .create_instances(&smoke_cpu_insts) + // .expect("Failed to upload particle instances to the GPU!"); + + // self.particles.push(Particles { + // alive_until: now + Duration::from_secs(1), + // instances: smoke_cpu_insts, + // }); + // } + fn maintain_boost_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); @@ -143,7 +311,7 @@ impl ParticleMgr { time, rng.gen(), ParticleMode::CampfireSmoke, - Mat4::identity().translated_3d(pos.0), + pos.0, )]; let gpu_insts = renderer @@ -165,17 +333,13 @@ impl ParticleMgr { lights: &Consts, shadows: &Consts, ) { + let model = &self + .model_cache + .get(MODEL_KEY) + .expect("Expected particle model in cache"); + for particle in &self.particles { - renderer.render_particles( - &self - .model_cache - .get(MODEL_KEY) - .expect("Expected particle model in cache"), - globals, - &particle.instances, - lights, - shadows, - ); + renderer.render_particles(model, globals, &particle.instances, lights, shadows); } } } From f9f9e9e1908c4f8bbeadee705304c98646b3f694 Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 21 Jul 2020 23:48:20 +0800 Subject: [PATCH 09/28] Add particle count to debug info --- voxygen/src/hud/mod.rs | 16 +++- voxygen/src/scene/mod.rs | 8 +- voxygen/src/scene/particle.rs | 176 ++++++++++++++-------------------- voxygen/src/session.rs | 3 + 4 files changed, 98 insertions(+), 105 deletions(-) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 7f8242af23..de2ac1649c 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -179,6 +179,7 @@ widget_ids! { entity_count, num_chunks, num_figures, + num_particles, // Game Version version, @@ -247,6 +248,8 @@ pub struct DebugInfo { pub num_visible_chunks: u32, pub num_figures: u32, pub num_figures_visible: u32, + pub num_particles: u32, + pub num_particles_visible: u32, } pub struct HudInfo { @@ -1496,6 +1499,17 @@ impl Hud { .font_size(self.fonts.cyri.scale(14)) .set(self.ids.num_figures, ui_widgets); + // Number of particles + Text::new(&format!( + "Particles: {} ({} visible)", + debug_info.num_particles, debug_info.num_particles_visible, + )) + .color(TEXT_COLOR) + .down_from(self.ids.num_figures, 5.0) + .font_id(self.fonts.cyri.conrod_id) + .font_size(self.fonts.cyri.scale(14)) + .set(self.ids.num_particles, ui_widgets); + // Help Window if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) { Text::new( @@ -1505,7 +1519,7 @@ impl Hud { .replace("{key}", help_key.to_string().as_str()), ) .color(TEXT_COLOR) - .down_from(self.ids.num_figures, 5.0) + .down_from(self.ids.num_particles, 5.0) .font_id(self.fonts.cyri.conrod_id) .font_size(self.fonts.cyri.scale(14)) .set(self.ids.help_info, ui_widgets); diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 8e7186cfb6..d242239e94 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -435,8 +435,12 @@ impl Scene { scene_data.figure_lod_render_distance, ); - self.particle_mgr - .render(renderer, &self.globals, &self.lights, &self.shadows); + self.particle_mgr.render( + renderer, + &self.globals, + &self.lights, + &self.shadows, + ); // Render the skybox. renderer.render_skybox(&self.skybox.model, &self.globals, &self.skybox.locals); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 5a0b299a7b..92ef68607e 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -20,12 +20,13 @@ use vek::Vec3; struct Particles { alive_until: Instant, // created_at + lifespan - instances: Instances, + instance: ParticleInstance, } pub struct ParticleMgr { // keep track of lifespans particles: Vec, + instances: Instances, model_cache: HashMap<&'static str, Model>, } @@ -54,21 +55,46 @@ impl ParticleMgr { model }); + let insts = Vec::new(); + + let instances = renderer + .create_instances(&insts) + .expect("Failed to upload particle instances to the GPU!"); + Self { particles: Vec::new(), + instances, model_cache, } } + pub fn particle_count(&self) -> usize { self.instances.count() } + + pub fn particle_count_visible(&self) -> usize { self.instances.count() } + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { let now = Instant::now(); // remove dead particles self.particles.retain(|p| p.alive_until > now); + // let zxc = scene_data.particle_render_distance; + self.maintain_body_particles(renderer, scene_data); self.maintain_boost_particles(renderer, scene_data); + + let all_cpu_instances = self + .particles + .iter() + .map(|p| p.instance) + .collect::>(); + + // TODO: upload just the ones that were created and added to queue, not all of + // them. + self.instances = renderer + .create_instances(&all_cpu_instances) + .expect("Failed to upload particle instances to the GPU!"); } fn maintain_body_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { @@ -112,34 +138,14 @@ impl ParticleMgr { let now = Instant::now(); let mut rng = rand::thread_rng(); - let fire_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireFire, - pos.0, - )]; - self.particles.push(Particles { alive_until: now + Duration::from_millis(250), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), }); - let smoke_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - pos.0, - )]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - self.particles.push(Particles { alive_until: now + Duration::from_secs(10), - instances: smoke_cpu_insts, + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), }); } @@ -153,34 +159,14 @@ impl ParticleMgr { let now = Instant::now(); let mut rng = rand::thread_rng(); - let fire_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireFire, - pos.0, - )]; - self.particles.push(Particles { alive_until: now + Duration::from_millis(250), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), }); - let smoke_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - pos.0, - )]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - self.particles.push(Particles { alive_until: now + Duration::from_secs(1), - instances: smoke_cpu_insts, + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), }); } @@ -194,31 +180,28 @@ impl ParticleMgr { let now = Instant::now(); let mut rng = rand::thread_rng(); - let fire_cpu_insts = vec![ - ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - ]; - + // fire self.particles.push(Particles { alive_until: now + Duration::from_millis(250), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), }); - let smoke_cpu_insts = vec![ - ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - ]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - + // smoke self.particles.push(Particles { alive_until: now + Duration::from_secs(2), - instances: smoke_cpu_insts, + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_secs(2), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), }); } @@ -232,35 +215,32 @@ impl ParticleMgr { let now = Instant::now(); let mut rng = rand::thread_rng(); - let fire_cpu_insts = vec![ - ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - ]; - + // sparks self.particles.push(Particles { alive_until: now + Duration::from_millis(1500), - instances: renderer - .create_instances(&fire_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), + }); + self.particles.push(Particles { + alive_until: now + Duration::from_millis(1500), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), }); - let smoke_cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - pos.0, - )]; - - let smoke_cpu_insts = renderer - .create_instances(&smoke_cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - + // smoke self.particles.push(Particles { alive_until: now + Duration::from_secs(2), - instances: smoke_cpu_insts, + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), }); } @@ -307,20 +287,14 @@ impl ParticleMgr { .enumerate() { if let CharacterState::Boost(_) = character_state { - let cpu_insts = vec![ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - pos.0, - )]; - - let gpu_insts = renderer - .create_instances(&cpu_insts) - .expect("Failed to upload particle instances to the GPU!"); - self.particles.push(Particles { alive_until: now + Duration::from_secs(15), - instances: gpu_insts, + instance: ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + pos.0, + ), }); } } @@ -338,8 +312,6 @@ impl ParticleMgr { .get(MODEL_KEY) .expect("Expected particle model in cache"); - for particle in &self.particles { - renderer.render_particles(model, globals, &particle.instances, lights, shadows); - } + renderer.render_particles(model, globals, &self.instances, lights, shadows); } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 37d3a7e637..86ea35b27a 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -689,6 +689,9 @@ impl PlayState for SessionState { num_visible_chunks: self.scene.terrain().visible_chunk_count() as u32, num_figures: self.scene.figure_mgr().figure_count() as u32, num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32, + num_particles: self.scene.particle_mgr().particle_count() as u32, + num_particles_visible: self.scene.particle_mgr().particle_count_visible() + as u32, }, &self.scene.camera(), global_state.clock.get_last_delta(), From 5acfe44cbb266dc7b860beb407e10bdef3403e42 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 25 Jul 2020 23:46:45 +0800 Subject: [PATCH 10/28] Add toggle particles graphics setting --- assets/voxygen/i18n/en.ron | 1 + assets/voxygen/shaders/include/globals.glsl | 1 - assets/voxygen/shaders/particle-frag.glsl | 2 +- voxygen/src/hud/mod.rs | 5 +- voxygen/src/hud/settings_window.rs | 31 +++++- voxygen/src/render/pipelines/mod.rs | 6 +- voxygen/src/scene/mod.rs | 4 +- voxygen/src/scene/particle.rs | 115 ++++++-------------- voxygen/src/scene/simple.rs | 1 - voxygen/src/session.rs | 17 +-- voxygen/src/settings.rs | 4 +- 11 files changed, 79 insertions(+), 108 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index e57424b780..b3a2004539 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -297,6 +297,7 @@ magically infused items?"#, "hud.settings.fluid_rendering_mode.cheap": "Cheap", "hud.settings.fluid_rendering_mode.shiny": "Shiny", "hud.settings.cloud_rendering_mode.regular": "Regular", + "hud.settings.particles": "Particles", "hud.settings.fullscreen": "Fullscreen", "hud.settings.save_window_size": "Save window size", diff --git a/assets/voxygen/shaders/include/globals.glsl b/assets/voxygen/shaders/include/globals.glsl index 7436e5c036..895492a23a 100644 --- a/assets/voxygen/shaders/include/globals.glsl +++ b/assets/voxygen/shaders/include/globals.glsl @@ -17,7 +17,6 @@ uniform u_globals { // 1 - ThirdPerson uint cam_mode; float sprite_render_distance; - float particle_render_distance; }; // Specifies the pattern used in the player dithering diff --git a/assets/voxygen/shaders/particle-frag.glsl b/assets/voxygen/shaders/particle-frag.glsl index 3e2ef6b9e6..3d730f7a50 100644 --- a/assets/voxygen/shaders/particle-frag.glsl +++ b/assets/voxygen/shaders/particle-frag.glsl @@ -34,5 +34,5 @@ void main() { vec3 fog_color = get_sky_color(normalize(f_pos - cam_pos.xyz), time_of_day.x, cam_pos.xyz, f_pos, 0.5, true, clouds); vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a); - tgt_color = vec4(color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (particle_render_distance - FADE_DIST)) / FADE_DIST, 0, 1)); + tgt_color = vec4(color, 1.0 - clamp((distance(focus_pos.xy, f_pos.xy) - (1000.0 - FADE_DIST)) / FADE_DIST, 0, 1)); } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index de2ac1649c..20a48df35e 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -269,7 +269,6 @@ pub enum Event { ToggleSmoothPan(bool), AdjustViewDistance(u32), AdjustSpriteRenderDistance(u32), - AdjustParticleRenderDistance(u32), AdjustFigureLoDRenderDistance(u32), AdjustMusicVolume(f32), AdjustSfxVolume(f32), @@ -279,6 +278,7 @@ pub enum Event { ChangeGamma(f32), MapZoom(f64), AdjustWindowSize([u16; 2]), + ToggleParticlesEnabled(bool), ToggleFullscreen, ChangeAaMode(AaMode), ChangeCloudMode(CloudMode), @@ -1911,6 +1911,9 @@ impl Hud { settings_window::Event::ChangeLanguage(language) => { events.push(Event::ChangeLanguage(language)); }, + settings_window::Event::ToggleParticlesEnabled(particles_enabled) => { + events.push(Event::ToggleParticlesEnabled(particles_enabled)); + }, settings_window::Event::ToggleFullscreen => { events.push(Event::ToggleFullscreen); }, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index c0b8e42fff..f9e6b7d17b 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -111,6 +111,8 @@ widget_ids! { cloud_mode_list, fluid_mode_text, fluid_mode_list, + particles_button, + particles_label, fullscreen_button, fullscreen_label, save_window_size_button, @@ -232,6 +234,7 @@ pub enum Event { AdjustFOV(u16), AdjustGamma(f32), AdjustWindowSize([u16; 2]), + ToggleParticlesEnabled(bool), ToggleFullscreen, ChangeAaMode(AaMode), ChangeCloudMode(CloudMode), @@ -1845,7 +1848,6 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.sprite_dist_text, ui); - Text::new(&format!( "{}", self.global_state.settings.graphics.sprite_render_distance @@ -1899,8 +1901,6 @@ impl<'a> Widget for SettingsWindow<'a> { .color(TEXT_COLOR) .set(state.ids.figure_dist_value, ui); - // TODO: Particle View Distance slider. - // AaMode Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode")) .down_from(state.ids.gamma_slider, 8.0) @@ -2016,11 +2016,34 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::ChangeFluidMode(mode_list[clicked])); } + // Particles + Text::new(&self.localized_strings.get("hud.settings.particles")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.fluid_mode_list, 8.0) + .color(TEXT_COLOR) + .set(state.ids.particles_label, ui); + + let particles_enabled = ToggleButton::new( + self.global_state.settings.graphics.particles_enabled, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .w_h(18.0, 18.0) + .right_from(state.ids.particles_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.particles_button, ui); + + if self.global_state.settings.graphics.particles_enabled != particles_enabled { + events.push(Event::ToggleParticlesEnabled(particles_enabled)); + } + // Fullscreen Text::new(&self.localized_strings.get("hud.settings.fullscreen")) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) - .down_from(state.ids.fluid_mode_list, 8.0) + .down_from(state.ids.particles_label, 8.0) .color(TEXT_COLOR) .set(state.ids.fullscreen_label, ui); diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index a3e978a70c..f97890626d 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,9 +1,9 @@ pub mod figure; pub mod fluid; +pub mod particle; pub mod postprocess; pub mod skybox; pub mod sprite; -pub mod particle; pub mod terrain; pub mod ui; @@ -30,7 +30,6 @@ gfx_defines! { gamma: [f32; 4] = "gamma", cam_mode: u32 = "cam_mode", sprite_render_distance: f32 = "sprite_render_distance", - particle_render_distance: f32 = "particle_render_distance", } constant Light { @@ -63,7 +62,6 @@ impl Globals { gamma: f32, cam_mode: CameraMode, sprite_render_distance: f32, - particle_render_distance: f32, ) -> Self { Self { view_mat: view_mat.into_col_arrays(), @@ -84,7 +82,6 @@ impl Globals { gamma: [gamma; 4], cam_mode: cam_mode as u32, sprite_render_distance, - particle_render_distance, } } } @@ -107,7 +104,6 @@ impl Default for Globals { 1.0, CameraMode::ThirdPerson, 250.0, - 250.0, ) } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index d242239e94..af59657488 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -81,7 +81,7 @@ pub struct SceneData<'a> { pub gamma: f32, pub mouse_smoothing: bool, pub sprite_render_distance: f32, - pub particle_render_distance: f32, + pub particles_enabled: bool, pub figure_lod_render_distance: f32, pub is_aiming: bool, } @@ -377,7 +377,6 @@ impl Scene { scene_data.gamma, self.camera.get_mode(), scene_data.sprite_render_distance as f32 - 20.0, - scene_data.particle_render_distance as f32 - 20.0, )]) .expect("Failed to update global constants"); @@ -437,6 +436,7 @@ impl Scene { self.particle_mgr.render( renderer, + scene_data, &self.globals, &self.lights, &self.shadows, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 92ef68607e..3a72f92b94 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -48,11 +48,9 @@ impl ParticleMgr { ) .0; - let model = renderer + renderer .create_model(mesh) .expect("Failed to create particle model"); - - model }); let insts = Vec::new(); @@ -73,31 +71,37 @@ impl ParticleMgr { pub fn particle_count_visible(&self) -> usize { self.instances.count() } pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { - let now = Instant::now(); + if scene_data.particles_enabled { + let now = Instant::now(); - // remove dead particles - self.particles.retain(|p| p.alive_until > now); + // remove dead particles + self.particles.retain(|p| p.alive_until > now); - // let zxc = scene_data.particle_render_distance; + self.maintain_body_particles(scene_data); + self.maintain_boost_particles(scene_data); - self.maintain_body_particles(renderer, scene_data); - - self.maintain_boost_particles(renderer, scene_data); + self.upload_particles(renderer); + } else { + self.particles.clear(); + } + } + fn upload_particles(&mut self, renderer: &mut Renderer) { let all_cpu_instances = self .particles .iter() .map(|p| p.instance) .collect::>(); - // TODO: upload just the ones that were created and added to queue, not all of - // them. - self.instances = renderer + // TODO: optimise buffer writes + let gpu_instances = renderer .create_instances(&all_cpu_instances) .expect("Failed to upload particle instances to the GPU!"); + + self.instances = gpu_instances; } - fn maintain_body_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + fn maintain_body_particles(&mut self, scene_data: &SceneData) { let ecs = scene_data.state.ecs(); for (_i, (_entity, body, pos)) in ( &ecs.entities(), @@ -109,31 +113,21 @@ impl ParticleMgr { { match body { Body::Object(object::Body::CampfireLit) => { - self.maintain_campfirelit_particles(renderer, scene_data, pos) + self.maintain_campfirelit_particles(scene_data, pos) }, Body::Object(object::Body::BoltFire) => { - self.maintain_boltfire_particles(renderer, scene_data, pos) + self.maintain_boltfire_particles(scene_data, pos) }, Body::Object(object::Body::BoltFireBig) => { - self.maintain_boltfirebig_particles(renderer, scene_data, pos) + self.maintain_boltfirebig_particles(scene_data, pos) }, - Body::Object(object::Body::Bomb) => { - self.maintain_bomb_particles(renderer, scene_data, pos) - }, - // Body::Object(object::Body::Pouch) => { - // self.maintain_pouch_particles(renderer, scene_data, pos) - // }, + Body::Object(object::Body::Bomb) => self.maintain_bomb_particles(scene_data, pos), _ => {}, } } } - fn maintain_campfirelit_particles( - &mut self, - renderer: &mut Renderer, - scene_data: &SceneData, - pos: &Pos, - ) { + fn maintain_campfirelit_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); let now = Instant::now(); let mut rng = rand::thread_rng(); @@ -149,12 +143,7 @@ impl ParticleMgr { }); } - fn maintain_boltfire_particles( - &mut self, - renderer: &mut Renderer, - scene_data: &SceneData, - pos: &Pos, - ) { + fn maintain_boltfire_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); let now = Instant::now(); let mut rng = rand::thread_rng(); @@ -170,12 +159,7 @@ impl ParticleMgr { }); } - fn maintain_boltfirebig_particles( - &mut self, - renderer: &mut Renderer, - scene_data: &SceneData, - pos: &Pos, - ) { + fn maintain_boltfirebig_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); let now = Instant::now(); let mut rng = rand::thread_rng(); @@ -205,12 +189,7 @@ impl ParticleMgr { }); } - fn maintain_bomb_particles( - &mut self, - renderer: &mut Renderer, - scene_data: &SceneData, - pos: &Pos, - ) { + fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); let now = Instant::now(); let mut rng = rand::thread_rng(); @@ -244,34 +223,7 @@ impl ParticleMgr { }); } - // fn maintain_pouch_particles( - // &mut self, - // renderer: &mut Renderer, - // scene_data: &SceneData, - // pos: &Pos, - // ) { - // let time = scene_data.state.get_time(); - // let now = Instant::now(); - // let mut rng = rand::thread_rng(); - - // let smoke_cpu_insts = vec![ParticleInstance::new( - // time, - // rng.gen(), - // ParticleMode::CampfireSmoke, - // pos.0, - // )]; - - // let smoke_cpu_insts = renderer - // .create_instances(&smoke_cpu_insts) - // .expect("Failed to upload particle instances to the GPU!"); - - // self.particles.push(Particles { - // alive_until: now + Duration::from_secs(1), - // instances: smoke_cpu_insts, - // }); - // } - - fn maintain_boost_particles(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { + fn maintain_boost_particles(&mut self, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); let time = state.get_time(); @@ -303,15 +255,18 @@ impl ParticleMgr { pub fn render( &self, renderer: &mut Renderer, + scene_data: &SceneData, globals: &Consts, lights: &Consts, shadows: &Consts, ) { - let model = &self - .model_cache - .get(MODEL_KEY) - .expect("Expected particle model in cache"); + if scene_data.particles_enabled { + let model = &self + .model_cache + .get(MODEL_KEY) + .expect("Expected particle model in cache"); - renderer.render_particles(model, globals, &self.instances, lights, shadows); + renderer.render_particles(model, globals, &self.instances, lights, shadows); + } } } diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index fb620aa0f1..c89ebc6511 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -190,7 +190,6 @@ impl Scene { scene_data.gamma, self.camera.get_mode(), 250.0, - 250.0, )]) { error!(?e, "Renderer failed to update"); } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 86ea35b27a..9c5122cee8 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -788,11 +788,6 @@ impl PlayState for SessionState { sprite_render_distance; global_state.settings.save_to_file_warn(); }, - HudEvent::AdjustParticleRenderDistance(particle_render_distance) => { - global_state.settings.graphics.particle_render_distance = - particle_render_distance; - global_state.settings.save_to_file_warn(); - }, HudEvent::AdjustFigureLoDRenderDistance(figure_lod_render_distance) => { global_state.settings.graphics.figure_lod_render_distance = figure_lod_render_distance; @@ -942,6 +937,10 @@ impl PlayState for SessionState { self.voxygen_i18n.log_missing_entries(); self.hud.update_language(self.voxygen_i18n.clone()); }, + HudEvent::ToggleParticlesEnabled(particles_enabled) => { + global_state.settings.graphics.particles_enabled = particles_enabled; + global_state.settings.save_to_file_warn(); + }, HudEvent::ToggleFullscreen => { global_state .window @@ -1006,11 +1005,7 @@ impl PlayState for SessionState { mouse_smoothing: global_state.settings.gameplay.smooth_pan_enable, sprite_render_distance: global_state.settings.graphics.sprite_render_distance as f32, - particle_render_distance: global_state - .settings - .graphics - .particle_render_distance - as f32, + particles_enabled: global_state.settings.graphics.particles_enabled, figure_lod_render_distance: global_state .settings .graphics @@ -1066,7 +1061,7 @@ impl PlayState for SessionState { 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, - particle_render_distance: settings.graphics.particle_render_distance as f32, + particles_enabled: settings.graphics.particles_enabled, is_aiming: self.is_aiming, }; self.scene.render( diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index ef1184c8ee..722b077ae7 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -603,7 +603,7 @@ impl Default for Log { pub struct GraphicsSettings { pub view_distance: u32, pub sprite_render_distance: u32, - pub particle_render_distance: u32, + pub particles_enabled: bool, pub figure_lod_render_distance: u32, pub max_fps: u32, pub fov: u16, @@ -620,7 +620,7 @@ impl Default for GraphicsSettings { Self { view_distance: 10, sprite_render_distance: 150, - particle_render_distance: 150, + particles_enabled: true, figure_lod_render_distance: 250, max_fps: 60, fov: 50, From 5ba4d2682139098b47dbc2dc450d4ff64edef239 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 25 Jul 2020 23:56:50 +0800 Subject: [PATCH 11/28] Update changelog --- CHANGELOG.md | 1 + server/src/sys/sentinel.rs | 3 +- voxygen/src/render/mod.rs | 2 +- voxygen/src/render/renderer.rs | 26 ++++++++------- voxygen/src/scene/particle.rs | 60 +++++++++++++++++++--------------- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c03261b87..078d5790e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Power stat to weapons which affects weapon damage - Add detection of entities under the cursor - Functional group-system with exp-sharing and disabled damage to group members +- Some Campfire, fireball & bomb; particle, light & sound effects. ### Changed diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 6121dbb4a9..8bda2339e8 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -2,8 +2,7 @@ use super::SysTimer; use common::{ comp::{ Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, - Loadout, Mass, MountState, Mounting, Ori, Player, Pos, - Scale, Stats, Sticky, Vel, + Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, }, msg::EcsCompPacket, sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, Uid, UpdateTracker, WorldSyncExt}, diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 408251d9ad..317ba97db6 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -18,11 +18,11 @@ pub use self::{ pipelines::{ figure::{BoneData as FigureBoneData, FigurePipeline, Locals as FigureLocals}, fluid::FluidPipeline, + particle::{Instance as ParticleInstance, ParticlePipeline}, postprocess::{ create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline, }, skybox::{create_mesh as create_skybox_mesh, Locals as SkyboxLocals, SkyboxPipeline}, - particle::{Instance as ParticleInstance, ParticlePipeline}, sprite::{Instance as SpriteInstance, SpritePipeline}, terrain::{Locals as TerrainLocals, TerrainPipeline}, ui::{ diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 80bdb65ba5..1fa5cc6f2c 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -4,7 +4,9 @@ use super::{ instances::Instances, mesh::Mesh, model::{DynamicModel, Model}, - pipelines::{figure, fluid, postprocess, skybox, sprite, particle, terrain, ui, Globals, Light, Shadow}, + pipelines::{ + figure, fluid, particle, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow, + }, texture::Texture, AaMode, CloudMode, FluidMode, Pipeline, RenderError, }; @@ -951,17 +953,17 @@ fn create_pipelines( gfx::state::CullFace::Back, )?; - // Construct a pipeline for rendering particles - let particle_pipeline = create_pipeline( - factory, - particle::pipe::new(), - &assets::load_watched::("voxygen.shaders.particle-vert", shader_reload_indicator) - .unwrap(), - &assets::load_watched::("voxygen.shaders.particle-frag", shader_reload_indicator) - .unwrap(), - &include_ctx, - gfx::state::CullFace::Back, - )?; + // Construct a pipeline for rendering particles + let particle_pipeline = create_pipeline( + factory, + particle::pipe::new(), + &assets::load_watched::("voxygen.shaders.particle-vert", shader_reload_indicator) + .unwrap(), + &assets::load_watched::("voxygen.shaders.particle-frag", shader_reload_indicator) + .unwrap(), + &include_ctx, + gfx::state::CullFace::Back, + )?; // Construct a pipeline for rendering UI elements let ui_pipeline = create_pipeline( diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 3a72f92b94..abc3b0eb88 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -34,35 +34,10 @@ const MODEL_KEY: &str = "voxygen.voxel.particle"; impl ParticleMgr { pub fn new(renderer: &mut Renderer) -> Self { - let mut model_cache = HashMap::new(); - - model_cache.entry(MODEL_KEY).or_insert_with(|| { - let offset = Vec3::zero(); - let lod_scale = Vec3::one(); - - let vox = assets::load_expect::(MODEL_KEY); - - let mesh = &Meshable::::generate_mesh( - &Segment::from(vox.as_ref()), - (offset * lod_scale, Vec3::one() / lod_scale), - ) - .0; - - renderer - .create_model(mesh) - .expect("Failed to create particle model"); - }); - - let insts = Vec::new(); - - let instances = renderer - .create_instances(&insts) - .expect("Failed to upload particle instances to the GPU!"); - Self { particles: Vec::new(), - instances, - model_cache, + instances: default_instances(renderer), + model_cache: default_cache(renderer), } } @@ -270,3 +245,34 @@ impl ParticleMgr { } } } + +fn default_instances(renderer: &mut Renderer) -> Instances { + let empty_vec = Vec::new(); + + renderer + .create_instances(&empty_vec) + .expect("Failed to upload particle instances to the GPU!") +} + +fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model> { + let mut model_cache = HashMap::new(); + + model_cache.entry(MODEL_KEY).or_insert_with(|| { + let offset = Vec3::zero(); + let lod_scale = Vec3::one(); + + let vox = assets::load_expect::(MODEL_KEY); + + let mesh = &Meshable::::generate_mesh( + &Segment::from(vox.as_ref()), + (offset * lod_scale, Vec3::one() / lod_scale), + ) + .0; + + renderer + .create_model(mesh) + .expect("Failed to create particle model") + }); + + model_cache +} From 82a0ecad6b4d741034cc41c8938104fee2cbeac5 Mon Sep 17 00:00:00 2001 From: scott-c Date: Fri, 31 Jul 2020 17:32:13 +0800 Subject: [PATCH 12/28] Add zest's changes --- assets/voxygen/shaders/particle-vert.glsl | 92 ++++++++++++++++------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 54fac86749..e9705d5505 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -2,6 +2,7 @@ #include #include +#include in vec3 v_pos; in uint v_col; @@ -43,49 +44,86 @@ mat4 translate(vec3 vec){ ); } +struct Attr { + vec3 offs; + float scale; + vec3 col; +}; + +float lifetime = tick.x - inst_time; + +vec3 linear_motion(vec3 init_offs, vec3 vel) { + return init_offs + vel * lifetime; +} + +vec3 grav_vel(float grav) { + return vec3(0, 0, -grav * lifetime); +} + +float exp_scale(float factor) { + return 1 / (1 - lifetime * factor); +} + void main() { mat4 inst_mat = translate(inst_pos); - float rand1 = gold_noise(vec2(0.0, 0.0), inst_entropy); - float rand2 = gold_noise(vec2(10.0, 10.0), inst_entropy); - float rand3 = gold_noise(vec2(20.0, 20.0), inst_entropy); - float rand4 = gold_noise(vec2(30.0, 30.0), inst_entropy); - float rand5 = gold_noise(vec2(40.0, 40.0), inst_entropy); - float rand6 = gold_noise(vec2(50.0, 50.0), inst_entropy); + float rand0 = hash(vec4(inst_entropy + 0)); + float rand1 = hash(vec4(inst_entropy + 1)); + float rand2 = hash(vec4(inst_entropy + 2)); + float rand3 = hash(vec4(inst_entropy + 3)); + float rand4 = hash(vec4(inst_entropy + 4)); + float rand5 = hash(vec4(inst_entropy + 5)); + float rand6 = hash(vec4(inst_entropy + 6)); + float rand7 = hash(vec4(inst_entropy + 7)); - vec3 inst_vel = vec3(0.0, 0.0, 0.0); - vec3 inst_pos2 = vec3(0.0, 0.0, 0.0); - vec3 inst_col = vec3(1.0, 1.0, 1.0); + Attr attr; if (inst_mode == SMOKE) { - inst_col = vec3(1.0, 1.0, 1.0); - inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 1.0 + rand3); - inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + attr = Attr( + linear_motion( + vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5), + vec3(rand2 * 0.2, rand3 * 0.2, 1.0 + rand4 * 0.5)// + vec3(sin(lifetime), sin(lifetime + 1.5), sin(lifetime * 4) * 0.25) + ), + exp_scale(-0.2), + vec3(1) + ); } else if (inst_mode == FIRE) { - inst_col = vec3(1.0, 1.0 * inst_entropy, 0.0); - inst_vel = vec3(rand1 * 0.2 - 0.1, rand2 * 0.2 - 0.1, 4.0 + rand3); - inst_pos2 = vec3(rand4 * 5.0 - 2.5, rand5 * 5.0 - 2.5, 0.0); + attr = Attr( + linear_motion( + vec3(rand0 * 0.25, rand1 * 0.25, 0.3), + vec3(rand2 * 0.1, rand3 * 0.1, 2.0 + rand4 * 1.0) + ), + 1.0, + vec3(2, rand5 + 2, 0) + ); } else if (inst_mode == GUN_POWDER_SPARK) { - inst_col = vec3(1.0, 1.0, 0.0); - inst_vel = vec3(rand2 * 2.0 - 1.0, rand1 * 2.0 - 1.0, 5.0 + rand3); - inst_vel -= vec3(0.0, 0.0, earth_gravity * (tick.x - inst_time)); - inst_pos2 = vec3(0.0, 0.0, 0.0); + attr = Attr( + linear_motion( + vec3(rand0, rand1, rand3) * 0.3, + vec3(rand4, rand5, rand6) * 2.0 + grav_vel(earth_gravity) + ), + 1.0, + vec3(3.5, 3 + rand7, 0) + ); } else { - inst_col = vec3(rand1, rand2, rand3); - inst_vel = vec3(rand4, rand5, rand6); - inst_pos2 = vec3(rand1, rand2, rand3); + attr = Attr( + linear_motion( + vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5), + vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5) + ), + exp_scale(-0.2), + vec3(1) + ); } - f_pos = (inst_mat * vec4((v_pos + inst_pos2) * SCALE, 1)).xyz; - - f_pos += inst_vel * (tick.x - inst_time); + f_pos = (inst_mat * vec4(v_pos * attr.scale * SCALE + attr.offs, 1)).xyz; // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); f_norm = (inst_mat * vec4(normals[(v_norm_ao >> 0) & 0x7u], 0)).xyz; vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; - f_col = srgb_to_linear(col) * srgb_to_linear(inst_col); + f_col = srgb_to_linear(col) * srgb_to_linear(attr.col); f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; f_light = 1.0; @@ -94,4 +132,4 @@ void main() { all_mat * vec4(f_pos, 1); gl_Position.z = -1000.0 / (gl_Position.z + 10000.0); -} +} \ No newline at end of file From 1a263834e7bbeff7e22928b0eefa34ca11d6a6d9 Mon Sep 17 00:00:00 2001 From: scott-c Date: Fri, 31 Jul 2020 17:34:26 +0800 Subject: [PATCH 13/28] Add campfire command --- common/src/cmd.rs | 4 ++++ server/src/cmd.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 6d8ed3af46..e128901f46 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -38,6 +38,7 @@ pub enum ChatCommand { Adminify, Alias, Build, + Campfire, Debug, DebugColumn, Dummy, @@ -79,6 +80,7 @@ pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::Adminify, ChatCommand::Alias, ChatCommand::Build, + ChatCommand::Campfire, ChatCommand::Debug, ChatCommand::DebugColumn, ChatCommand::Dummy, @@ -185,6 +187,7 @@ impl ChatCommand { ), ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin), ChatCommand::Build => cmd(vec![], "Toggles build mode on and off", Admin), + ChatCommand::Campfire => cmd(vec![], "Spawns a campfire", Admin), ChatCommand::Debug => cmd(vec![], "Place all debug items into your pack.", Admin), ChatCommand::DebugColumn => cmd( vec![Integer("x", 15000, Required), Integer("y", 15000, Required)], @@ -365,6 +368,7 @@ impl ChatCommand { ChatCommand::Adminify => "adminify", ChatCommand::Alias => "alias", ChatCommand::Build => "build", + ChatCommand::Campfire => "campfire", ChatCommand::Debug => "debug", ChatCommand::DebugColumn => "debug_column", ChatCommand::Dummy => "dummy", diff --git a/server/src/cmd.rs b/server/src/cmd.rs index b580cfbd98..3d8d3bef72 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -65,6 +65,7 @@ fn get_handler(cmd: &ChatCommand) -> CommandHandler { ChatCommand::Adminify => handle_adminify, ChatCommand::Alias => handle_alias, ChatCommand::Build => handle_build, + ChatCommand::Campfire => handle_spawn_campfire, ChatCommand::Debug => handle_debug, ChatCommand::DebugColumn => handle_debug_column, ChatCommand::Dummy => handle_spawn_training_dummy, @@ -664,6 +665,47 @@ fn handle_spawn_training_dummy( } } +fn handle_spawn_campfire( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + _args: String, + _action: &ChatCommand, +) { + match server.state.read_component_cloned::(target) { + Some(pos) => { + let vel = Vec3::new( + rand::thread_rng().gen_range(-2.0, 3.0), + rand::thread_rng().gen_range(-2.0, 3.0), + 10.0, + ); + + let body = comp::Body::Object(comp::object::Body::CampfireLit); + + let mut stats = comp::Stats::new("Campfire".to_string(), body); + + // Level 0 will prevent exp gain from kill + stats.level.set_level(0); + + server + .state + .create_npc(pos, stats, comp::Loadout::default(), body) + .with(comp::Vel(vel)) + .with(comp::MountState::Unmounted) + .build(); + + server.notify_client( + client, + ChatType::CommandInfo.server_msg("Spawned a campfire"), + ); + }, + None => server.notify_client( + client, + ChatType::CommandError.server_msg("You have no position!"), + ), + } +} + fn handle_players( server: &mut Server, client: EcsEntity, From 3f7667352dba865b74ac6cb2143642b2e12360ee Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 31 Jul 2020 15:00:58 +0100 Subject: [PATCH 14/28] Better hash RNG --- assets/voxygen/shaders/include/random.glsl | 2 +- assets/voxygen/shaders/include/sky.glsl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/voxygen/shaders/include/random.glsl b/assets/voxygen/shaders/include/random.glsl index 240d17e50a..7d217d858e 100644 --- a/assets/voxygen/shaders/include/random.glsl +++ b/assets/voxygen/shaders/include/random.glsl @@ -1,5 +1,5 @@ float hash(vec4 p) { - p = fract(p * 0.3183099 + 0.1); + p = fract(p * 0.3183099 + 0.1) - fract(p + 23.22121); p *= 17.0; return (fract(p.x * p.y * p.z * p.w * (p.x + p.y + p.z + p.w)) - 0.5) * 2.0; } diff --git a/assets/voxygen/shaders/include/sky.glsl b/assets/voxygen/shaders/include/sky.glsl index 6fd726230b..fe12499086 100644 --- a/assets/voxygen/shaders/include/sky.glsl +++ b/assets/voxygen/shaders/include/sky.glsl @@ -94,10 +94,10 @@ float is_star_at(vec3 dir) { vec3 pos = (floor(dir * star_scale) - 0.5) / star_scale; // Noisy offsets - pos += (3.0 / star_scale) * rand_perm_3(pos); + pos += (3.0 / star_scale) * (1.0 + hash(pos.yxzz) * 0.85); // Find distance to fragment - float dist = length(normalize(pos) - dir); + float dist = length(pos - dir); // Star threshold if (dist < 0.0015) { From 7c31baef6f767794d0f8b1663e89a7386cc426f7 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 31 Jul 2020 18:16:20 +0100 Subject: [PATCH 15/28] Added outcome system, sound effects --- assets/voxygen/shaders/particle-vert.glsl | 2 +- client/src/lib.rs | 5 ++++ common/src/lib.rs | 1 + common/src/msg/server.rs | 2 ++ common/src/outcome.rs | 30 +++++++++++++++++++++++ server/src/events/entity_creation.rs | 8 +++++- server/src/events/entity_manipulation.rs | 24 ++++++------------ server/src/lib.rs | 2 ++ server/src/sys/entity_sync.rs | 26 +++++++++++++++++++- voxygen/src/audio/sfx/mod.rs | 13 ++++++++++ voxygen/src/session.rs | 11 ++++++++- 11 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 common/src/outcome.rs diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index e9705d5505..70580be584 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -132,4 +132,4 @@ void main() { all_mat * vec4(f_pos, 1); gl_Position.z = -1000.0 / (gl_Position.z + 10000.0); -} \ No newline at end of file +} diff --git a/client/src/lib.rs b/client/src/lib.rs index 404c2a1686..08109865db 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -30,6 +30,7 @@ use common::{ sync::{Uid, UidAllocator, WorldSyncExt}, terrain::{block::Block, TerrainChunk, TerrainChunkSize}, vol::RectVolSize, + outcome::Outcome, }; use futures_executor::block_on; use futures_timer::Delay; @@ -66,6 +67,7 @@ pub enum Event { InventoryUpdated(InventoryUpdateEvent), Notification(Notification), SetViewDistance(u32), + Outcome(Outcome), } pub struct Client { @@ -1229,6 +1231,9 @@ impl Client { self.view_distance = Some(vd); frontend_events.push(Event::SetViewDistance(vd)); }, + ServerMsg::Outcomes(outcomes) => frontend_events.extend(outcomes + .into_iter() + .map(Event::Outcome)), } } } diff --git a/common/src/lib.rs b/common/src/lib.rs index fb04ff28b1..3bddacde79 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -24,6 +24,7 @@ pub mod generation; pub mod loadout_builder; pub mod msg; pub mod npc; +pub mod outcome; pub mod path; pub mod ray; pub mod recipe; diff --git a/common/src/msg/server.rs b/common/src/msg/server.rs index 156c18fd61..d6d55e46e8 100644 --- a/common/src/msg/server.rs +++ b/common/src/msg/server.rs @@ -2,6 +2,7 @@ use super::{ClientState, EcsCompPacket}; use crate::{ character::CharacterItem, comp, + outcome::Outcome, recipe::RecipeBook, state, sync, sync::Uid, @@ -120,6 +121,7 @@ pub enum ServerMsg { /// Send a popup notification such as "Waypoint Saved" Notification(Notification), SetViewDistance(u32), + Outcomes(Vec), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/common/src/outcome.rs b/common/src/outcome.rs new file mode 100644 index 0000000000..9f047bcc26 --- /dev/null +++ b/common/src/outcome.rs @@ -0,0 +1,30 @@ +use crate::comp; +use serde::{Deserialize, Serialize}; +use vek::*; + +/// An outcome represents the final result of an instantaneous event. It implies that said event has +/// already occurred. It is not a request for that event to occur, nor is it something that may be +/// cancelled or otherwise altered. Its primary purpose is to act as something for frontends (both +/// server and client) to listen to in order to receive feedback about events in the world. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Outcome { + Explosion { + pos: Vec3, + power: f32, + }, + ProjectileShot { + pos: Vec3, + body: comp::Body, + vel: Vec3, + }, +} + + +impl Outcome { + pub fn get_pos(&self) -> Option> { + match self { + Outcome::Explosion { pos, .. } => Some(*pos), + Outcome::ProjectileShot { pos, .. } => Some(*pos), + } + } +} diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index a62c0b342c..0d9a8ff61a 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -5,6 +5,7 @@ use common::{ Projectile, Scale, Stats, Vel, WaypointArea, }, util::Dir, + outcome::Outcome, }; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; @@ -89,10 +90,15 @@ pub fn handle_shoot( .expect("Failed to fetch entity") .0; + let vel = *dir * 100.0; + + // Add an outcome + state.ecs().write_resource::>().push(Outcome::ProjectileShot { pos, body, vel }); + // TODO: Player height pos.z += 1.2; - let mut builder = state.create_projectile(Pos(pos), Vel(*dir * 100.0), body, projectile); + let mut builder = state.create_projectile(Pos(pos), Vel(vel), body, projectile); if let Some(light) = light { builder = builder.with(light) } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 0a73123a28..d87e61ea08 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -6,6 +6,7 @@ use common::{ HealthChange, HealthSource, Player, Pos, Stats, }, msg::{PlayerListUpdate, ServerMsg}, + outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, sys::combat::BLOCK_ANGLE, @@ -277,25 +278,16 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) { } } -pub fn handle_explosion( - server: &Server, - pos: Vec3, - power: f32, - owner: Option, - friendly_damage: bool, -) { - // Go through all other entities - let hit_range = 3.0 * power; +pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Option) { let ecs = &server.state.ecs(); - let owner_entity = owner.and_then(|uid| { - ecs.read_resource::() - .retrieve_entity_internal(uid.into()) - }); - let groups = ecs.read_storage::(); + // Add an outcome + ecs.write_resource::>() + .push(Outcome::Explosion { pos, power }); - for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in ( - &ecs.entities(), + // Go through all other entities + let hit_range = 3.0 * power; + for (pos_b, ori_b, character_b, stats_b, loadout_b) in ( &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), diff --git a/server/src/lib.rs b/server/src/lib.rs index 7d27b933ab..1447793c98 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -34,6 +34,7 @@ use common::{ comp::{self, ChatType}, event::{EventBus, ServerEvent}, msg::{ClientState, ServerInfo, ServerMsg}, + outcome::Outcome, recipe::default_recipe_book, state::{State, TimeOfDay}, sync::WorldSyncExt, @@ -118,6 +119,7 @@ impl Server { state .ecs_mut() .insert(comp::AdminList(settings.admins.clone())); + state.ecs_mut().insert(Vec::::new()); // System timers for performance monitoring state.ecs_mut().insert(sys::EntitySyncTimer::default()); diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index d4bbf62d20..6e4a5fa897 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -7,15 +7,19 @@ use crate::{ Tick, }; use common::{ - comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel}, + comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel, Player}, msg::ServerMsg, + outcome::Outcome, region::{Event as RegionEvent, RegionMap}, state::TimeOfDay, sync::{CompSyncPackage, Uid}, + vol::RectVolSize, + terrain::TerrainChunkSize, }; use specs::{ Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, }; +use vek::*; /// This system will send physics updates to the client pub struct Sys; @@ -33,6 +37,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Ori>, ReadStorage<'a, Inventory>, ReadStorage<'a, RegionSubscription>, + ReadStorage<'a, Player>, WriteStorage<'a, Last>, WriteStorage<'a, Last>, WriteStorage<'a, Last>, @@ -40,6 +45,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, ForceUpdate>, WriteStorage<'a, InventoryUpdate>, Write<'a, DeletedEntities>, + Write<'a, Vec>, TrackedComps<'a>, ReadTrackers<'a>, ); @@ -58,6 +64,7 @@ impl<'a> System<'a> for Sys { orientations, inventories, subscriptions, + players, mut last_pos, mut last_vel, mut last_ori, @@ -65,6 +72,7 @@ impl<'a> System<'a> for Sys { mut force_updates, mut inventory_updates, mut deleted_entities, + mut outcomes, tracked_comps, trackers, ): Self::SystemData, @@ -316,6 +324,22 @@ impl<'a> System<'a> for Sys { )); } + // Sync outcomes + for (client, player, pos) in (&mut clients, &players, positions.maybe()).join() { + let is_near = |o_pos: Vec3| pos + .zip_with(player.view_distance, |pos, vd| pos.0.xy().distance_squared(o_pos.xy()) < (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0)); + + let outcomes = outcomes + .iter() + .filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true)) + .cloned() + .collect::>(); + if outcomes.len() > 0 { + client.notify(ServerMsg::Outcomes(outcomes)); + } + } + outcomes.clear(); + // Remove all force flags. force_updates.clear(); inventory_updates.clear(); diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index b76c287cdb..669065f1cc 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -93,6 +93,7 @@ use common::{ }, event::EventBus, state::State, + outcome::Outcome, }; use event_mapper::SfxEventMapper; use hashbrown::HashMap; @@ -188,6 +189,18 @@ impl From<&InventoryUpdateEvent> for SfxEvent { } } +impl TryFrom for SfxEventItem { + type Error = (); + + fn try_from(outcome: Outcome) -> Result { + match outcome { + Outcome::Explosion { pos, power } => Ok(Self::new(SfxEvent::GliderOpen, Some(pos), Some((power / 10.0).min(1.0)))), + Outcome::ProjectileShot { pos, .. } => Ok(Self::new(SfxEvent::GliderOpen, Some(pos), None)), + _ => Err(()), + } + } +} + #[derive(Deserialize)] pub struct SfxTriggerItem { pub files: Vec, diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 9c5122cee8..abf55456bc 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -26,7 +26,7 @@ use common::{ vol::ReadVol, }; use specs::{Join, WorldExt}; -use std::{cell::RefCell, rc::Rc, time::Duration}; +use std::{cell::RefCell, rc::Rc, time::Duration, convert::TryFrom}; use tracing::{error, info}; use vek::*; @@ -158,6 +158,15 @@ impl SessionState { global_state.settings.graphics.view_distance = vd; global_state.settings.save_to_file_warn(); }, + client::Event::Outcome(outcome) => { + if let Ok(sfx_event_item) = SfxEventItem::try_from(outcome) { + client + .state() + .ecs() + .read_resource::>() + .emit_now(sfx_event_item); + } + }, } } From 8547cdd6810f9d912f454fa339e86f4831244f61 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 31 Jul 2020 23:25:23 +0100 Subject: [PATCH 16/28] Added outcome sound effects, fixed directional sound, particle outcomes --- assets/voxygen/audio/sfx.ron | 14 ++++- voxygen/src/audio/channel.rs | 15 +++-- voxygen/src/audio/mod.rs | 59 +++++++------------ .../src/audio/sfx/event_mapper/combat/mod.rs | 15 +++-- voxygen/src/audio/sfx/event_mapper/mod.rs | 6 +- .../audio/sfx/event_mapper/movement/mod.rs | 14 ++--- .../audio/sfx/event_mapper/progression/mod.rs | 7 ++- voxygen/src/audio/sfx/mod.rs | 41 ++++++------- voxygen/src/scene/camera.rs | 4 ++ voxygen/src/scene/mod.rs | 8 ++- voxygen/src/scene/particle.rs | 21 ++++++- voxygen/src/scene/simple.rs | 1 + voxygen/src/session.rs | 33 ++++++----- 13 files changed, 136 insertions(+), 102 deletions(-) diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index c2725ba3cd..85ff1b92af 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -106,6 +106,18 @@ "voxygen.audio.sfx.inventory.consumable.food", ], threshold: 0.3, - ) + ), + Explosion: ( + files: [ + "voxygen.audio.sfx.glider_open", + ], + threshold: 0.5, + ), + ProjectileShot: ( + files: [ + "voxygen.audio.sfx.glider_open", + ], + threshold: 0.5, + ), } ) diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs index 8a88923f95..6662e0efd0 100644 --- a/voxygen/src/audio/channel.rs +++ b/voxygen/src/audio/channel.rs @@ -16,7 +16,10 @@ //! the channel capacity has been reached and all channels are occupied, a //! warning is logged, and no sound is played. -use crate::audio::fader::{FadeDirection, Fader}; +use crate::audio::{ + fader::{FadeDirection, Fader}, + Listener, +}; use rodio::{Device, Sample, Sink, Source, SpatialSink}; use vek::*; @@ -163,11 +166,13 @@ impl SfxChannel { pub fn is_done(&self) -> bool { self.sink.empty() } - pub fn set_emitter_position(&mut self, pos: [f32; 3]) { self.sink.set_emitter_position(pos); } + pub fn set_pos(&mut self, pos: Vec3) { self.pos = pos; } - pub fn set_left_ear_position(&mut self, pos: [f32; 3]) { self.sink.set_left_ear_position(pos); } + pub fn update(&mut self, listener: &Listener) { + const FALLOFF: f32 = 0.13; - pub fn set_right_ear_position(&mut self, pos: [f32; 3]) { - self.sink.set_right_ear_position(pos); + self.sink.set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array()); + self.sink.set_left_ear_position(listener.ear_left_rpos.into_array()); + self.sink.set_right_ear_position(listener.ear_right_rpos.into_array()); } } diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 9c7b022dc4..5af08f8dc6 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -16,7 +16,14 @@ use cpal::traits::DeviceTrait; use rodio::{source::Source, Decoder, Device}; use vek::*; -const FALLOFF: f32 = 0.13; +#[derive(Default, Clone)] +pub struct Listener { + pos: Vec3, + ori: Vec3, + + ear_left_rpos: Vec3, + ear_right_rpos: Vec3, +} /// Holds information about the system audio devices and internal channels used /// for sfx and music playback. An instance of `AudioFrontend` is used by @@ -34,11 +41,7 @@ pub struct AudioFrontend { sfx_volume: f32, music_volume: f32, - listener_pos: Vec3, - listener_ori: Vec3, - - listener_ear_left: Vec3, - listener_ear_right: Vec3, + listener: Listener, } impl AudioFrontend { @@ -63,10 +66,8 @@ impl AudioFrontend { sfx_channels, sfx_volume: 1.0, music_volume: 1.0, - listener_pos: Vec3::zero(), - listener_ori: Vec3::zero(), - listener_ear_left: Vec3::zero(), - listener_ear_right: Vec3::zero(), + + listener: Listener::default(), } } @@ -81,10 +82,7 @@ impl AudioFrontend { sfx_channels: Vec::new(), sfx_volume: 1.0, music_volume: 1.0, - listener_pos: Vec3::zero(), - listener_ori: Vec3::zero(), - listener_ear_left: Vec3::zero(), - listener_ear_right: Vec3::zero(), + listener: Listener::default(), } } @@ -146,20 +144,15 @@ impl AudioFrontend { /// Play (once) an sfx file by file path at the give position and volume pub fn play_sfx(&mut self, sound: &str, pos: Vec3, vol: Option) { if self.audio_device.is_some() { - let calc_pos = ((pos - self.listener_pos) * FALLOFF).into_array(); - let sound = self .sound_cache .load_sound(sound) .amplify(vol.unwrap_or(1.0)); - let left_ear = self.listener_ear_left.into_array(); - let right_ear = self.listener_ear_right.into_array(); - + let listener = self.listener.clone(); if let Some(channel) = self.get_sfx_channel() { - channel.set_emitter_position(calc_pos); - channel.set_left_ear_position(left_ear); - channel.set_right_ear_position(right_ear); + channel.set_pos(pos); + channel.update(&listener); channel.play(sound); } } @@ -174,27 +167,17 @@ impl AudioFrontend { } } - pub fn set_listener_pos(&mut self, pos: &Vec3, ori: &Vec3) { - self.listener_pos = *pos; - self.listener_ori = ori.normalized(); + pub fn set_listener_pos(&mut self, pos: Vec3, ori: Vec3) { + self.listener.pos = pos; + self.listener.ori = ori.normalized(); let up = Vec3::new(0.0, 0.0, 1.0); - - let pos_left = up.cross(self.listener_ori).normalized(); - let pos_right = self.listener_ori.cross(up).normalized(); - - self.listener_ear_left = pos_left; - self.listener_ear_right = pos_right; + self.listener.ear_left_rpos = up.cross(self.listener.ori).normalized(); + self.listener.ear_right_rpos = -up.cross(self.listener.ori).normalized(); for channel in self.sfx_channels.iter_mut() { if !channel.is_done() { - // TODO: Update this to correctly determine the updated relative position of - // the SFX emitter when the player (listener) moves - // channel.set_emitter_position( - // ((channel.pos - self.listener_pos) * FALLOFF).into_array(), - // ); - channel.set_left_ear_position(pos_left.into_array()); - channel.set_right_ear_position(pos_right.into_array()); + channel.update(&self.listener); } } } diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs index eeeafca25d..5a81800930 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs @@ -1,6 +1,9 @@ /// EventMapper::Combat watches the combat states of surrounding entities' and /// emits sfx related to weapons and attacks/abilities -use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}; +use crate::{ + audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}, + scene::Camera, +}; use super::EventMapper; @@ -15,7 +18,6 @@ use common::{ use hashbrown::HashMap; use specs::{Entity as EcsEntity, Join, WorldExt}; use std::time::{Duration, Instant}; -use vek::*; #[derive(Clone)] struct PreviousEntityState { @@ -39,16 +41,13 @@ pub struct CombatEventMapper { } impl EventMapper for CombatEventMapper { - fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) { + fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers) { let ecs = state.ecs(); let sfx_event_bus = ecs.read_resource::>(); let mut sfx_emitter = sfx_event_bus.emitter(); - let player_position = ecs - .read_storage::() - .get(player_entity) - .map_or(Vec3::zero(), |pos| pos.0); + let cam_pos = camera.dependents().cam_pos; for (entity, pos, loadout, character) in ( &ecs.entities(), @@ -58,7 +57,7 @@ impl EventMapper for CombatEventMapper { ) .join() .filter(|(_, e_pos, ..)| { - (e_pos.0.distance_squared(player_position)) < SFX_DIST_LIMIT_SQR + (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR }) { if let Some(character) = character { diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs index 1628185479..4ac331fa86 100644 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/mod.rs @@ -8,10 +8,11 @@ use combat::CombatEventMapper; use movement::MovementEventMapper; use progression::ProgressionEventMapper; +use crate::scene::Camera; use super::SfxTriggers; trait EventMapper { - fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers); + fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers); } pub struct SfxEventMapper { @@ -33,10 +34,11 @@ impl SfxEventMapper { &mut self, state: &State, player_entity: specs::Entity, + camera: &Camera, triggers: &SfxTriggers, ) { for mapper in &mut self.mappers { - mapper.maintain(state, player_entity, triggers); + mapper.maintain(state, player_entity, camera, triggers); } } } diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index a884066274..da6d29edc7 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -2,7 +2,10 @@ /// and triggers sfx related to running, climbing and gliding, at a volume /// proportionate to the extity's size use super::EventMapper; -use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}; +use crate::{ + audio::sfx::{SfxEvent, SfxEventItem, SfxTriggerItem, SfxTriggers, SFX_DIST_LIMIT_SQR}, + scene::Camera, +}; use common::{ comp::{Body, CharacterState, PhysicsState, Pos, Vel}, event::EventBus, @@ -35,16 +38,13 @@ pub struct MovementEventMapper { } impl EventMapper for MovementEventMapper { - fn maintain(&mut self, state: &State, player_entity: EcsEntity, triggers: &SfxTriggers) { + fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers) { let ecs = state.ecs(); let sfx_event_bus = ecs.read_resource::>(); let mut sfx_emitter = sfx_event_bus.emitter(); - let player_position = ecs - .read_storage::() - .get(player_entity) - .map_or(Vec3::zero(), |pos| pos.0); + let cam_pos = camera.dependents().cam_pos; for (entity, pos, vel, body, physics, character) in ( &ecs.entities(), @@ -56,7 +56,7 @@ impl EventMapper for MovementEventMapper { ) .join() .filter(|(_, e_pos, ..)| { - (e_pos.0.distance_squared(player_position)) < SFX_DIST_LIMIT_SQR + (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR }) { if let Some(character) = character { diff --git a/voxygen/src/audio/sfx/event_mapper/progression/mod.rs b/voxygen/src/audio/sfx/event_mapper/progression/mod.rs index 0b845e03a9..64c21b5303 100644 --- a/voxygen/src/audio/sfx/event_mapper/progression/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/progression/mod.rs @@ -2,7 +2,10 @@ /// and triggers sfx for gaining experience and levelling up use super::EventMapper; -use crate::audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers}; +use crate::{ + audio::sfx::{SfxEvent, SfxEventItem, SfxTriggers}, + scene::Camera, +}; use common::{comp::Stats, event::EventBus, state::State}; use specs::WorldExt; @@ -23,7 +26,7 @@ pub struct ProgressionEventMapper { impl EventMapper for ProgressionEventMapper { #[allow(clippy::op_ref)] // TODO: Pending review in #587 - fn maintain(&mut self, state: &State, player_entity: specs::Entity, triggers: &SfxTriggers) { + fn maintain(&mut self, state: &State, player_entity: specs::Entity, _camera: &Camera, triggers: &SfxTriggers) { let ecs = state.ecs(); let next_state = ecs.read_storage::().get(player_entity).map_or( diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 669065f1cc..d2bb328494 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -83,17 +83,17 @@ mod event_mapper; -use crate::audio::AudioFrontend; +use crate::{audio::AudioFrontend, scene::Camera}; use common::{ assets, comp::{ - item::{ItemKind, ToolCategory}, + item::{Consumable, ItemKind, ToolCategory}, CharacterAbilityType, InventoryUpdateEvent, Ori, Pos, }, event::EventBus, - state::State, outcome::Outcome, + state::State, }; use event_mapper::SfxEventMapper; use hashbrown::HashMap; @@ -147,6 +147,8 @@ pub enum SfxEvent { Wield(ToolCategory), Unwield(ToolCategory), Inventory(SfxInventoryEvent), + Explosion, + ProjectileShot, } #[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)] @@ -189,13 +191,19 @@ impl From<&InventoryUpdateEvent> for SfxEvent { } } -impl TryFrom for SfxEventItem { +impl<'a> TryFrom<&'a Outcome> for SfxEventItem { type Error = (); - fn try_from(outcome: Outcome) -> Result { + fn try_from(outcome: &'a Outcome) -> Result { match outcome { - Outcome::Explosion { pos, power } => Ok(Self::new(SfxEvent::GliderOpen, Some(pos), Some((power / 10.0).min(1.0)))), - Outcome::ProjectileShot { pos, .. } => Ok(Self::new(SfxEvent::GliderOpen, Some(pos), None)), + Outcome::Explosion { pos, power } => Ok(Self::new( + SfxEvent::Explosion, + Some(*pos), + Some((*power / 10.0).min(1.0)), + )), + Outcome::ProjectileShot { pos, .. } => { + Ok(Self::new(SfxEvent::ProjectileShot, Some(*pos), None)) + }, _ => Err(()), } } @@ -237,36 +245,25 @@ impl SfxMgr { audio: &mut AudioFrontend, state: &State, player_entity: specs::Entity, + camera: &Camera, ) { if !audio.sfx_enabled() { return; } self.event_mapper - .maintain(state, player_entity, &self.triggers); + .maintain(state, player_entity, camera, &self.triggers); let ecs = state.ecs(); - let player_position = ecs - .read_storage::() - .get(player_entity) - .map_or(Vec3::zero(), |pos| pos.0); - - let player_ori = *ecs - .read_storage::() - .get(player_entity) - .copied() - .unwrap_or_default() - .0; - - audio.set_listener_pos(&player_position, &player_ori); + audio.set_listener_pos(camera.dependents().cam_pos, camera.dependents().cam_dir); let events = ecs.read_resource::>().recv_all(); for event in events { let position = match event.pos { Some(pos) => pos, - _ => player_position, + _ => camera.dependents().cam_pos, }; if let Some(item) = self.triggers.get_trigger(&event.sfx) { diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 265688dc82..65aaa7bbe2 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -29,6 +29,7 @@ pub struct Dependents { pub view_mat: Mat4, pub proj_mat: Mat4, pub cam_pos: Vec3, + pub cam_dir: Vec3, } pub struct Camera { @@ -67,6 +68,7 @@ impl Camera { view_mat: Mat4::identity(), proj_mat: Mat4::identity(), cam_pos: Vec3::zero(), + cam_dir: Vec3::unit_y(), }, } } @@ -104,6 +106,8 @@ impl Camera { // TODO: Make this more efficient. self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w()); + + self.dependents.cam_dir = Vec3::from(self.dependents.view_mat.inverted() * -Vec4::unit_z()); } pub fn frustum(&self) -> Frustum { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index af59657488..83b827799e 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -4,7 +4,7 @@ pub mod particle; pub mod simple; pub mod terrain; -use self::{ +pub use self::{ camera::{Camera, CameraMode}, figure::FigureMgr, particle::ParticleMgr, @@ -135,6 +135,9 @@ impl Scene { /// Get a reference to the scene's particle manager. pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr } + /// Get a mutable reference to the scene's particle manager. + pub fn particle_mgr_mut(&mut self) -> &mut ParticleMgr { &mut self.particle_mgr } + /// Get a reference to the scene's figure manager. pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr } @@ -273,6 +276,7 @@ impl Scene { view_mat, proj_mat, cam_pos, + .. } = self.camera.dependents(); // Update chunk loaded distance smoothly for nice shader fog @@ -401,7 +405,7 @@ impl Scene { // Maintain audio self.sfx_mgr - .maintain(audio, scene_data.state, scene_data.player_entity); + .maintain(audio, scene_data.state, scene_data.player_entity, &self.camera); self.music_mgr.maintain(audio, scene_data.state); } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index abc3b0eb88..68aed3c937 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -10,13 +10,14 @@ use common::{ assets, comp::{object, Body, CharacterState, Pos}, figure::Segment, + outcome::Outcome, }; use dot_vox::DotVoxData; use hashbrown::HashMap; use rand::Rng; use specs::{Join, WorldExt}; use std::time::{Duration, Instant}; -use vek::Vec3; +use vek::*; struct Particles { alive_until: Instant, // created_at + lifespan @@ -45,6 +46,24 @@ impl ParticleMgr { pub fn particle_count_visible(&self) -> usize { self.instances.count() } + pub fn handle_outcome(&mut self, outcome: &Outcome, scene_data: &SceneData) { + let time = scene_data.state.get_time(); + let now = Instant::now(); + let mut rng = rand::thread_rng(); + + match outcome { + Outcome::Explosion { pos, power } => { + for _ in 0..64 { + self.particles.push(Particles { + alive_until: now + Duration::from_secs(4), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power)), + }); + } + }, + _ => {}, + } + } + pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) { if scene_data.particles_enabled { let now = Instant::now(); diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index c89ebc6511..259419fee6 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -171,6 +171,7 @@ impl Scene { view_mat, proj_mat, cam_pos, + .. } = self.camera.dependents(); const VD: f32 = 115.0; // View Distance const TIME: f64 = 43200.0; // 12 hours*3600 seconds diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index abf55456bc..a6a417c9f3 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -24,6 +24,7 @@ use common::{ terrain::{Block, BlockKind}, util::Dir, vol::ReadVol, + outcome::Outcome, }; use specs::{Join, WorldExt}; use std::{cell::RefCell, rc::Rc, time::Duration, convert::TryFrom}; @@ -100,7 +101,7 @@ impl SessionState { } /// Tick the session (and the client attached to it). - fn tick(&mut self, dt: Duration, global_state: &mut GlobalState) -> Result { + fn tick(&mut self, dt: Duration, global_state: &mut GlobalState, outcomes: &mut Vec) -> Result { self.inputs.tick(dt); let mut client = self.client.borrow_mut(); @@ -158,15 +159,7 @@ impl SessionState { global_state.settings.graphics.view_distance = vd; global_state.settings.save_to_file_warn(); }, - client::Event::Outcome(outcome) => { - if let Ok(sfx_event_item) = SfxEventItem::try_from(outcome) { - client - .state() - .ecs() - .read_resource::>() - .emit_now(sfx_event_item); - } - }, + client::Event::Outcome(outcome) => outcomes.push(outcome), } } @@ -218,7 +211,7 @@ impl PlayState for SessionState { .camera_mut() .compute_dependents(&*self.client.borrow().state().terrain()); let camera::Dependents { - view_mat, cam_pos, .. + cam_pos, cam_dir, .. } = self.scene.camera().dependents(); let (is_aiming, aim_dir_offset) = { @@ -241,8 +234,6 @@ impl PlayState for SessionState { }; self.is_aiming = is_aiming; - let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); - // Check to see whether we're aiming at anything let (build_pos, select_pos, target_entity) = under_cursor(&self.client.borrow(), cam_pos, cam_dir); @@ -642,10 +633,12 @@ impl PlayState for SessionState { self.inputs.climb = self.key_state.climb(); + let mut outcomes = Vec::new(); + // Runs if either in a multiplayer server or the singleplayer server is unpaused if !global_state.paused() { // Perform an in-game tick. - match self.tick(global_state.clock.get_avg_delta(), global_state) { + match self.tick(global_state.clock.get_avg_delta(), global_state, &mut outcomes) { Ok(TickAction::Continue) => {}, // Do nothing Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu Err(err) => { @@ -1030,6 +1023,18 @@ impl PlayState for SessionState { &mut global_state.audio, &scene_data, ); + + // Process outcomes from client + for outcome in outcomes { + if let Ok(sfx_event_item) = SfxEventItem::try_from(&outcome) { + client + .state() + .ecs() + .read_resource::>() + .emit_now(sfx_event_item); + } + self.scene.particle_mgr_mut().handle_outcome(&outcome, &scene_data); + } } } From a924f9694d302361bb1beb1bc7abaa2ecff16138 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Mon, 3 Aug 2020 12:56:12 +0100 Subject: [PATCH 17/28] Explosion sound and event lights --- assets/voxygen/audio/sfx.ron | 4 +-- assets/voxygen/audio/sfx/explosion.wav | 3 ++ assets/voxygen/shaders/particle-vert.glsl | 10 ++++++ voxygen/src/audio/sfx/mod.rs | 2 +- voxygen/src/render/pipelines/mod.rs | 5 +++ voxygen/src/render/pipelines/particle.rs | 7 ++-- voxygen/src/scene/mod.rs | 41 ++++++++++++++++++++--- voxygen/src/scene/particle.rs | 8 ++++- voxygen/src/session.rs | 2 +- 9 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 assets/voxygen/audio/sfx/explosion.wav diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index 85ff1b92af..e81459a2df 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -109,9 +109,9 @@ ), Explosion: ( files: [ - "voxygen.audio.sfx.glider_open", + "voxygen.audio.sfx.explosion", ], - threshold: 0.5, + threshold: 0.2, ), ProjectileShot: ( files: [ diff --git a/assets/voxygen/audio/sfx/explosion.wav b/assets/voxygen/audio/sfx/explosion.wav new file mode 100644 index 0000000000..f7269ac71c --- /dev/null +++ b/assets/voxygen/audio/sfx/explosion.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d7f0bb0a0865d45e98d107c1d24a448aaeeced9c37db9f9e472ab3e1bd2eb03 +size 604946 diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 70580be584..49d2fd86ab 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -31,6 +31,7 @@ float gold_noise(in vec2 xy, in float seed){ const int SMOKE = 0; const int FIRE = 1; const int GUN_POWDER_SPARK = 2; +const int SHRAPNEL = 3; // meters per second const float earth_gravity = 9.807; @@ -105,6 +106,15 @@ void main() { 1.0, vec3(3.5, 3 + rand7, 0) ); + } else if (inst_mode == SHRAPNEL) { + attr = Attr( + linear_motion( + vec3(0), + vec3(rand4, rand5, rand6) * 40.0 + grav_vel(earth_gravity) + ), + 3.0 + rand0, + vec3(0.6 + rand7 * 0.4) + ); } else { attr = Attr( linear_motion( diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index d2bb328494..6a4e0f8855 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -199,7 +199,7 @@ impl<'a> TryFrom<&'a Outcome> for SfxEventItem { Outcome::Explosion { pos, power } => Ok(Self::new( SfxEvent::Explosion, Some(*pos), - Some((*power / 10.0).min(1.0)), + Some((*power / 2.5).min(1.5)), )), Outcome::ProjectileShot { pos, .. } => { Ok(Self::new(SfxEvent::ProjectileShot, Some(*pos), None)) diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index f97890626d..442b31be32 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -117,6 +117,11 @@ impl Light { } pub fn get_pos(&self) -> Vec3 { Vec3::new(self.pos[0], self.pos[1], self.pos[2]) } + + pub fn with_strength(mut self, strength: f32) -> Self { + self.col = (Vec4::::from(self.col) * strength).into_array(); + self + } } impl Default for Light { diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 1f467c638e..e8e222e224 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -83,9 +83,10 @@ impl Vertex { } pub enum ParticleMode { - CampfireSmoke, - CampfireFire, - GunPowderSpark, + CampfireSmoke = 0, + CampfireFire = 1, + GunPowderSpark = 2, + Shrapnel = 3, } impl ParticleMode { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 83b827799e..5ee7f0257b 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -21,9 +21,10 @@ use crate::{ use anim::character::SkeletonAttr; use common::{ comp, - state::State, + state::{State, DeltaTime}, terrain::{BlockKind, TerrainChunk}, vol::ReadVol, + outcome::Outcome, }; use specs::{Entity as EcsEntity, Join, WorldExt}; use vek::*; @@ -41,6 +42,12 @@ const SHADOW_MAX_DIST: f32 = 96.0; // The distance beyond which shadows may not /// Used for first person camera effects const RUNNING_THRESHOLD: f32 = 0.7; +struct EventLight { + light: Light, + timeout: f32, + fadeout: fn(f32) -> f32, +} + struct Skybox { model: Model, locals: Consts, @@ -57,6 +64,7 @@ pub struct Scene { shadows: Consts, camera: Camera, camera_input_state: Vec2, + event_lights: Vec, skybox: Skybox, postprocess: PostProcess, @@ -101,6 +109,7 @@ impl Scene { .unwrap(), camera: Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson), camera_input_state: Vec2::zero(), + event_lights: Vec::new(), skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), @@ -135,9 +144,6 @@ impl Scene { /// Get a reference to the scene's particle manager. pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr } - /// Get a mutable reference to the scene's particle manager. - pub fn particle_mgr_mut(&mut self) -> &mut ParticleMgr { &mut self.particle_mgr } - /// Get a reference to the scene's figure manager. pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr } @@ -187,6 +193,23 @@ impl Scene { } } + pub fn handle_outcome(&mut self, outcome: &Outcome, scene_data: &SceneData) { + match outcome { + Outcome::Explosion { pos, power, .. } => self.event_lights.push(EventLight { + light: Light::new( + *pos, + Rgb::new(1.0, 0.5, 0.0), + *power * 2.5, + ), + timeout: 0.5, + fadeout: |timeout| timeout * 2.0, + }), + _ => {}, + } + + self.particle_mgr.handle_outcome(&outcome, &scene_data); + } + /// Maintain data such as GPU constant buffers, models, etc. To be called /// once per tick. pub fn maintain( @@ -319,6 +342,9 @@ impl Scene { light_anim.strength, ) }) + .chain(self.event_lights + .iter() + .map(|el| el.light.with_strength((el.fadeout)(el.timeout)))) .collect::>(); lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32); lights.truncate(MAX_LIGHT_COUNT); @@ -326,6 +352,13 @@ impl Scene { .update_consts(&mut self.lights, &lights) .expect("Failed to update light constants"); + // Update event lights + let dt = ecs.fetch::().0; + self.event_lights.drain_filter(|el| { + el.timeout -= dt; + el.timeout <= 0.0 + }); + // Update shadow constants let mut shadows = ( &scene_data.state.ecs().read_storage::(), diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 68aed3c937..ddc9595ff1 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -53,7 +53,13 @@ impl ParticleMgr { match outcome { Outcome::Explosion { pos, power } => { - for _ in 0..64 { + for _ in 0..150 { + self.particles.push(Particles { + alive_until: now + Duration::from_millis(250), + instance: ParticleInstance::new(time, rng.gen(), ParticleMode::Shrapnel, *pos), + }); + } + for _ in 0..200 { self.particles.push(Particles { alive_until: now + Duration::from_secs(4), instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power)), diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index a6a417c9f3..15570db133 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1033,7 +1033,7 @@ impl PlayState for SessionState { .read_resource::>() .emit_now(sfx_event_item); } - self.scene.particle_mgr_mut().handle_outcome(&outcome, &scene_data); + self.scene.handle_outcome(&outcome, &scene_data); } } } From 023d46537027de0ef27bdc0564496b35e1c5092a Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 18:27:55 +0800 Subject: [PATCH 18/28] clear particle gpu instance buffer when disabled --- voxygen/src/scene/particle.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index ddc9595ff1..f3ff7e5c64 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -56,13 +56,23 @@ impl ParticleMgr { for _ in 0..150 { self.particles.push(Particles { alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::Shrapnel, *pos), + instance: ParticleInstance::new( + time, + rng.gen(), + ParticleMode::Shrapnel, + *pos, + ), }); } for _ in 0..200 { self.particles.push(Particles { alive_until: now + Duration::from_secs(4), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power)), + instance: ParticleInstance::new( + time, + rng.gen(), + ParticleMode::CampfireSmoke, + *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power), + ), }); } }, @@ -77,13 +87,15 @@ impl ParticleMgr { // remove dead particles self.particles.retain(|p| p.alive_until > now); + // add new particles self.maintain_body_particles(scene_data); self.maintain_boost_particles(scene_data); - - self.upload_particles(renderer); } else { + // remove all particles self.particles.clear(); } + + self.upload_particles(renderer); } fn upload_particles(&mut self, renderer: &mut Renderer) { From 267d5a141a836ed28639faeab311b41812a56c2a Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 18:59:38 +0800 Subject: [PATCH 19/28] cleanup unneeded matrix and shader code --- assets/voxygen/shaders/particle-vert.glsl | 32 +++++++---------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 49d2fd86ab..2f2914f63d 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -20,31 +20,15 @@ out float f_light; const float SCALE = 1.0 / 11.0; -// Φ = Golden Ratio -float PHI = 1.61803398874989484820459; - -float gold_noise(in vec2 xy, in float seed){ - return fract(tan(distance(xy * PHI, xy) * seed) * xy.x); -} - // Modes const int SMOKE = 0; const int FIRE = 1; const int GUN_POWDER_SPARK = 2; const int SHRAPNEL = 3; -// meters per second +// meters per second squared (acceleration) const float earth_gravity = 9.807; -mat4 translate(vec3 vec){ - return mat4( - vec4(1.0, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(vec.x, vec.y, vec.z, 1.0) - ); -} - struct Attr { vec3 offs; float scale; @@ -66,8 +50,6 @@ float exp_scale(float factor) { } void main() { - mat4 inst_mat = translate(inst_pos); - float rand0 = hash(vec4(inst_entropy + 0)); float rand1 = hash(vec4(inst_entropy + 1)); float rand2 = hash(vec4(inst_entropy + 2)); @@ -126,14 +108,18 @@ void main() { ); } - f_pos = (inst_mat * vec4(v_pos * attr.scale * SCALE + attr.offs, 1)).xyz; + f_pos = inst_pos + (v_pos * attr.scale * SCALE + attr.offs); // First 3 normals are negative, next 3 are positive vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1)); - f_norm = (inst_mat * vec4(normals[(v_norm_ao >> 0) & 0x7u], 0)).xyz; + f_norm = + // inst_pos * + normals[(v_norm_ao >> 0) & 0x7u]; - vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; - f_col = srgb_to_linear(col) * srgb_to_linear(attr.col); + //vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; + f_col = + //srgb_to_linear(col) * + srgb_to_linear(attr.col); f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; f_light = 1.0; From ba301696f4a3ec41fbbdf234988a25ab3dbedd6f Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 19:58:08 +0800 Subject: [PATCH 20/28] change campfire cmd to spawn object instead of npc --- server/src/cmd.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 3d8d3bef72..70f6840834 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -7,7 +7,7 @@ use chrono::{NaiveTime, Timelike}; use common::{ assets, cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, - comp::{self, ChatType, Item}, + comp::{self, ChatType, Item, LightEmitter, WaypointArea}, event::{EventBus, ServerEvent}, msg::{Notification, PlayerListUpdate, ServerMsg}, npc::{self, get_npc_name}, @@ -674,24 +674,16 @@ fn handle_spawn_campfire( ) { match server.state.read_component_cloned::(target) { Some(pos) => { - let vel = Vec3::new( - rand::thread_rng().gen_range(-2.0, 3.0), - rand::thread_rng().gen_range(-2.0, 3.0), - 10.0, - ); - - let body = comp::Body::Object(comp::object::Body::CampfireLit); - - let mut stats = comp::Stats::new("Campfire".to_string(), body); - - // Level 0 will prevent exp gain from kill - stats.level.set_level(0); - server .state - .create_npc(pos, stats, comp::Loadout::default(), body) - .with(comp::Vel(vel)) - .with(comp::MountState::Unmounted) + .create_object(pos, comp::object::Body::CampfireLit) + .with(LightEmitter { + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + flicker: 1.0, + animated: true, + }) + .with(WaypointArea::default()) .build(); server.notify_client( From bb8ba752873e959f57d141c331c6e6374b8c8b91 Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 20:03:01 +0800 Subject: [PATCH 21/28] cleanup redundant function --- common/src/comp/phys.rs | 2 +- common/src/util/dir.rs | 2 -- voxygen/src/audio/sfx/mod.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index b6fdb18d73..466734cb4c 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -25,7 +25,7 @@ impl Component for Vel { pub struct Ori(pub Dir); impl Ori { - pub fn vec(&self) -> &Vec3 { &self.0.vec() } + pub fn vec(&self) -> &Vec3 { &*self.0 } } impl Component for Ori { diff --git a/common/src/util/dir.rs b/common/src/util/dir.rs index 96c085479d..22aa1b0fe6 100644 --- a/common/src/util/dir.rs +++ b/common/src/util/dir.rs @@ -87,8 +87,6 @@ impl Dir { } pub fn is_valid(&self) -> bool { !self.0.map(f32::is_nan).reduce_or() && self.is_normalized() } - - pub fn vec(&self) -> &Vec3 { &self.0 } } impl std::ops::Deref for Dir { diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 6a4e0f8855..c823c0f648 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -204,7 +204,7 @@ impl<'a> TryFrom<&'a Outcome> for SfxEventItem { Outcome::ProjectileShot { pos, .. } => { Ok(Self::new(SfxEvent::ProjectileShot, Some(*pos), None)) }, - _ => Err(()), + // _ => Err(()), } } } From cc36e9c3000afd215907c4865d8dc16856f90de3 Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 20:17:50 +0800 Subject: [PATCH 22/28] fix rebase --- common/src/comp/inventory/item/tool.rs | 132 ++++++++++++------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 8b09cc7324..b040f0517f 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -2,8 +2,7 @@ // version in voxygen\src\meta.rs in order to reset save files to being empty use crate::comp::{ - body::object, projectile, Body, CharacterAbility, Gravity, HealthChange, HealthSource, - LightEmitter, Projectile, + body::object, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -194,7 +193,6 @@ impl Tool { recover_duration: Duration::from_millis(500), projectile_body: Body::Object(object::Body::Arrow), projectile_light: None, - projectile_gravity: Some(Gravity(0.05)), }, ], Dagger(_) => vec![ @@ -263,62 +261,41 @@ impl Tool { col: (0.85, 0.5, 0.11).into(), ..Default::default() }), - projectile::Effect::RewardEnergy(150), - projectile::Effect::Vanish, - ], - time_left: Duration::from_secs(20), - owner: None, - }, - projectile_body: Body::Object(object::Body::BoltFire), - projectile_light: Some(LightEmitter { - col: (0.85, 0.5, 0.11).into(), - ..Default::default() - }), - projectile_gravity: None, - }, - BasicRanged { - energy_cost: 400, - holdable: true, - prepare_duration: Duration::from_millis(800), - recover_duration: Duration::from_millis(50), - projectile: Projectile { - hit_solid: vec![ - projectile::Effect::Explode { power: 1.4 }, - projectile::Effect::Vanish, - ], - hit_entity: vec![ - projectile::Effect::Explode { power: 1.4 }, - projectile::Effect::Vanish, - ], - time_left: Duration::from_secs(20), - owner: None, - }, - projectile_body: Body::Object(object::Body::BoltFireBig), - projectile_light: Some(LightEmitter { - col: (1.0, 0.75, 0.11).into(), - ..Default::default() - }), - projectile_gravity: None, - }, - ], - Staff(StaffKind::Sceptre) => vec![ - BasicMelee { - energy_cost: 0, - buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(300), - base_healthchange: -1, - range: 10.0, - max_angle: 45.0, - }, - BasicMelee { - energy_cost: 350, - buildup_duration: Duration::from_millis(0), - recover_duration: Duration::from_millis(1000), - base_healthchange: 15, - range: 10.0, - max_angle: 45.0, - }, - ], + + projectile_gravity: None, + }, + BasicRanged { + energy_cost: 400, + holdable: true, + prepare_duration: Duration::from_millis(800), + recover_duration: Duration::from_millis(50), + projectile: Projectile { + hit_solid: vec![ + projectile::Effect::Explode { + power: 1.4 * self.base_power(), + }, + projectile::Effect::Vanish, + ], + hit_entity: vec![ + projectile::Effect::Explode { + power: 1.4 * self.base_power(), + }, + projectile::Effect::Vanish, + ], + time_left: Duration::from_secs(20), + owner: None, + }, + projectile_body: Body::Object(object::Body::BoltFireBig), + projectile_light: Some(LightEmitter { + col: (1.0, 0.75, 0.11).into(), + ..Default::default() + }), + + projectile_gravity: None, + }, + ] + } + }, Shield(_) => vec![ BasicMelee { energy_cost: 0, @@ -337,14 +314,35 @@ impl Tool { duration: Duration::from_millis(50), only_up: false, }, - projectile_body: Body::Object(object::Body::ArrowSnake), - projectile_light: Some(LightEmitter { - col: (0.0, 1.0, 0.33).into(), - ..Default::default() - }), - projectile_gravity: None, - }, - ], + CharacterAbility::Boost { + duration: Duration::from_millis(50), + only_up: true, + }, + BasicRanged { + energy_cost: 0, + holdable: false, + prepare_duration: Duration::from_millis(0), + recover_duration: Duration::from_millis(10), + projectile: Projectile { + hit_solid: vec![projectile::Effect::Stick], + hit_entity: vec![ + projectile::Effect::Stick, + projectile::Effect::Possess, + ], + time_left: Duration::from_secs(10), + owner: None, + }, + projectile_body: Body::Object(object::Body::ArrowSnake), + projectile_light: Some(LightEmitter { + col: (0.0, 1.0, 0.33).into(), + ..Default::default() + }), + projectile_gravity: None, + }, + ] + } else { + vec![] + } }, Empty => vec![BasicMelee { energy_cost: 0, From bf025df204a112687ceb0a493dcb6cc92e55ece6 Mon Sep 17 00:00:00 2001 From: scott-c Date: Tue, 4 Aug 2020 23:24:58 +0800 Subject: [PATCH 23/28] refactor sfx mgr outcome useage --- voxygen/src/audio/sfx/mod.rs | 51 +++++++++++++++++++++--------------- voxygen/src/scene/mod.rs | 38 ++++++++++++++++----------- voxygen/src/session.rs | 26 +++++++++--------- 3 files changed, 67 insertions(+), 48 deletions(-) diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index c823c0f648..143e2820c1 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -191,24 +191,6 @@ impl From<&InventoryUpdateEvent> for SfxEvent { } } -impl<'a> TryFrom<&'a Outcome> for SfxEventItem { - type Error = (); - - fn try_from(outcome: &'a Outcome) -> Result { - match outcome { - Outcome::Explosion { pos, power } => Ok(Self::new( - SfxEvent::Explosion, - Some(*pos), - Some((*power / 2.5).min(1.5)), - )), - Outcome::ProjectileShot { pos, .. } => { - Ok(Self::new(SfxEvent::ProjectileShot, Some(*pos), None)) - }, - // _ => Err(()), - } - } -} - #[derive(Deserialize)] pub struct SfxTriggerItem { pub files: Vec, @@ -251,13 +233,15 @@ impl SfxMgr { return; } - self.event_mapper - .maintain(state, player_entity, camera, &self.triggers); - let ecs = state.ecs(); audio.set_listener_pos(camera.dependents().cam_pos, camera.dependents().cam_dir); + // deprecated in favor of outcomes + self.event_mapper + .maintain(state, player_entity, camera, &self.triggers); + + // deprecated in favor of outcomes let events = ecs.read_resource::>().recv_all(); for event in events { @@ -283,6 +267,31 @@ impl SfxMgr { } } + pub fn handle_outcome(&mut self, outcome: &Outcome, audio: &mut AudioFrontend) { + if !audio.sfx_enabled() { + return; + } + + match outcome { + Outcome::Explosion { pos, power } => { + audio.play_sfx( + // TODO: from sfx triggers config + "voxygen.audio.sfx.explosion", + *pos, + Some((*power / 2.5).min(1.5)), + ); + }, + Outcome::ProjectileShot { pos, body, .. } => { + audio.play_sfx( + // TODO: from sfx triggers config + "voxygen.audio.sfx.glider_open", + *pos, + None, + ); + }, + } + } + fn load_sfx_items() -> SfxTriggers { match assets::load_file("voxygen.audio.sfx", &["ron"]) { Ok(file) => match ron::de::from_reader(file) { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 5ee7f0257b..c635c02c83 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -21,10 +21,10 @@ use crate::{ use anim::character::SkeletonAttr; use common::{ comp, - state::{State, DeltaTime}, + outcome::Outcome, + state::{DeltaTime, State}, terrain::{BlockKind, TerrainChunk}, vol::ReadVol, - outcome::Outcome, }; use specs::{Entity as EcsEntity, Join, WorldExt}; use vek::*; @@ -193,21 +193,23 @@ impl Scene { } } - pub fn handle_outcome(&mut self, outcome: &Outcome, scene_data: &SceneData) { + pub fn handle_outcome( + &mut self, + outcome: &Outcome, + scene_data: &SceneData, + audio: &mut AudioFrontend, + ) { + self.particle_mgr.handle_outcome(&outcome, &scene_data); + self.sfx_mgr.handle_outcome(&outcome, audio); + match outcome { Outcome::Explosion { pos, power, .. } => self.event_lights.push(EventLight { - light: Light::new( - *pos, - Rgb::new(1.0, 0.5, 0.0), - *power * 2.5, - ), + light: Light::new(*pos, Rgb::new(1.0, 0.5, 0.0), *power * 2.5), timeout: 0.5, fadeout: |timeout| timeout * 2.0, }), _ => {}, } - - self.particle_mgr.handle_outcome(&outcome, &scene_data); } /// Maintain data such as GPU constant buffers, models, etc. To be called @@ -342,9 +344,11 @@ impl Scene { light_anim.strength, ) }) - .chain(self.event_lights - .iter() - .map(|el| el.light.with_strength((el.fadeout)(el.timeout)))) + .chain( + self.event_lights + .iter() + .map(|el| el.light.with_strength((el.fadeout)(el.timeout))), + ) .collect::>(); lights.sort_by_key(|light| light.get_pos().distance_squared(player_pos) as i32); lights.truncate(MAX_LIGHT_COUNT); @@ -437,8 +441,12 @@ impl Scene { self.particle_mgr.maintain(renderer, &scene_data); // Maintain audio - self.sfx_mgr - .maintain(audio, scene_data.state, scene_data.player_entity, &self.camera); + self.sfx_mgr.maintain( + audio, + scene_data.state, + scene_data.player_entity, + &self.camera, + ); self.music_mgr.maintain(audio, scene_data.state); } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 15570db133..83bf35eb24 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -21,13 +21,13 @@ use common::{ }, event::EventBus, msg::ClientState, + outcome::Outcome, terrain::{Block, BlockKind}, util::Dir, vol::ReadVol, - outcome::Outcome, }; use specs::{Join, WorldExt}; -use std::{cell::RefCell, rc::Rc, time::Duration, convert::TryFrom}; +use std::{cell::RefCell, rc::Rc, time::Duration}; use tracing::{error, info}; use vek::*; @@ -101,7 +101,12 @@ impl SessionState { } /// Tick the session (and the client attached to it). - fn tick(&mut self, dt: Duration, global_state: &mut GlobalState, outcomes: &mut Vec) -> Result { + fn tick( + &mut self, + dt: Duration, + global_state: &mut GlobalState, + outcomes: &mut Vec, + ) -> Result { self.inputs.tick(dt); let mut client = self.client.borrow_mut(); @@ -638,7 +643,11 @@ impl PlayState for SessionState { // Runs if either in a multiplayer server or the singleplayer server is unpaused if !global_state.paused() { // Perform an in-game tick. - match self.tick(global_state.clock.get_avg_delta(), global_state, &mut outcomes) { + match self.tick( + global_state.clock.get_avg_delta(), + global_state, + &mut outcomes, + ) { Ok(TickAction::Continue) => {}, // Do nothing Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu Err(err) => { @@ -1026,14 +1035,7 @@ impl PlayState for SessionState { // Process outcomes from client for outcome in outcomes { - if let Ok(sfx_event_item) = SfxEventItem::try_from(&outcome) { - client - .state() - .ecs() - .read_resource::>() - .emit_now(sfx_event_item); - } - self.scene.handle_outcome(&outcome, &scene_data); + self.scene.handle_outcome(&outcome, &scene_data, &mut global_state.audio); } } } From a0107d5cda77a7f8aeab2dbced4e745965170ac0 Mon Sep 17 00:00:00 2001 From: scott-c Date: Fri, 7 Aug 2020 00:41:43 +0800 Subject: [PATCH 24/28] fix rebase --- client/src/lib.rs | 8 ++++---- common/src/outcome.rs | 10 +++++----- server/src/events/entity_creation.rs | 7 +++++-- server/src/sys/entity_sync.rs | 12 ++++++++---- voxygen/src/audio/channel.rs | 9 ++++++--- voxygen/src/audio/sfx/event_mapper/combat/mod.rs | 12 ++++++++---- voxygen/src/audio/sfx/event_mapper/mod.rs | 10 ++++++++-- voxygen/src/audio/sfx/event_mapper/movement/mod.rs | 12 ++++++++---- .../src/audio/sfx/event_mapper/progression/mod.rs | 8 +++++++- voxygen/src/audio/sfx/mod.rs | 6 +++--- voxygen/src/scene/mod.rs | 2 +- voxygen/src/scene/particle.rs | 2 +- voxygen/src/session.rs | 3 ++- 13 files changed, 66 insertions(+), 35 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index 08109865db..f41d1efe04 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -25,12 +25,12 @@ use common::{ Notification, PlayerInfo, PlayerListUpdate, RegisterError, RequestStateError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG, }, + outcome::Outcome, recipe::RecipeBook, state::State, sync::{Uid, UidAllocator, WorldSyncExt}, terrain::{block::Block, TerrainChunk, TerrainChunkSize}, vol::RectVolSize, - outcome::Outcome, }; use futures_executor::block_on; use futures_timer::Delay; @@ -1231,9 +1231,9 @@ impl Client { self.view_distance = Some(vd); frontend_events.push(Event::SetViewDistance(vd)); }, - ServerMsg::Outcomes(outcomes) => frontend_events.extend(outcomes - .into_iter() - .map(Event::Outcome)), + ServerMsg::Outcomes(outcomes) => { + frontend_events.extend(outcomes.into_iter().map(Event::Outcome)) + }, } } } diff --git a/common/src/outcome.rs b/common/src/outcome.rs index 9f047bcc26..4f89f418b0 100644 --- a/common/src/outcome.rs +++ b/common/src/outcome.rs @@ -2,10 +2,11 @@ use crate::comp; use serde::{Deserialize, Serialize}; use vek::*; -/// An outcome represents the final result of an instantaneous event. It implies that said event has -/// already occurred. It is not a request for that event to occur, nor is it something that may be -/// cancelled or otherwise altered. Its primary purpose is to act as something for frontends (both -/// server and client) to listen to in order to receive feedback about events in the world. +/// An outcome represents the final result of an instantaneous event. It implies +/// that said event has already occurred. It is not a request for that event to +/// occur, nor is it something that may be cancelled or otherwise altered. Its +/// primary purpose is to act as something for frontends (both server and +/// client) to listen to in order to receive feedback about events in the world. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Outcome { Explosion { @@ -19,7 +20,6 @@ pub enum Outcome { }, } - impl Outcome { pub fn get_pos(&self) -> Option> { match self { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 0d9a8ff61a..f894268737 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -4,8 +4,8 @@ use common::{ self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos, Projectile, Scale, Stats, Vel, WaypointArea, }, - util::Dir, outcome::Outcome, + util::Dir, }; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; @@ -93,7 +93,10 @@ pub fn handle_shoot( let vel = *dir * 100.0; // Add an outcome - state.ecs().write_resource::>().push(Outcome::ProjectileShot { pos, body, vel }); + state + .ecs() + .write_resource::>() + .push(Outcome::ProjectileShot { pos, body, vel }); // TODO: Player height pos.z += 1.2; diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 6e4a5fa897..2dc20648b4 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -7,14 +7,14 @@ use crate::{ Tick, }; use common::{ - comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel, Player}, + comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, msg::ServerMsg, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, state::TimeOfDay, sync::{CompSyncPackage, Uid}, - vol::RectVolSize, terrain::TerrainChunkSize, + vol::RectVolSize, }; use specs::{ Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, @@ -326,8 +326,12 @@ impl<'a> System<'a> for Sys { // Sync outcomes for (client, player, pos) in (&mut clients, &players, positions.maybe()).join() { - let is_near = |o_pos: Vec3| pos - .zip_with(player.view_distance, |pos, vd| pos.0.xy().distance_squared(o_pos.xy()) < (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0)); + let is_near = |o_pos: Vec3| { + pos.zip_with(player.view_distance, |pos, vd| { + pos.0.xy().distance_squared(o_pos.xy()) + < (vd as f32 * TerrainChunkSize::RECT_SIZE.x as f32).powf(2.0) + }) + }; let outcomes = outcomes .iter() diff --git a/voxygen/src/audio/channel.rs b/voxygen/src/audio/channel.rs index 6662e0efd0..5f19e42532 100644 --- a/voxygen/src/audio/channel.rs +++ b/voxygen/src/audio/channel.rs @@ -171,8 +171,11 @@ impl SfxChannel { pub fn update(&mut self, listener: &Listener) { const FALLOFF: f32 = 0.13; - self.sink.set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array()); - self.sink.set_left_ear_position(listener.ear_left_rpos.into_array()); - self.sink.set_right_ear_position(listener.ear_right_rpos.into_array()); + self.sink + .set_emitter_position(((self.pos - listener.pos) * FALLOFF).into_array()); + self.sink + .set_left_ear_position(listener.ear_left_rpos.into_array()); + self.sink + .set_right_ear_position(listener.ear_right_rpos.into_array()); } } diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs index 5a81800930..56aca955b0 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs @@ -41,7 +41,13 @@ pub struct CombatEventMapper { } impl EventMapper for CombatEventMapper { - fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers) { + fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + camera: &Camera, + triggers: &SfxTriggers, + ) { let ecs = state.ecs(); let sfx_event_bus = ecs.read_resource::>(); @@ -56,9 +62,7 @@ impl EventMapper for CombatEventMapper { ecs.read_storage::().maybe(), ) .join() - .filter(|(_, e_pos, ..)| { - (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR - }) + .filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR) { if let Some(character) = character { let state = self.event_history.entry(entity).or_default(); diff --git a/voxygen/src/audio/sfx/event_mapper/mod.rs b/voxygen/src/audio/sfx/event_mapper/mod.rs index 4ac331fa86..d5a9d6f8cb 100644 --- a/voxygen/src/audio/sfx/event_mapper/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/mod.rs @@ -8,11 +8,17 @@ use combat::CombatEventMapper; use movement::MovementEventMapper; use progression::ProgressionEventMapper; -use crate::scene::Camera; use super::SfxTriggers; +use crate::scene::Camera; trait EventMapper { - fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers); + fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + camera: &Camera, + triggers: &SfxTriggers, + ); } pub struct SfxEventMapper { diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index da6d29edc7..55bff58c96 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -38,7 +38,13 @@ pub struct MovementEventMapper { } impl EventMapper for MovementEventMapper { - fn maintain(&mut self, state: &State, player_entity: specs::Entity, camera: &Camera, triggers: &SfxTriggers) { + fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + camera: &Camera, + triggers: &SfxTriggers, + ) { let ecs = state.ecs(); let sfx_event_bus = ecs.read_resource::>(); @@ -55,9 +61,7 @@ impl EventMapper for MovementEventMapper { ecs.read_storage::().maybe(), ) .join() - .filter(|(_, e_pos, ..)| { - (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR - }) + .filter(|(_, e_pos, ..)| (e_pos.0.distance_squared(cam_pos)) < SFX_DIST_LIMIT_SQR) { if let Some(character) = character { let state = self.event_history.entry(entity).or_default(); diff --git a/voxygen/src/audio/sfx/event_mapper/progression/mod.rs b/voxygen/src/audio/sfx/event_mapper/progression/mod.rs index 64c21b5303..499e39e559 100644 --- a/voxygen/src/audio/sfx/event_mapper/progression/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/progression/mod.rs @@ -26,7 +26,13 @@ pub struct ProgressionEventMapper { impl EventMapper for ProgressionEventMapper { #[allow(clippy::op_ref)] // TODO: Pending review in #587 - fn maintain(&mut self, state: &State, player_entity: specs::Entity, _camera: &Camera, triggers: &SfxTriggers) { + fn maintain( + &mut self, + state: &State, + player_entity: specs::Entity, + _camera: &Camera, + triggers: &SfxTriggers, + ) { let ecs = state.ecs(); let next_state = ecs.read_storage::().get(player_entity).map_or( diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 143e2820c1..d85d7b2765 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -88,8 +88,8 @@ use crate::{audio::AudioFrontend, scene::Camera}; use common::{ assets, comp::{ - item::{Consumable, ItemKind, ToolCategory}, - CharacterAbilityType, InventoryUpdateEvent, Ori, Pos, + item::{ItemKind, ToolCategory}, + CharacterAbilityType, InventoryUpdateEvent, }, event::EventBus, outcome::Outcome, @@ -281,7 +281,7 @@ impl SfxMgr { Some((*power / 2.5).min(1.5)), ); }, - Outcome::ProjectileShot { pos, body, .. } => { + Outcome::ProjectileShot { pos, .. } => { audio.play_sfx( // TODO: from sfx triggers config "voxygen.audio.sfx.glider_open", diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index c635c02c83..5eacf0870b 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -208,7 +208,7 @@ impl Scene { timeout: 0.5, fadeout: |timeout| timeout * 2.0, }), - _ => {}, + Outcome::ProjectileShot { .. } => {}, } } diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index f3ff7e5c64..6784390b5a 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -76,7 +76,7 @@ impl ParticleMgr { }); } }, - _ => {}, + Outcome::ProjectileShot { .. } => {}, } } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 83bf35eb24..b852157a96 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1035,7 +1035,8 @@ impl PlayState for SessionState { // Process outcomes from client for outcome in outcomes { - self.scene.handle_outcome(&outcome, &scene_data, &mut global_state.audio); + self.scene + .handle_outcome(&outcome, &scene_data, &mut global_state.audio); } } } From dd1e89a691546c7636b20302721a305fe19752e5 Mon Sep 17 00:00:00 2001 From: scott-c Date: Fri, 7 Aug 2020 22:04:52 +0800 Subject: [PATCH 25/28] Implement particle heartbeat scheduler --- voxygen/src/render/pipelines/particle.rs | 12 +- voxygen/src/scene/particle.rs | 318 +++++++++++++---------- 2 files changed, 190 insertions(+), 140 deletions(-) diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index e8e222e224..eae013242d 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -94,15 +94,11 @@ impl ParticleMode { } impl Instance { - pub fn new( - inst_time: f64, - inst_entropy: f32, - inst_mode: ParticleMode, - inst_pos: Vec3, - ) -> Self { + pub fn new(inst_time: f64, inst_mode: ParticleMode, inst_pos: Vec3) -> Self { + use rand::Rng; Self { inst_time: inst_time as f32, - inst_entropy, + inst_entropy: rand::thread_rng().gen(), inst_mode: inst_mode as i32, inst_pos: inst_pos.into_array(), } @@ -110,7 +106,7 @@ impl Instance { } impl Default for Instance { - fn default() -> Self { Self::new(0.0, 0.0, ParticleMode::CampfireSmoke, Vec3::zero()) } + fn default() -> Self { Self::new(0.0, ParticleMode::CampfireSmoke, Vec3::zero()) } } pub struct ParticlePipeline; diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 6784390b5a..11e3b74a1d 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -19,61 +19,51 @@ use specs::{Join, WorldExt}; use std::time::{Duration, Instant}; use vek::*; -struct Particles { - alive_until: Instant, // created_at + lifespan - instance: ParticleInstance, -} - pub struct ParticleMgr { - // keep track of lifespans - particles: Vec, + /// keep track of lifespans + particles: Vec, + + /// keep track of timings + scheduler: HeartbeatScheduler, + + /// GPU Instance Buffer instances: Instances, + + /// GPU Vertex Buffers model_cache: HashMap<&'static str, Model>, } -const MODEL_KEY: &str = "voxygen.voxel.particle"; - impl ParticleMgr { pub fn new(renderer: &mut Renderer) -> Self { Self { particles: Vec::new(), + scheduler: HeartbeatScheduler::new(), instances: default_instances(renderer), model_cache: default_cache(renderer), } } - pub fn particle_count(&self) -> usize { self.instances.count() } - - pub fn particle_count_visible(&self) -> usize { self.instances.count() } - pub fn handle_outcome(&mut self, outcome: &Outcome, scene_data: &SceneData) { let time = scene_data.state.get_time(); - let now = Instant::now(); let mut rng = rand::thread_rng(); match outcome { Outcome::Explosion { pos, power } => { for _ in 0..150 { - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new( - time, - rng.gen(), - ParticleMode::Shrapnel, - *pos, - ), - }); + self.particles.push(Particle::new( + Duration::from_millis(250), + time, + ParticleMode::Shrapnel, + *pos, + )); } for _ in 0..200 { - self.particles.push(Particles { - alive_until: now + Duration::from_secs(4), - instance: ParticleInstance::new( - time, - rng.gen(), - ParticleMode::CampfireSmoke, - *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power), - ), - }); + self.particles.push(Particle::new( + Duration::from_secs(4), + time, + ParticleMode::CampfireSmoke, + *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power), + )); } }, Outcome::ProjectileShot { .. } => {}, @@ -84,35 +74,26 @@ impl ParticleMgr { if scene_data.particles_enabled { let now = Instant::now(); - // remove dead particles + // remove dead Particle self.particles.retain(|p| p.alive_until > now); - // add new particles + // add new Particle self.maintain_body_particles(scene_data); self.maintain_boost_particles(scene_data); + + // update timings + self.scheduler.maintain(); } else { - // remove all particles + // remove all particle lifespans self.particles.clear(); + + // remove all timings + self.scheduler.clear(); } self.upload_particles(renderer); } - fn upload_particles(&mut self, renderer: &mut Renderer) { - let all_cpu_instances = self - .particles - .iter() - .map(|p| p.instance) - .collect::>(); - - // TODO: optimise buffer writes - let gpu_instances = renderer - .create_instances(&all_cpu_instances) - .expect("Failed to upload particle instances to the GPU!"); - - self.instances = gpu_instances; - } - fn maintain_body_particles(&mut self, scene_data: &SceneData) { let ecs = scene_data.state.ecs(); for (_i, (_entity, body, pos)) in ( @@ -141,106 +122,93 @@ impl ParticleMgr { fn maintain_campfirelit_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - }); + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { + self.particles.push(Particle::new( + Duration::from_millis(250), + time, + ParticleMode::CampfireFire, + pos.0, + )); - self.particles.push(Particles { - alive_until: now + Duration::from_secs(10), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); + self.particles.push(Particle::new( + Duration::from_secs(10), + time, + ParticleMode::CampfireSmoke, + pos.0, + )); + } } fn maintain_boltfire_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - }); - - self.particles.push(Particles { - alive_until: now + Duration::from_secs(1), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { + self.particles.push(Particle::new( + Duration::from_millis(250), + time, + ParticleMode::CampfireFire, + pos.0, + )); + self.particles.push(Particle::new( + Duration::from_secs(1), + time, + ParticleMode::CampfireSmoke, + pos.0, + )); + } } fn maintain_boltfirebig_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); // fire - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(250), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0), - }); + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(3)) { + self.particles.push(Particle::new( + Duration::from_millis(250), + time, + ParticleMode::CampfireFire, + pos.0, + )); + } // smoke - self.particles.push(Particles { - alive_until: now + Duration::from_secs(2), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_secs(2), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_secs(2), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) { + self.particles.push(Particle::new( + Duration::from_secs(2), + time, + ParticleMode::CampfireSmoke, + pos.0, + )); + } } fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) { let time = scene_data.state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); - // sparks - self.particles.push(Particles { - alive_until: now + Duration::from_millis(1500), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(1500), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(1500), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(1500), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - }); - self.particles.push(Particles { - alive_until: now + Duration::from_millis(1500), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::GunPowderSpark, pos.0), - }); + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { + // sparks + self.particles.push(Particle::new( + Duration::from_millis(1500), + time, + ParticleMode::GunPowderSpark, + pos.0, + )); - // smoke - self.particles.push(Particles { - alive_until: now + Duration::from_secs(2), - instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0), - }); + // smoke + self.particles.push(Particle::new( + Duration::from_secs(2), + time, + ParticleMode::CampfireSmoke, + pos.0, + )); + } } fn maintain_boost_particles(&mut self, scene_data: &SceneData) { let state = scene_data.state; let ecs = state.ecs(); let time = state.get_time(); - let now = Instant::now(); - let mut rng = rand::thread_rng(); for (_i, (_entity, pos, character_state)) in ( &ecs.entities(), @@ -251,19 +219,33 @@ impl ParticleMgr { .enumerate() { if let CharacterState::Boost(_) = character_state { - self.particles.push(Particles { - alive_until: now + Duration::from_secs(15), - instance: ParticleInstance::new( + for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { + self.particles.push(Particle::new( + Duration::from_secs(15), time, - rng.gen(), ParticleMode::CampfireSmoke, pos.0, - ), - }); + )); + } } } } + fn upload_particles(&mut self, renderer: &mut Renderer) { + let all_cpu_instances = self + .particles + .iter() + .map(|p| p.instance) + .collect::>(); + + // TODO: optimise buffer writes + let gpu_instances = renderer + .create_instances(&all_cpu_instances) + .expect("Failed to upload particle instances to the GPU!"); + + self.instances = gpu_instances; + } + pub fn render( &self, renderer: &mut Renderer, @@ -275,12 +257,16 @@ impl ParticleMgr { if scene_data.particles_enabled { let model = &self .model_cache - .get(MODEL_KEY) + .get(DEFAULT_MODEL_KEY) .expect("Expected particle model in cache"); renderer.render_particles(model, globals, &self.instances, lights, shadows); } } + + pub fn particle_count(&self) -> usize { self.instances.count() } + + pub fn particle_count_visible(&self) -> usize { self.instances.count() } } fn default_instances(renderer: &mut Renderer) -> Instances { @@ -291,14 +277,16 @@ fn default_instances(renderer: &mut Renderer) -> Instances { .expect("Failed to upload particle instances to the GPU!") } +const DEFAULT_MODEL_KEY: &str = "voxygen.voxel.particle"; + fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model> { let mut model_cache = HashMap::new(); - model_cache.entry(MODEL_KEY).or_insert_with(|| { + model_cache.entry(DEFAULT_MODEL_KEY).or_insert_with(|| { let offset = Vec3::zero(); let lod_scale = Vec3::one(); - let vox = assets::load_expect::(MODEL_KEY); + let vox = assets::load_expect::(DEFAULT_MODEL_KEY); let mesh = &Meshable::::generate_mesh( &Segment::from(vox.as_ref()), @@ -313,3 +301,69 @@ fn default_cache(renderer: &mut Renderer) -> HashMap<&'static str, Model, +} + +impl HeartbeatScheduler { + pub fn new() -> Self { + HeartbeatScheduler { + timers: HashMap::new(), + } + } + + /// updates the last elapsed times and elasped counts + /// this should be called once, and only once per tick. + pub fn maintain(&mut self) { + for (frequency, (last_update, heartbeats)) in self.timers.iter_mut() { + // the number of iterations since last update + *heartbeats = + // TODO: use nightly api once stable; https://github.com/rust-lang/rust/issues/63139 + (last_update.elapsed().as_secs_f32() / frequency.as_secs_f32()).floor() as u8; + + // Instant::now() minus the heart beat count precision, + // or alternatively as expressed below. + *last_update += frequency.mul_f32(*heartbeats as f32); + // Note: we want to preserve incomplete heartbeats, and include them + // in the next update. + } + } + + /// returns the number of times this duration has elasped since the last + /// tick: + /// - if it's more frequent then tick rate, it could be 1 or more. + /// - if it's less frequent then tick rate, it could be 1 or 0. + /// - if it's equal to the tick rate, it could be between 2 and 0, due to + /// delta time variance. + pub fn heartbeats(&mut self, frequency: Duration) -> u8 { + self.timers + .entry(frequency) + .or_insert_with(|| (Instant::now(), 0)) + .1 + } + + pub fn clear(&mut self) { self.timers.clear() } +} + +struct Particle { + alive_until: Instant, // created_at + lifespan + instance: ParticleInstance, +} + +impl Particle { + fn new(lifespan: Duration, time: f64, mode: ParticleMode, pos: Vec3) -> Self { + Particle { + alive_until: Instant::now() + lifespan, + instance: ParticleInstance::new(time, mode, pos), + } + } +} From 0ace0acdcdd65f8dcce914f824ac04c1df143a23 Mon Sep 17 00:00:00 2001 From: scott-c Date: Fri, 7 Aug 2020 22:22:17 +0800 Subject: [PATCH 26/28] update comment --- voxygen/src/audio/sfx/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index d85d7b2765..783056271b 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -237,11 +237,11 @@ impl SfxMgr { audio.set_listener_pos(camera.dependents().cam_pos, camera.dependents().cam_dir); - // deprecated in favor of outcomes + // TODO: replace; deprecated in favor of outcomes self.event_mapper .maintain(state, player_entity, camera, &self.triggers); - // deprecated in favor of outcomes + // TODO: replace; deprecated in favor of outcomes let events = ecs.read_resource::>().recv_all(); for event in events { From 70ff8c3faa287ea44b00195654406cec4b3b961e Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 8 Aug 2020 19:18:59 +0800 Subject: [PATCH 27/28] Add Copy trait to dependents --- voxygen/src/scene/camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 65aaa7bbe2..b074f73ef5 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -24,7 +24,7 @@ impl Default for CameraMode { fn default() -> Self { Self::ThirdPerson } } -#[derive(Clone)] +#[derive(Clone, Copy)] pub struct Dependents { pub view_mat: Mat4, pub proj_mat: Mat4, @@ -116,7 +116,7 @@ impl Camera { ) } - pub fn dependents(&self) -> Dependents { self.dependents.clone() } + pub fn dependents(&self) -> Dependents { &self.dependents } /// Rotate the camera about its focus by the given delta, limiting the input /// accordingly. From cae84dd4c9c61afb85ef004e9771b7bd9b575ac0 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 8 Aug 2020 20:53:07 +0800 Subject: [PATCH 28/28] fix rebase --- server/src/cmd.rs | 2 +- server/src/events/entity_creation.rs | 1 + server/src/events/entity_manipulation.rs | 21 +++++++++++++++++---- server/src/sys/sentinel.rs | 2 +- voxygen/src/scene/camera.rs | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 70f6840834..5a282766fd 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -672,7 +672,7 @@ fn handle_spawn_campfire( _args: String, _action: &ChatCommand, ) { - match server.state.read_component_cloned::(target) { + match server.state.read_component_copied::(target) { Some(pos) => { server .state diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index f894268737..5e910452c9 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -7,6 +7,7 @@ use common::{ outcome::Outcome, util::Dir, }; +use comp::group; use specs::{Builder, Entity as EcsEntity, WorldExt}; use vek::{Rgb, Vec3}; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index d87e61ea08..ff6f4ae2b6 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -278,16 +278,29 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) { } } -pub fn handle_explosion(server: &Server, pos: Vec3, power: f32, owner: Option) { +pub fn handle_explosion( + server: &Server, + pos: Vec3, + power: f32, + owner: Option, + friendly_damage: bool, +) { + // Go through all other entities + let hit_range = 3.0 * power; let ecs = &server.state.ecs(); // Add an outcome ecs.write_resource::>() .push(Outcome::Explosion { pos, power }); - // Go through all other entities - let hit_range = 3.0 * power; - for (pos_b, ori_b, character_b, stats_b, loadout_b) in ( + let owner_entity = owner.and_then(|uid| { + ecs.read_resource::() + .retrieve_entity_internal(uid.into()) + }); + let groups = ecs.read_storage::(); + + for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in ( + &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 8bda2339e8..adcc76bd30 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,7 +1,7 @@ use super::SysTimer; use common::{ comp::{ - Alignment, Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, + Body, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, }, msg::EcsCompPacket, diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index b074f73ef5..deaadcc963 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -116,7 +116,7 @@ impl Camera { ) } - pub fn dependents(&self) -> Dependents { &self.dependents } + pub fn dependents(&self) -> Dependents { self.dependents } /// Rotate the camera about its focus by the given delta, limiting the input /// accordingly.