use crate::tui::ecs::components::{Position, Sprite, TermColor, ZIndex};
use crate::tui::ecs::world::*;
use freecs::Entity;
use rand::Rng;
#[derive(Clone)]
pub struct ParticleConfig {
pub characters: Vec<char>,
pub colors: Vec<TermColor>,
pub lifetime: f64,
pub speed_min: f64,
pub speed_max: f64,
pub spread: f64,
pub direction: f64,
pub z_index: i32,
}
impl Default for ParticleConfig {
fn default() -> Self {
Self {
characters: vec!['*'],
colors: vec![TermColor::White],
lifetime: 1.0,
speed_min: 1.0,
speed_max: 3.0,
spread: std::f64::consts::PI * 2.0,
direction: 0.0,
z_index: 10,
}
}
}
struct Particle {
entity: Entity,
velocity_column: f64,
velocity_row: f64,
remaining: f64,
}
pub struct ParticleEmitter {
particles: Vec<Particle>,
}
impl ParticleEmitter {
pub fn new() -> Self {
Self {
particles: Vec::new(),
}
}
pub fn emit(
&mut self,
world: &mut World,
origin_column: f64,
origin_row: f64,
count: usize,
config: &ParticleConfig,
) {
let mut rng = rand::rng();
for _ in 0..count {
let angle =
config.direction + rng.random_range(-config.spread / 2.0..=config.spread / 2.0);
let speed = rng.random_range(config.speed_min..=config.speed_max);
let velocity_column = angle.cos() * speed;
let velocity_row = angle.sin() * speed;
let character = config.characters[rng.random_range(0..config.characters.len())];
let color = config.colors[rng.random_range(0..config.colors.len())];
let entity = world.spawn_entities(POSITION | SPRITE | Z_INDEX, 1)[0];
world.set_position(
entity,
Position {
column: origin_column,
row: origin_row,
},
);
world.set_sprite(
entity,
Sprite {
character,
foreground: color,
background: TermColor::Black,
},
);
world.set_z_index(entity, ZIndex(config.z_index));
self.particles.push(Particle {
entity,
velocity_column,
velocity_row,
remaining: config.lifetime,
});
}
}
pub fn update(&mut self, world: &mut World, delta: f64) {
let mut expired = Vec::new();
for (index, particle) in self.particles.iter_mut().enumerate() {
particle.remaining -= delta;
if particle.remaining <= 0.0 {
expired.push(index);
continue;
}
if let Some(position) = world.get_position_mut(particle.entity) {
position.column += particle.velocity_column * delta;
position.row += particle.velocity_row * delta;
}
}
for &index in expired.iter().rev() {
let particle = self.particles.swap_remove(index);
world.despawn_entities(&[particle.entity]);
}
}
pub fn despawn_all(&mut self, world: &mut World) {
let entities: Vec<Entity> = self
.particles
.iter()
.map(|particle| particle.entity)
.collect();
if !entities.is_empty() {
world.despawn_entities(&entities);
}
self.particles.clear();
}
pub fn active_count(&self) -> usize {
self.particles.len()
}
}
impl Default for ParticleEmitter {
fn default() -> Self {
Self::new()
}
}