use crate::ecs::sprite::components::SpriteBlendMode;
use nalgebra_glm::Vec2;
#[derive(Debug, Clone, Copy)]
pub struct Particle2D {
pub position: Vec2,
pub velocity: Vec2,
pub age: f32,
pub lifetime: f32,
pub rotation: f32,
pub rotation_speed: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum EmitterShape2D {
#[default]
Point,
Circle {
radius: f32,
},
Rectangle {
half_extents: Vec2,
},
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct ColorRange2D {
pub start: [f32; 4],
pub end: [f32; 4],
}
impl Default for ColorRange2D {
fn default() -> Self {
Self {
start: [1.0, 1.0, 1.0, 1.0],
end: [1.0, 1.0, 1.0, 0.0],
}
}
}
impl ColorRange2D {
pub fn new(start: [f32; 4], end: [f32; 4]) -> Self {
Self { start, end }
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SpriteParticleEmitter {
pub enabled: bool,
pub one_shot: bool,
pub has_fired: bool,
pub spawn_rate: f32,
pub burst_count: u32,
pub accumulated_spawn: f32,
pub max_particles: u32,
pub lifetime_min: f32,
pub lifetime_max: f32,
pub velocity_min: Vec2,
pub velocity_max: Vec2,
pub gravity: Vec2,
pub drag: f32,
pub size_start: Vec2,
pub size_end: Vec2,
pub color: ColorRange2D,
pub rotation_min: f32,
pub rotation_max: f32,
pub rotation_speed_min: f32,
pub rotation_speed_max: f32,
pub blend_mode: SpriteBlendMode,
pub texture_index: u32,
pub uv_min: Vec2,
pub uv_max: Vec2,
pub depth: f32,
pub anchor: Vec2,
pub shape: EmitterShape2D,
#[serde(skip)]
pub particles: Vec<Particle2D>,
#[serde(skip)]
pub rng_state: u64,
}
impl Default for SpriteParticleEmitter {
fn default() -> Self {
Self {
enabled: true,
one_shot: false,
has_fired: false,
spawn_rate: 50.0,
burst_count: 0,
accumulated_spawn: 0.0,
max_particles: 1000,
lifetime_min: 0.5,
lifetime_max: 1.5,
velocity_min: Vec2::new(-50.0, -50.0),
velocity_max: Vec2::new(50.0, 50.0),
gravity: Vec2::new(0.0, -200.0),
drag: 0.1,
size_start: Vec2::new(8.0, 8.0),
size_end: Vec2::new(2.0, 2.0),
color: ColorRange2D::default(),
rotation_min: 0.0,
rotation_max: 0.0,
rotation_speed_min: 0.0,
rotation_speed_max: 0.0,
blend_mode: SpriteBlendMode::Alpha,
texture_index: 0,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
depth: 0.0,
anchor: Vec2::new(0.0, 0.0),
shape: EmitterShape2D::default(),
particles: Vec::new(),
rng_state: 0,
}
}
}
static EMITTER_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
fn next_rng_seed() -> u64 {
EMITTER_COUNTER
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407)
}
impl SpriteParticleEmitter {
pub fn with_blend_mode(mut self, blend_mode: SpriteBlendMode) -> Self {
self.blend_mode = blend_mode;
self
}
pub fn with_shape(mut self, shape: EmitterShape2D) -> Self {
self.shape = shape;
self
}
pub fn with_texture(mut self, texture_index: u32) -> Self {
self.texture_index = texture_index;
self
}
pub fn with_uv(mut self, uv_min: Vec2, uv_max: Vec2) -> Self {
self.uv_min = uv_min;
self.uv_max = uv_max;
self
}
pub fn with_color(mut self, color: ColorRange2D) -> Self {
self.color = color;
self
}
pub fn with_gravity(mut self, gravity: Vec2) -> Self {
self.gravity = gravity;
self
}
pub fn with_depth(mut self, depth: f32) -> Self {
self.depth = depth;
self
}
pub fn with_size(mut self, start: Vec2, end: Vec2) -> Self {
self.size_start = start;
self.size_end = end;
self
}
pub fn with_velocity(mut self, min: Vec2, max: Vec2) -> Self {
self.velocity_min = min;
self.velocity_max = max;
self
}
pub fn with_lifetime(mut self, min: f32, max: f32) -> Self {
self.lifetime_min = min;
self.lifetime_max = max;
self
}
pub fn with_rotation(mut self, min: f32, max: f32) -> Self {
self.rotation_min = min;
self.rotation_max = max;
self
}
pub fn with_rotation_speed(mut self, min: f32, max: f32) -> Self {
self.rotation_speed_min = min;
self.rotation_speed_max = max;
self
}
pub fn with_spawn_rate(mut self, rate: f32) -> Self {
self.spawn_rate = rate;
self
}
pub fn with_burst(mut self, count: u32) -> Self {
self.burst_count = count;
self.one_shot = true;
self
}
pub fn with_max_particles(mut self, max: u32) -> Self {
self.max_particles = max;
self
}
pub fn explosion(position_x: f32, position_y: f32) -> Self {
Self {
enabled: true,
one_shot: true,
has_fired: false,
spawn_rate: 0.0,
burst_count: 64,
accumulated_spawn: 0.0,
max_particles: 256,
lifetime_min: 0.3,
lifetime_max: 0.8,
velocity_min: Vec2::new(-200.0, -200.0),
velocity_max: Vec2::new(200.0, 200.0),
gravity: Vec2::new(0.0, -300.0),
drag: 0.2,
size_start: Vec2::new(6.0, 6.0),
size_end: Vec2::new(1.0, 1.0),
color: ColorRange2D::new([1.0, 0.8, 0.2, 1.0], [1.0, 0.2, 0.0, 0.0]),
rotation_min: 0.0,
rotation_max: std::f32::consts::TAU,
rotation_speed_min: -3.0,
rotation_speed_max: 3.0,
blend_mode: SpriteBlendMode::Additive,
texture_index: 0,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
depth: 0.0,
anchor: Vec2::new(position_x, position_y),
shape: EmitterShape2D::Point,
particles: Vec::new(),
rng_state: next_rng_seed(),
}
}
pub fn fire_trail(position_x: f32, position_y: f32) -> Self {
Self {
enabled: true,
one_shot: false,
has_fired: false,
spawn_rate: 80.0,
burst_count: 0,
accumulated_spawn: 0.0,
max_particles: 512,
lifetime_min: 0.3,
lifetime_max: 0.8,
velocity_min: Vec2::new(-20.0, 30.0),
velocity_max: Vec2::new(20.0, 80.0),
gravity: Vec2::new(0.0, 50.0),
drag: 0.3,
size_start: Vec2::new(8.0, 8.0),
size_end: Vec2::new(2.0, 2.0),
color: ColorRange2D::new([1.0, 0.6, 0.1, 1.0], [1.0, 0.1, 0.0, 0.0]),
rotation_min: 0.0,
rotation_max: std::f32::consts::TAU,
rotation_speed_min: -2.0,
rotation_speed_max: 2.0,
blend_mode: SpriteBlendMode::Additive,
texture_index: 0,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
depth: 0.0,
anchor: Vec2::new(position_x, position_y),
shape: EmitterShape2D::Circle { radius: 4.0 },
particles: Vec::new(),
rng_state: next_rng_seed(),
}
}
pub fn smoke(position_x: f32, position_y: f32) -> Self {
Self {
enabled: true,
one_shot: false,
has_fired: false,
spawn_rate: 30.0,
burst_count: 0,
accumulated_spawn: 0.0,
max_particles: 256,
lifetime_min: 1.0,
lifetime_max: 2.5,
velocity_min: Vec2::new(-15.0, 20.0),
velocity_max: Vec2::new(15.0, 60.0),
gravity: Vec2::new(0.0, 10.0),
drag: 0.2,
size_start: Vec2::new(6.0, 6.0),
size_end: Vec2::new(20.0, 20.0),
color: ColorRange2D::new([0.5, 0.5, 0.5, 0.6], [0.3, 0.3, 0.3, 0.0]),
rotation_min: 0.0,
rotation_max: std::f32::consts::TAU,
rotation_speed_min: -0.5,
rotation_speed_max: 0.5,
blend_mode: SpriteBlendMode::Alpha,
texture_index: 0,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
depth: 0.0,
anchor: Vec2::new(position_x, position_y),
shape: EmitterShape2D::Circle { radius: 3.0 },
particles: Vec::new(),
rng_state: next_rng_seed(),
}
}
pub fn sparks(position_x: f32, position_y: f32) -> Self {
Self {
enabled: true,
one_shot: true,
has_fired: false,
spawn_rate: 0.0,
burst_count: 32,
accumulated_spawn: 0.0,
max_particles: 128,
lifetime_min: 0.2,
lifetime_max: 0.5,
velocity_min: Vec2::new(-150.0, 50.0),
velocity_max: Vec2::new(150.0, 250.0),
gravity: Vec2::new(0.0, -400.0),
drag: 0.05,
size_start: Vec2::new(3.0, 3.0),
size_end: Vec2::new(1.0, 1.0),
color: ColorRange2D::new([1.0, 1.0, 0.8, 1.0], [1.0, 0.5, 0.0, 0.0]),
rotation_min: 0.0,
rotation_max: 0.0,
rotation_speed_min: 0.0,
rotation_speed_max: 0.0,
blend_mode: SpriteBlendMode::Additive,
texture_index: 0,
uv_min: Vec2::new(0.0, 0.0),
uv_max: Vec2::new(1.0, 1.0),
depth: 0.0,
anchor: Vec2::new(position_x, position_y),
shape: EmitterShape2D::Point,
particles: Vec::new(),
rng_state: next_rng_seed(),
}
}
}