Add particle lifespan

This commit is contained in:
scott-c 2020-07-11 18:37:19 +08:00
parent 39b676cd8f
commit da5f4828a5
5 changed files with 133 additions and 69 deletions

View File

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

View File

@ -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<f32>?
pub initial_lifespan: Duration,
pub initial_offset: (Vec3<f32>, Vec3<f32>), // fn() -> Vec3<f32>,
pub initial_scale: (f32, f32), // fn() -> Vec3<f32>,
pub initial_orientation: (Vec3<f32>, Vec3<f32>), // fn() -> Vec3<f32>,
}
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<Self, IDVStorage<Self>>;
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}

View File

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

View File

@ -118,9 +118,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
flicker: 1.0,
animated: true,
})
.with(ParticleEmitter {
mode: ParticleEmitterMode::Sprinkler,
})
.with(ParticleEmitter::default())
.with(WaypointArea::default())
.build();
}

View File

@ -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<ParticlePipeline>,
// created_at: Instant,
// lifespan: Duration,
alive_until: Instant, // created_at + lifespan
instances: Instances<ParticleInstance>,
}
pub struct ParticleMgr {
entity_particles: HashMap<EcsEntity, Vec<Particles>>,
model_cache: Model<ParticlePipeline>,
struct Emitter {
last_emit: Instant,
}
pub struct ParticleMgr {
// to keep track of spawn intervals
emitters: HashMap<EcsEntity, Emitter>,
// to keep track of lifespans
particles: Vec<Particles>,
model_cache: HashMap<&'static str, Model<ParticlePipeline>>,
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::<DotVoxData>("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::<ParticlePipeline, ParticlePipeline>::generate_mesh(
&Segment::from(vox.as_ref()),
(offset * lod_scale, Vec3::one() / lod_scale),
)
.0;
// TODO: from cache
let vox = assets::load_expect::<DotVoxData>(MODEL_KEY);
// TODO: from cache
let model = renderer
.create_model(mesh)
.expect("Failed to create particle model");
// TODO: from cache
let mesh = &Meshable::<ParticlePipeline, ParticlePipeline>::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<Shadow>,
focus_pos: Vec3<f32>,
) {
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<Particles> {
) -> Vec<ParticleInstance> {
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
}