From da5f4828a5237c88df2a1f7585e9f2887eceef6e Mon Sep 17 00:00:00 2001 From: scott-c Date: Sat, 11 Jul 2020 18:37:19 +0800 Subject: [PATCH] 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 }