From 39b676cd8f436555ec79424cea5f5f4637cf6c53 Mon Sep 17 00:00:00 2001 From: scott-c Date: Sun, 5 Jul 2020 20:10:58 +0800 Subject: [PATCH] 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 }] +}