use std::{
collections::VecDeque,
time::{Duration, SystemTime},
};
use glam::Vec2;
use rand::{seq::IteratorRandom, thread_rng};
use crate::particle::{LifeState, Particle, ParticleConfig};
pub struct Firework {
pub init_time: SystemTime,
pub spawn_after: Duration,
pub time_elapsed: Duration,
pub center: Vec2,
pub state: FireworkState,
pub config: FireworkConfig,
pub form: ExplosionForm,
pub particles: Vec<ParticleConfig>,
pub current_particles: Vec<Particle>,
}
impl Default for Firework {
fn default() -> Self {
Self {
init_time: SystemTime::now(),
spawn_after: Duration::ZERO,
time_elapsed: Duration::ZERO,
center: Vec2::ZERO,
state: FireworkState::Waiting,
config: FireworkConfig::default(),
form: ExplosionForm::Instant { used: false },
particles: Vec::new(),
current_particles: Vec::new(),
}
}
}
impl Firework {
pub fn update(&mut self, now: SystemTime, delta_time: Duration) {
if now >= self.init_time + self.spawn_after {
self.time_elapsed += delta_time;
match &mut self.form {
ExplosionForm::Instant { used } => {
if !*used {
self.particles.iter().for_each(|p| {
self.current_particles.push(Particle {
pos: p.init_pos,
vel: p.init_vel,
trail: init_trail(p.init_pos, p.trail_length),
life_state: LifeState::Alive,
time_elapsed: Duration::ZERO,
config: *p,
})
})
}
*used = true;
}
ExplosionForm::Sustained {
lasts,
time_interval,
timer,
} => {
if self.time_elapsed <= *lasts {
if *timer + delta_time <= *time_interval {
*timer += delta_time;
} else {
let n =
(*timer + delta_time).as_millis() / (*time_interval).as_millis();
self.particles
.iter()
.choose_multiple(&mut thread_rng(), n as usize)
.iter()
.for_each(|p| {
self.current_particles.push(Particle {
pos: p.init_pos,
vel: p.init_vel,
trail: init_trail(p.init_pos, p.trail_length),
life_state: LifeState::Alive,
time_elapsed: Duration::ZERO,
config: **p,
})
});
*timer = Duration::from_millis(
((*timer + delta_time).as_millis() % (*time_interval).as_millis())
as u64,
);
}
}
}
}
self.state = FireworkState::Alive;
}
self.current_particles
.iter_mut()
.for_each(|p| p.update(delta_time, &self.config));
self.current_particles
.retain(|p| p.life_state != LifeState::Dead);
match self.form {
ExplosionForm::Instant { used } => {
if used && self.state == FireworkState::Alive && self.current_particles.is_empty() {
self.state = FireworkState::Gone;
}
}
ExplosionForm::Sustained { lasts, .. } => {
if self.time_elapsed > lasts
&& self.state == FireworkState::Alive
&& self.current_particles.is_empty()
{
self.state = FireworkState::Gone;
}
}
}
}
pub fn is_gone(&self) -> bool {
self.state == FireworkState::Gone
}
pub fn reset(&mut self) {
self.init_time = SystemTime::now();
self.state = FireworkState::Waiting;
self.time_elapsed = Duration::ZERO;
self.current_particles = Vec::new();
match &mut self.form {
ExplosionForm::Instant { used } => {
*used = false;
}
ExplosionForm::Sustained { timer, .. } => {
*timer = Duration::ZERO;
}
}
}
}
#[derive(Debug, PartialEq, Default)]
pub enum FireworkState {
#[default]
Waiting,
Alive,
Gone,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExplosionForm {
Instant {
used: bool,
},
Sustained {
lasts: Duration,
time_interval: Duration,
timer: Duration,
},
}
pub struct FireworkConfig {
pub gravity_scale: f32,
pub ar_scale: f32,
pub additional_force: Box<dyn Fn(&Particle) -> Vec2>,
pub gradient_scale: fn(f32) -> f32,
pub enable_gradient: bool,
}
impl Default for FireworkConfig {
fn default() -> Self {
Self {
gravity_scale: 1.,
ar_scale: 0.28,
additional_force: Box::new(move |_| Vec2::ZERO),
gradient_scale: |_| 1.,
enable_gradient: false,
}
}
}
impl FireworkConfig {
#[inline]
#[must_use]
pub fn with_gradient_scale(mut self, f: fn(f32) -> f32) -> Self {
self.gradient_scale = f;
self
}
#[inline]
#[must_use]
pub fn with_gravity_scale(mut self, s: f32) -> Self {
self.gravity_scale = s;
self
}
#[inline]
#[must_use]
pub fn with_ar_scale(mut self, s: f32) -> Self {
self.ar_scale = s;
self
}
#[inline]
#[must_use]
pub fn with_additional_force(mut self, af: impl Fn(&Particle) -> Vec2 + 'static) -> Self {
self.additional_force = Box::new(af);
self
}
pub fn set_enable_gradient(&mut self, enable_gradient: bool) {
self.enable_gradient = enable_gradient;
}
}
pub struct FireworkManager {
pub fireworks: Vec<Firework>,
pub enable_loop: bool,
pub install_form: FireworkInstallForm,
}
impl Default for FireworkManager {
fn default() -> Self {
Self {
fireworks: Vec::new(),
enable_loop: false,
install_form: FireworkInstallForm::StaticInstall,
}
}
}
impl FireworkManager {
pub fn new(fireworks: Vec<Firework>) -> Self {
Self {
fireworks,
enable_loop: false,
install_form: FireworkInstallForm::StaticInstall,
}
}
pub fn add_firework(&mut self, firework: Firework) {
self.fireworks.push(firework);
}
pub fn add_fireworks(&mut self, mut fireworks: Vec<Firework>) {
self.fireworks.append(&mut fireworks);
}
#[inline]
#[must_use]
pub fn with_firework(mut self, firework: Firework) -> Self {
self.fireworks.push(firework);
self
}
#[inline]
#[must_use]
pub fn with_fireworks(mut self, mut fireworks: Vec<Firework>) -> Self {
self.fireworks.append(&mut fireworks);
self
}
#[inline]
#[must_use]
pub fn enable_loop(mut self) -> Self {
self.enable_loop = true;
self
}
#[inline]
#[must_use]
pub fn disable_loop(mut self) -> Self {
self.enable_loop = false;
self
}
pub fn reset(&mut self) {
for ele in self.fireworks.iter_mut() {
ele.reset();
}
}
pub fn set_enable_loop(&mut self, enable_loop: bool) {
self.enable_loop = enable_loop;
}
pub fn update(&mut self, now: SystemTime, delta_time: Duration) {
for ele in self.fireworks.iter_mut() {
ele.update(now, delta_time);
}
if self.install_form == FireworkInstallForm::DynamicInstall {
self.fireworks.retain(|f| f.state != FireworkState::Gone);
}
if self.install_form == FireworkInstallForm::StaticInstall
&& self.enable_loop
&& self.fireworks.iter().all(|f| f.is_gone())
{
self.reset();
}
}
pub fn enable_dyn_install(mut self) -> Self {
self.install_form = FireworkInstallForm::DynamicInstall;
self
}
}
#[derive(Debug, PartialEq)]
pub enum FireworkInstallForm {
StaticInstall,
DynamicInstall,
}
fn init_trail(init_pos: Vec2, n: usize) -> VecDeque<Vec2> {
VecDeque::from(vec![init_pos; n])
}