use crate::{
core::{
algebra::{Vector2, Vector3},
color::Color,
color_gradient::ColorGradient,
inspect::{Inspect, PropertyInfo},
math::TriangleDefinition,
pool::Handle,
visitor::prelude::*,
},
resource::texture::Texture,
scene::{
base::{Base, BaseBuilder},
graph::Graph,
node::Node,
particle_system::{
draw::{DrawData, Vertex},
emitter::{Emit, Emitter},
particle::Particle,
},
},
};
use std::{
cmp::Ordering,
fmt::Debug,
ops::{Deref, DerefMut},
};
pub(crate) mod draw;
pub mod emitter;
pub mod particle;
#[derive(Copy, Clone, Debug)]
pub enum ParticleLimit {
Unlimited,
Strict(u32),
}
impl Visit for ParticleLimit {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
visitor.enter_region(name)?;
let mut amount = match self {
Self::Unlimited => -1,
Self::Strict(value) => *value as i32,
};
amount.visit("Amount", visitor)?;
if visitor.is_reading() {
*self = if amount < 0 {
Self::Unlimited
} else {
Self::Strict(amount as u32)
};
}
visitor.leave_region()
}
}
#[derive(Debug, Visit, Inspect)]
pub struct ParticleSystem {
base: Base,
#[inspect(skip)]
particles: Vec<Particle>,
#[inspect(skip)]
free_particles: Vec<u32>,
pub emitters: Vec<Emitter>,
texture: Option<Texture>,
acceleration: Vector3<f32>,
#[visit(rename = "ColorGradient")]
color_over_lifetime: Option<ColorGradient>,
#[visit(optional)] soft_boundary_sharpness_factor: f32,
#[visit(optional)] enabled: bool,
}
impl Deref for ParticleSystem {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for ParticleSystem {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl ParticleSystem {
pub fn raw_copy(&self) -> Self {
Self {
base: self.base.raw_copy(),
particles: self.particles.clone(),
free_particles: self.free_particles.clone(),
emitters: self.emitters.clone(),
texture: self.texture.clone(),
acceleration: self.acceleration,
color_over_lifetime: self.color_over_lifetime.clone(),
soft_boundary_sharpness_factor: self.soft_boundary_sharpness_factor,
enabled: self.enabled,
}
}
pub fn acceleration(&self) -> Vector3<f32> {
self.acceleration
}
pub fn set_acceleration(&mut self, accel: Vector3<f32>) {
self.acceleration = accel;
}
pub fn set_color_over_lifetime_gradient(&mut self, gradient: ColorGradient) {
self.color_over_lifetime = Some(gradient)
}
pub fn soft_boundary_sharpness_factor(&self) -> f32 {
self.soft_boundary_sharpness_factor
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_soft_boundary_sharpness_factor(&mut self, factor: f32) {
self.soft_boundary_sharpness_factor = factor;
}
pub fn clear_particles(&mut self) {
self.particles.clear();
self.free_particles.clear();
for emitter in self.emitters.iter_mut() {
emitter.alive_particles = 0;
}
}
pub fn update(&mut self, dt: f32) {
if !self.enabled {
return;
}
for emitter in self.emitters.iter_mut() {
emitter.tick(dt);
}
for (i, emitter) in self.emitters.iter_mut().enumerate() {
for _ in 0..emitter.particles_to_spawn {
let mut particle = Particle {
emitter_index: i as u32,
..Particle::default()
};
emitter.alive_particles += 1;
emitter.emit(&mut particle);
if let Some(free_index) = self.free_particles.pop() {
self.particles[free_index as usize] = particle;
} else {
self.particles.push(particle);
}
}
}
let acceleration_offset = self.acceleration.scale(dt * dt);
for (i, particle) in self.particles.iter_mut().enumerate() {
if particle.alive {
particle.lifetime += dt;
if particle.lifetime >= particle.initial_lifetime {
self.free_particles.push(i as u32);
if let Some(emitter) = self.emitters.get_mut(particle.emitter_index as usize) {
emitter.alive_particles -= 1;
}
particle.alive = false;
particle.lifetime = particle.initial_lifetime;
} else {
particle.velocity += acceleration_offset;
particle.position += particle.velocity;
particle.size += particle.size_modifier * dt;
if particle.size < 0.0 {
particle.size = 0.0;
}
particle.rotation += particle.rotation_speed * dt;
if let Some(color_over_lifetime) = &self.color_over_lifetime {
let k = particle.lifetime / particle.initial_lifetime;
particle.color = color_over_lifetime.get_color(k);
} else {
particle.color = Color::WHITE;
}
}
}
}
}
pub fn generate_draw_data(
&self,
sorted_particles: &mut Vec<u32>,
draw_data: &mut DrawData,
camera_pos: &Vector3<f32>,
) {
sorted_particles.clear();
for (i, particle) in self.particles.iter().enumerate() {
if particle.alive {
let actual_position = particle.position + self.base.global_position();
particle
.sqr_distance_to_camera
.set((camera_pos - actual_position).norm_squared());
sorted_particles.push(i as u32);
}
}
let particles = &self.particles;
sorted_particles.sort_by(|a, b| {
let particle_a = particles.get(*a as usize).unwrap();
let particle_b = particles.get(*b as usize).unwrap();
if particle_a.sqr_distance_to_camera < particle_b.sqr_distance_to_camera {
Ordering::Greater
} else if particle_a.sqr_distance_to_camera > particle_b.sqr_distance_to_camera {
Ordering::Less
} else {
Ordering::Equal
}
});
draw_data.clear();
for (i, particle_index) in sorted_particles.iter().enumerate() {
let particle = self.particles.get(*particle_index as usize).unwrap();
let linear_color = particle.color.srgb_to_linear();
draw_data.vertices.push(Vertex {
position: particle.position,
tex_coord: Vector2::default(),
size: particle.size,
rotation: particle.rotation,
color: linear_color,
});
draw_data.vertices.push(Vertex {
position: particle.position,
tex_coord: Vector2::new(1.0, 0.0),
size: particle.size,
rotation: particle.rotation,
color: linear_color,
});
draw_data.vertices.push(Vertex {
position: particle.position,
tex_coord: Vector2::new(1.0, 1.0),
size: particle.size,
rotation: particle.rotation,
color: linear_color,
});
draw_data.vertices.push(Vertex {
position: particle.position,
tex_coord: Vector2::new(0.0, 1.0),
size: particle.size,
rotation: particle.rotation,
color: linear_color,
});
let base_index = (i * 4) as u32;
draw_data.triangles.push(TriangleDefinition([
base_index,
base_index + 1,
base_index + 2,
]));
draw_data.triangles.push(TriangleDefinition([
base_index,
base_index + 2,
base_index + 3,
]));
}
}
pub fn set_texture(&mut self, texture: Option<Texture>) {
self.texture = texture
}
pub fn texture(&self) -> Option<Texture> {
self.texture.clone()
}
pub fn texture_ref(&self) -> Option<&Texture> {
self.texture.as_ref()
}
}
impl Default for ParticleSystem {
fn default() -> Self {
ParticleSystemBuilder::new(BaseBuilder::new()).build_particle_system()
}
}
pub struct ParticleSystemBuilder {
base_builder: BaseBuilder,
emitters: Vec<Emitter>,
texture: Option<Texture>,
acceleration: Vector3<f32>,
particles: Vec<Particle>,
color_over_lifetime: Option<ColorGradient>,
soft_boundary_sharpness_factor: f32,
enabled: bool,
}
impl ParticleSystemBuilder {
pub fn new(base_builder: BaseBuilder) -> Self {
Self {
base_builder,
emitters: Default::default(),
texture: None,
particles: Default::default(),
acceleration: Vector3::new(0.0, -9.81, 0.0),
color_over_lifetime: None,
soft_boundary_sharpness_factor: 2.5,
enabled: true,
}
}
pub fn with_emitters(mut self, emitters: Vec<Emitter>) -> Self {
self.emitters = emitters;
self
}
pub fn with_texture(mut self, texture: Texture) -> Self {
self.texture = Some(texture);
self
}
pub fn with_opt_texture(mut self, texture: Option<Texture>) -> Self {
self.texture = texture;
self
}
pub fn with_soft_boundary_sharpness_factor(mut self, factor: f32) -> Self {
self.soft_boundary_sharpness_factor = factor;
self
}
pub fn with_acceleration(mut self, acceleration: Vector3<f32>) -> Self {
self.acceleration = acceleration;
self
}
pub fn with_color_over_lifetime_gradient(mut self, color_over_lifetime: ColorGradient) -> Self {
self.color_over_lifetime = Some(color_over_lifetime);
self
}
pub fn with_particles(mut self, particles: Vec<Particle>) -> Self {
self.particles = particles;
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
fn build_particle_system(self) -> ParticleSystem {
ParticleSystem {
base: self.base_builder.build_base(),
particles: self.particles,
free_particles: Vec::new(),
emitters: self.emitters,
texture: self.texture.clone(),
acceleration: self.acceleration,
color_over_lifetime: self.color_over_lifetime,
soft_boundary_sharpness_factor: self.soft_boundary_sharpness_factor,
enabled: self.enabled,
}
}
pub fn build_node(self) -> Node {
Node::ParticleSystem(self.build_particle_system())
}
pub fn build(self, graph: &mut Graph) -> Handle<Node> {
graph.add_node(self.build_node())
}
}