mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add particle lifespan
This commit is contained in:
parent
39b676cd8f
commit
da5f4828a5
@ -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,
|
||||
},
|
||||
],
|
||||
|
@ -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>>;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user