veloren/voxygen/src/scene/particle.rs

298 lines
10 KiB
Rust
Raw Normal View History

2020-07-05 12:10:58 +00:00
use super::SceneData;
use crate::{
mesh::Meshable,
render::{
2020-07-15 15:45:47 +00:00
pipelines::particle::ParticleMode, Consts, Globals, Instances, Light, Model,
ParticleInstance, ParticlePipeline, Renderer, Shadow,
2020-07-05 12:10:58 +00:00
},
};
use common::{
assets,
2020-07-15 15:45:47 +00:00
comp::{object, Body, CharacterState, Pos},
2020-07-05 12:10:58 +00:00
figure::Segment,
outcome::Outcome,
2020-07-05 12:10:58 +00:00
};
use dot_vox::DotVoxData;
use hashbrown::HashMap;
use rand::Rng;
2020-07-15 15:45:47 +00:00
use specs::{Join, WorldExt};
2020-07-11 10:37:19 +00:00
use std::time::{Duration, Instant};
use vek::*;
2020-07-11 10:37:19 +00:00
2020-07-05 12:10:58 +00:00
struct Particles {
2020-07-11 10:37:19 +00:00
alive_until: Instant, // created_at + lifespan
2020-07-21 15:48:20 +00:00
instance: ParticleInstance,
2020-07-05 12:10:58 +00:00
}
pub struct ParticleMgr {
2020-07-15 15:45:47 +00:00
// keep track of lifespans
2020-07-11 10:37:19 +00:00
particles: Vec<Particles>,
2020-07-21 15:48:20 +00:00
instances: Instances<ParticleInstance>,
2020-07-11 10:37:19 +00:00
model_cache: HashMap<&'static str, Model<ParticlePipeline>>,
2020-07-05 12:10:58 +00:00
}
const MODEL_KEY: &str = "voxygen.voxel.particle";
2020-07-11 10:37:19 +00:00
2020-07-05 12:10:58 +00:00
impl ParticleMgr {
pub fn new(renderer: &mut Renderer) -> Self {
Self {
2020-07-11 10:37:19 +00:00
particles: Vec::new(),
2020-07-25 15:56:50 +00:00
instances: default_instances(renderer),
model_cache: default_cache(renderer),
2020-07-05 12:10:58 +00:00
}
}
2020-07-21 15:48:20 +00:00
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..64 {
self.particles.push(Particles {
alive_until: now + Duration::from_secs(4),
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, *pos + Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0, 1.0) * power)),
});
}
},
_ => {},
}
}
2020-07-15 15:45:47 +00:00
pub fn maintain(&mut self, renderer: &mut Renderer, scene_data: &SceneData) {
2020-07-25 15:46:45 +00:00
if scene_data.particles_enabled {
let now = Instant::now();
2020-07-05 12:10:58 +00:00
2020-07-25 15:46:45 +00:00
// remove dead particles
self.particles.retain(|p| p.alive_until > now);
2020-07-21 15:48:20 +00:00
2020-07-25 15:46:45 +00:00
self.maintain_body_particles(scene_data);
self.maintain_boost_particles(scene_data);
2020-07-25 15:46:45 +00:00
self.upload_particles(renderer);
} else {
self.particles.clear();
}
}
2020-07-21 15:48:20 +00:00
2020-07-25 15:46:45 +00:00
fn upload_particles(&mut self, renderer: &mut Renderer) {
2020-07-21 15:48:20 +00:00
let all_cpu_instances = self
.particles
.iter()
.map(|p| p.instance)
.collect::<Vec<ParticleInstance>>();
2020-07-25 15:46:45 +00:00
// TODO: optimise buffer writes
let gpu_instances = renderer
2020-07-21 15:48:20 +00:00
.create_instances(&all_cpu_instances)
.expect("Failed to upload particle instances to the GPU!");
2020-07-25 15:46:45 +00:00
self.instances = gpu_instances;
}
2020-07-25 15:46:45 +00:00
fn maintain_body_particles(&mut self, scene_data: &SceneData) {
2020-07-19 07:16:06 +00:00
let ecs = scene_data.state.ecs();
for (_i, (_entity, body, pos)) in (
2020-07-05 12:10:58 +00:00
&ecs.entities(),
2020-07-15 15:45:47 +00:00
&ecs.read_storage::<Body>(),
2020-07-19 07:16:06 +00:00
&ecs.read_storage::<Pos>(),
2020-07-05 12:10:58 +00:00
)
.join()
.enumerate()
{
2020-07-15 15:45:47 +00:00
match body {
Body::Object(object::Body::CampfireLit) => {
2020-07-25 15:46:45 +00:00
self.maintain_campfirelit_particles(scene_data, pos)
2020-07-19 07:16:06 +00:00
},
Body::Object(object::Body::BoltFire) => {
2020-07-25 15:46:45 +00:00
self.maintain_boltfire_particles(scene_data, pos)
2020-07-15 15:45:47 +00:00
},
2020-07-19 07:16:06 +00:00
Body::Object(object::Body::BoltFireBig) => {
2020-07-25 15:46:45 +00:00
self.maintain_boltfirebig_particles(scene_data, pos)
2020-07-19 07:16:06 +00:00
},
2020-07-25 15:46:45 +00:00
Body::Object(object::Body::Bomb) => self.maintain_bomb_particles(scene_data, pos),
2020-07-15 15:45:47 +00:00
_ => {},
}
}
}
2020-07-25 15:46:45 +00:00
fn maintain_campfirelit_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
2020-07-19 07:16:06 +00:00
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),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0),
2020-07-19 07:16:06 +00:00
});
self.particles.push(Particles {
alive_until: now + Duration::from_secs(10),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0),
2020-07-19 07:16:06 +00:00
});
}
2020-07-25 15:46:45 +00:00
fn maintain_boltfire_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
2020-07-19 07:16:06 +00:00
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),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireFire, pos.0),
2020-07-19 07:16:06 +00:00
});
self.particles.push(Particles {
alive_until: now + Duration::from_secs(1),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0),
2020-07-19 07:16:06 +00:00
});
}
2020-07-25 15:46:45 +00:00
fn maintain_boltfirebig_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
2020-07-19 07:16:06 +00:00
let time = scene_data.state.get_time();
let now = Instant::now();
let mut rng = rand::thread_rng();
2020-07-21 15:48:20 +00:00
// fire
2020-07-19 07:16:06 +00:00
self.particles.push(Particles {
alive_until: now + Duration::from_millis(250),
2020-07-21 15:48:20 +00:00
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),
2020-07-19 07:16:06 +00:00
});
2020-07-21 15:48:20 +00:00
// smoke
2020-07-19 07:16:06 +00:00
self.particles.push(Particles {
alive_until: now + Duration::from_secs(2),
2020-07-21 15:48:20 +00:00
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),
2020-07-19 07:16:06 +00:00
});
}
2020-07-25 15:46:45 +00:00
fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
2020-07-19 07:16:06 +00:00
let time = scene_data.state.get_time();
let now = Instant::now();
let mut rng = rand::thread_rng();
2020-07-21 15:48:20 +00:00
// sparks
2020-07-19 07:16:06 +00:00
self.particles.push(Particles {
alive_until: now + Duration::from_millis(1500),
2020-07-21 15:48:20 +00:00
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),
2020-07-19 07:16:06 +00:00
});
2020-07-21 15:48:20 +00:00
// smoke
2020-07-19 07:16:06 +00:00
self.particles.push(Particles {
alive_until: now + Duration::from_secs(2),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(time, rng.gen(), ParticleMode::CampfireSmoke, pos.0),
2020-07-19 07:16:06 +00:00
});
}
2020-07-25 15:46:45 +00:00
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();
2020-07-15 15:45:47 +00:00
let mut rng = rand::thread_rng();
2020-07-15 15:45:47 +00:00
for (_i, (_entity, pos, character_state)) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<CharacterState>(),
)
.join()
.enumerate()
{
2020-07-15 15:45:47 +00:00
if let CharacterState::Boost(_) = character_state {
self.particles.push(Particles {
alive_until: now + Duration::from_secs(15),
2020-07-21 15:48:20 +00:00
instance: ParticleInstance::new(
time,
rng.gen(),
ParticleMode::CampfireSmoke,
pos.0,
),
2020-07-11 10:37:19 +00:00
});
}
2020-07-05 12:10:58 +00:00
}
}
pub fn render(
&self,
renderer: &mut Renderer,
2020-07-25 15:46:45 +00:00
scene_data: &SceneData,
2020-07-05 12:10:58 +00:00
globals: &Consts<Globals>,
lights: &Consts<Light>,
shadows: &Consts<Shadow>,
) {
2020-07-25 15:46:45 +00:00
if scene_data.particles_enabled {
let model = &self
.model_cache
.get(MODEL_KEY)
.expect("Expected particle model in cache");
2020-07-19 07:16:06 +00:00
2020-07-25 15:46:45 +00:00
renderer.render_particles(model, globals, &self.instances, lights, shadows);
}
2020-07-05 12:10:58 +00:00
}
}
2020-07-25 15:56:50 +00:00
fn default_instances(renderer: &mut Renderer) -> Instances<ParticleInstance> {
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<ParticlePipeline>> {
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::<DotVoxData>(MODEL_KEY);
let mesh = &Meshable::<ParticlePipeline, ParticlePipeline>::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
}