use nalgebra_glm::{Vec3, Vec4};
#[derive(Debug, Clone)]
pub struct ParticleTextureUpload {
pub slot: u32,
pub rgba_data: Vec<u8>,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EmitterType {
Firework,
Fire,
Smoke,
Sparks,
Trail,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum EmitterShape {
#[default]
Point,
Sphere { radius: f32 },
Cone { angle: f32, height: f32 },
Box { half_extents: Vec3 },
}
#[derive(Debug, Clone)]
pub struct ColorGradient {
pub colors: Vec<(f32, Vec4)>,
}
impl Default for ColorGradient {
fn default() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(1.0, Vec4::new(1.0, 1.0, 1.0, 0.0)),
],
}
}
}
impl ColorGradient {
pub fn firework_trail() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 0.95, 1.0)),
(0.15, Vec4::new(1.0, 0.95, 0.7, 1.0)),
(0.4, Vec4::new(1.0, 0.7, 0.3, 0.85)),
(0.7, Vec4::new(1.0, 0.4, 0.1, 0.5)),
(1.0, Vec4::new(0.6, 0.15, 0.0, 0.0)),
],
}
}
pub fn firework_explosion(base_color: Vec3) -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(
0.05,
Vec4::new(
(base_color.x + 1.0) * 0.5,
(base_color.y + 1.0) * 0.5,
(base_color.z + 1.0) * 0.5,
1.0,
),
),
(
0.15,
Vec4::new(base_color.x, base_color.y, base_color.z, 1.0),
),
(
0.4,
Vec4::new(base_color.x, base_color.y, base_color.z, 0.95),
),
(
0.65,
Vec4::new(
base_color.x * 0.85,
base_color.y * 0.85,
base_color.z * 0.85,
0.7,
),
),
(
0.85,
Vec4::new(
base_color.x * 0.5,
base_color.y * 0.5,
base_color.z * 0.5,
0.3,
),
),
(1.0, Vec4::new(0.1, 0.1, 0.1, 0.0)),
],
}
}
pub fn firework_willow(base_color: Vec3) -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 0.95, 1.0)),
(
0.1,
Vec4::new(base_color.x, base_color.y, base_color.z, 1.0),
),
(
0.3,
Vec4::new(base_color.x, base_color.y, base_color.z, 0.9),
),
(
0.6,
Vec4::new(
base_color.x * 0.7,
base_color.y * 0.8,
base_color.z * 0.6,
0.6,
),
),
(
0.85,
Vec4::new(
base_color.x * 0.4,
base_color.y * 0.5,
base_color.z * 0.3,
0.25,
),
),
(1.0, Vec4::new(0.1, 0.1, 0.05, 0.0)),
],
}
}
pub fn firework_glitter() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.2, Vec4::new(1.0, 1.0, 0.9, 1.0)),
(0.5, Vec4::new(1.0, 0.95, 0.7, 0.9)),
(0.8, Vec4::new(1.0, 0.85, 0.5, 0.5)),
(1.0, Vec4::new(0.8, 0.6, 0.2, 0.0)),
],
}
}
pub fn firework_crackle() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.1, Vec4::new(1.0, 0.95, 0.8, 1.0)),
(0.3, Vec4::new(1.0, 0.8, 0.4, 0.9)),
(0.6, Vec4::new(1.0, 0.5, 0.1, 0.6)),
(1.0, Vec4::new(0.4, 0.1, 0.0, 0.0)),
],
}
}
pub fn flash_burst() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.15, Vec4::new(1.0, 1.0, 0.95, 1.0)),
(0.4, Vec4::new(1.0, 0.95, 0.8, 0.7)),
(0.7, Vec4::new(1.0, 0.9, 0.6, 0.3)),
(1.0, Vec4::new(1.0, 0.8, 0.4, 0.0)),
],
}
}
pub fn comet_trail() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.1, Vec4::new(1.0, 0.98, 0.9, 1.0)),
(0.25, Vec4::new(1.0, 0.9, 0.6, 0.95)),
(0.5, Vec4::new(1.0, 0.7, 0.3, 0.8)),
(0.75, Vec4::new(1.0, 0.4, 0.1, 0.5)),
(1.0, Vec4::new(0.5, 0.1, 0.0, 0.0)),
],
}
}
pub fn palm_frond(base_color: Vec3) -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(
0.08,
Vec4::new(base_color.x, base_color.y, base_color.z, 1.0),
),
(
0.25,
Vec4::new(base_color.x, base_color.y, base_color.z, 0.95),
),
(
0.5,
Vec4::new(
base_color.x * 0.9,
base_color.y * 0.9,
base_color.z * 0.9,
0.85,
),
),
(
0.75,
Vec4::new(
base_color.x * 0.7,
base_color.y * 0.7,
base_color.z * 0.7,
0.5,
),
),
(
1.0,
Vec4::new(
base_color.x * 0.3,
base_color.y * 0.3,
base_color.z * 0.3,
0.0,
),
),
],
}
}
pub fn crossette() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.1, Vec4::new(1.0, 0.95, 0.85, 1.0)),
(0.3, Vec4::new(1.0, 0.85, 0.6, 0.9)),
(0.6, Vec4::new(1.0, 0.6, 0.2, 0.7)),
(1.0, Vec4::new(0.6, 0.2, 0.0, 0.0)),
],
}
}
pub fn strobe() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(0.5, Vec4::new(1.0, 1.0, 1.0, 1.0)),
(1.0, Vec4::new(0.8, 0.8, 0.8, 0.0)),
],
}
}
pub fn fire() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 0.9, 1.0)),
(0.2, Vec4::new(1.0, 0.9, 0.4, 1.0)),
(0.4, Vec4::new(1.0, 0.5, 0.1, 0.9)),
(0.7, Vec4::new(0.9, 0.2, 0.0, 0.6)),
(1.0, Vec4::new(0.3, 0.05, 0.0, 0.0)),
],
}
}
pub fn smoke() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(0.4, 0.4, 0.45, 0.0)),
(0.1, Vec4::new(0.5, 0.5, 0.55, 0.6)),
(0.4, Vec4::new(0.55, 0.55, 0.6, 0.5)),
(0.7, Vec4::new(0.6, 0.6, 0.65, 0.3)),
(1.0, Vec4::new(0.65, 0.65, 0.7, 0.0)),
],
}
}
pub fn sparks() -> Self {
Self {
colors: vec![
(0.0, Vec4::new(1.0, 1.0, 0.8, 1.0)),
(0.3, Vec4::new(1.0, 0.8, 0.2, 1.0)),
(0.7, Vec4::new(1.0, 0.4, 0.0, 0.7)),
(1.0, Vec4::new(0.5, 0.1, 0.0, 0.0)),
],
}
}
pub fn to_gpu_data(&self) -> [[f32; 8]; 8] {
let mut data = [[0.0f32; 8]; 8];
for (index, (time, color)) in self.colors.iter().take(8).enumerate() {
data[index] = [*time, color.x, color.y, color.z, color.w, 0.0, 0.0, 0.0];
}
data
}
pub fn color_count(&self) -> u32 {
self.colors.len().min(8) as u32
}
}
#[derive(Debug, Clone)]
pub struct ParticleEmitter {
pub emitter_type: EmitterType,
pub shape: EmitterShape,
pub position: Vec3,
pub direction: Vec3,
pub spawn_rate: f32,
pub burst_count: u32,
pub particle_lifetime_min: f32,
pub particle_lifetime_max: f32,
pub initial_velocity_min: f32,
pub initial_velocity_max: f32,
pub velocity_spread: f32,
pub gravity: Vec3,
pub drag: f32,
pub size_start: f32,
pub size_end: f32,
pub color_gradient: ColorGradient,
pub emissive_strength: f32,
pub enabled: bool,
pub accumulated_spawn: f32,
pub one_shot: bool,
pub has_fired: bool,
pub turbulence_strength: f32,
pub turbulence_frequency: f32,
pub texture_index: u32,
}
impl Default for ParticleEmitter {
fn default() -> Self {
Self {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Point,
position: Vec3::zeros(),
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 100.0,
burst_count: 0,
particle_lifetime_min: 1.0,
particle_lifetime_max: 2.0,
initial_velocity_min: 1.0,
initial_velocity_max: 3.0,
velocity_spread: 0.3,
gravity: Vec3::new(0.0, -9.81, 0.0),
drag: 0.1,
size_start: 0.1,
size_end: 0.0,
color_gradient: ColorGradient::default(),
emissive_strength: 1.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.0,
turbulence_frequency: 1.0,
texture_index: 0,
}
}
}
impl ParticleEmitter {
pub fn firework_shell(position: Vec3, velocity: Vec3) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Point,
position,
direction: velocity.normalize(),
spawn_rate: 120.0,
burst_count: 0,
particle_lifetime_min: 0.15,
particle_lifetime_max: 0.4,
initial_velocity_min: velocity.magnitude() * 0.05,
initial_velocity_max: velocity.magnitude() * 0.15,
velocity_spread: 0.25,
gravity: Vec3::new(0.0, -4.0, 0.0),
drag: 0.1,
size_start: 0.15,
size_end: 0.03,
color_gradient: ColorGradient::firework_trail(),
emissive_strength: 5.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.2,
turbulence_frequency: 2.0,
texture_index: 0,
}
}
pub fn firework_explosion(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Sphere { radius: 0.5 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 2.0,
particle_lifetime_max: 3.5,
initial_velocity_min: 12.0,
initial_velocity_max: 25.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -4.0, 0.0),
drag: 0.4,
size_start: 0.25,
size_end: 0.04,
color_gradient: ColorGradient::firework_explosion(color),
emissive_strength: 8.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.3,
turbulence_frequency: 1.5,
texture_index: 0,
}
}
pub fn firework_willow(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Sphere { radius: 0.3 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 3.5,
particle_lifetime_max: 5.5,
initial_velocity_min: 8.0,
initial_velocity_max: 18.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -6.0, 0.0),
drag: 0.15,
size_start: 0.18,
size_end: 0.02,
color_gradient: ColorGradient::firework_willow(color),
emissive_strength: 6.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.1,
turbulence_frequency: 0.8,
texture_index: 0,
}
}
pub fn firework_chrysanthemum(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Sphere { radius: 0.2 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 2.5,
particle_lifetime_max: 4.0,
initial_velocity_min: 15.0,
initial_velocity_max: 30.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -2.5, 0.0),
drag: 0.6,
size_start: 0.2,
size_end: 0.05,
color_gradient: ColorGradient::firework_explosion(color),
emissive_strength: 10.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.15,
turbulence_frequency: 1.2,
texture_index: 0,
}
}
pub fn firework_ring(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Point,
position,
direction: Vec3::new(0.0, 0.0, 1.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 2.0,
particle_lifetime_max: 3.0,
initial_velocity_min: 18.0,
initial_velocity_max: 22.0,
velocity_spread: 0.15,
gravity: Vec3::new(0.0, -1.5, 0.0),
drag: 0.35,
size_start: 0.22,
size_end: 0.03,
color_gradient: ColorGradient::firework_explosion(color),
emissive_strength: 9.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.05,
turbulence_frequency: 1.0,
texture_index: 0,
}
}
pub fn firework_glitter(position: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 2.0 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 0.8,
particle_lifetime_max: 2.5,
initial_velocity_min: 1.0,
initial_velocity_max: 5.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -8.0, 0.0),
drag: 0.05,
size_start: 0.12,
size_end: 0.02,
color_gradient: ColorGradient::firework_glitter(),
emissive_strength: 12.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.8,
turbulence_frequency: 4.0,
texture_index: 0,
}
}
pub fn firework_crackle(position: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 1.5 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 0.3,
particle_lifetime_max: 0.8,
initial_velocity_min: 3.0,
initial_velocity_max: 10.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -12.0, 0.0),
drag: 0.02,
size_start: 0.08,
size_end: 0.01,
color_gradient: ColorGradient::firework_crackle(),
emissive_strength: 15.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 1.2,
turbulence_frequency: 6.0,
texture_index: 0,
}
}
pub fn flash_burst(position: Vec3) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Point,
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: 50,
particle_lifetime_min: 0.1,
particle_lifetime_max: 0.25,
initial_velocity_min: 2.0,
initial_velocity_max: 8.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, 0.0, 0.0),
drag: 0.0,
size_start: 1.5,
size_end: 0.3,
color_gradient: ColorGradient::flash_burst(),
emissive_strength: 25.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.0,
turbulence_frequency: 0.0,
texture_index: 0,
}
}
pub fn comet_shell(position: Vec3, velocity: Vec3) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Point,
position,
direction: velocity.normalize(),
spawn_rate: 200.0,
burst_count: 0,
particle_lifetime_min: 0.2,
particle_lifetime_max: 0.5,
initial_velocity_min: velocity.magnitude() * 0.02,
initial_velocity_max: velocity.magnitude() * 0.08,
velocity_spread: 0.15,
gravity: Vec3::new(0.0, -2.0, 0.0),
drag: 0.05,
size_start: 0.25,
size_end: 0.04,
color_gradient: ColorGradient::comet_trail(),
emissive_strength: 8.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.1,
turbulence_frequency: 1.5,
texture_index: 0,
}
}
pub fn palm_explosion(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Firework,
shape: EmitterShape::Sphere { radius: 0.2 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 3.0,
particle_lifetime_max: 5.0,
initial_velocity_min: 20.0,
initial_velocity_max: 35.0,
velocity_spread: 0.4,
gravity: Vec3::new(0.0, -8.0, 0.0),
drag: 0.25,
size_start: 0.22,
size_end: 0.03,
color_gradient: ColorGradient::palm_frond(color),
emissive_strength: 8.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.2,
turbulence_frequency: 1.0,
texture_index: 0,
}
}
pub fn crossette_burst(position: Vec3, color: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 0.1 },
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 0.5,
particle_lifetime_max: 1.2,
initial_velocity_min: 15.0,
initial_velocity_max: 25.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -5.0, 0.0),
drag: 0.3,
size_start: 0.15,
size_end: 0.02,
color_gradient: ColorGradient::firework_explosion(color),
emissive_strength: 12.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.5,
turbulence_frequency: 3.0,
texture_index: 0,
}
}
pub fn strobe_effect(position: Vec3, particle_count: u32) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Sphere { radius: 3.0 },
position,
direction: Vec3::new(0.0, -1.0, 0.0),
spawn_rate: 0.0,
burst_count: particle_count,
particle_lifetime_min: 1.5,
particle_lifetime_max: 3.0,
initial_velocity_min: 0.5,
initial_velocity_max: 2.0,
velocity_spread: std::f32::consts::PI,
gravity: Vec3::new(0.0, -6.0, 0.0),
drag: 0.1,
size_start: 0.1,
size_end: 0.02,
color_gradient: ColorGradient::strobe(),
emissive_strength: 20.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: true,
has_fired: false,
turbulence_strength: 0.3,
turbulence_frequency: 2.0,
texture_index: 0,
}
}
pub fn trailing_sparks(position: Vec3, velocity: Vec3) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Point,
position,
direction: (-velocity).normalize(),
spawn_rate: 80.0,
burst_count: 0,
particle_lifetime_min: 0.2,
particle_lifetime_max: 0.6,
initial_velocity_min: 1.0,
initial_velocity_max: 4.0,
velocity_spread: 0.5,
gravity: Vec3::new(0.0, -15.0, 0.0),
drag: 0.05,
size_start: 0.06,
size_end: 0.01,
color_gradient: ColorGradient::firework_glitter(),
emissive_strength: 10.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.8,
turbulence_frequency: 5.0,
texture_index: 0,
}
}
pub fn fire(position: Vec3) -> Self {
Self {
emitter_type: EmitterType::Fire,
shape: EmitterShape::Cone {
angle: 0.3,
height: 0.1,
},
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 200.0,
burst_count: 0,
particle_lifetime_min: 0.5,
particle_lifetime_max: 1.5,
initial_velocity_min: 2.0,
initial_velocity_max: 5.0,
velocity_spread: 0.4,
gravity: Vec3::new(0.0, 2.0, 0.0),
drag: 0.5,
size_start: 0.3,
size_end: 0.1,
color_gradient: ColorGradient::fire(),
emissive_strength: 4.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 1.5,
turbulence_frequency: 3.0,
texture_index: 0,
}
}
pub fn smoke(position: Vec3) -> Self {
Self {
emitter_type: EmitterType::Smoke,
shape: EmitterShape::Cone {
angle: 0.1,
height: 0.2,
},
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 50.0,
burst_count: 0,
particle_lifetime_min: 2.5,
particle_lifetime_max: 4.5,
initial_velocity_min: 0.8,
initial_velocity_max: 1.8,
velocity_spread: 0.15,
gravity: Vec3::new(0.0, 0.6, 0.0),
drag: 0.3,
size_start: 0.4,
size_end: 1.2,
color_gradient: ColorGradient::smoke(),
emissive_strength: 0.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.3,
turbulence_frequency: 0.4,
texture_index: 0,
}
}
pub fn sparks(position: Vec3) -> Self {
Self {
emitter_type: EmitterType::Sparks,
shape: EmitterShape::Point,
position,
direction: Vec3::new(0.0, 1.0, 0.0),
spawn_rate: 50.0,
burst_count: 0,
particle_lifetime_min: 0.3,
particle_lifetime_max: 0.8,
initial_velocity_min: 5.0,
initial_velocity_max: 10.0,
velocity_spread: 0.8,
gravity: Vec3::new(0.0, -15.0, 0.0),
drag: 0.1,
size_start: 0.05,
size_end: 0.01,
color_gradient: ColorGradient::sparks(),
emissive_strength: 6.0,
enabled: true,
accumulated_spawn: 0.0,
one_shot: false,
has_fired: false,
turbulence_strength: 0.0,
turbulence_frequency: 1.0,
texture_index: 0,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ParticleSystemStats {
pub active_particles: u32,
pub total_spawned: u64,
pub total_died: u64,
}