1use std::{collections::VecDeque, time::Duration};
4
5use glam::Vec2;
6
7use crate::fireworks::FireworkConfig;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum LifeState {
14 Alive,
15 Declining,
16 Dying,
17 Dead,
18}
19
20#[derive(Debug, Clone)]
22pub struct Particle {
23 pub pos: Vec2,
24 pub vel: Vec2,
25 pub trail: VecDeque<Vec2>,
27 pub life_state: LifeState,
28 pub time_elapsed: Duration,
30 pub config: ParticleConfig,
31}
32
33impl Default for Particle {
34 fn default() -> Self {
35 Self {
36 pos: Vec2::ZERO,
37 vel: Vec2::ZERO,
38 trail: VecDeque::new(),
39 life_state: LifeState::Alive,
40 time_elapsed: Duration::ZERO,
41 config: ParticleConfig::default(),
42 }
43 }
44}
45
46impl Particle {
47 pub fn new(
49 pos: Vec2,
50 vel: Vec2,
51 trail_length: usize,
52 life_time: Duration,
53 color: (u8, u8, u8),
54 ) -> Self {
55 let trail = VecDeque::from(vec![pos; trail_length]);
56 let life_state = LifeState::Alive;
57 Self {
58 pos,
59 vel,
60 trail,
61 life_state,
62 time_elapsed: Duration::ZERO,
63 config: ParticleConfig::new(pos, vel, trail_length, life_time, color),
64 }
65 }
66
67 pub fn is_dead(&self) -> bool {
69 self.life_state == LifeState::Dead
70 }
71
72 pub fn reset(&mut self) {
74 self.pos = self.config.init_pos;
75 self.vel = self.config.init_vel;
76 (0..self.config.trail_length).for_each(|i| self.trail[i] = self.pos);
77 self.life_state = LifeState::Alive;
78 self.time_elapsed = Duration::ZERO;
79 }
80
81 pub fn update(&mut self, duration: Duration, config: &FireworkConfig) {
87 const TIME_STEP: f32 = 0.001;
88 self.time_elapsed += duration;
89 self.life_state = cal_life_state(self.config.life_time, self.time_elapsed);
90 let mut t = 0.;
91 while t < duration.as_secs_f32() {
92 self.vel += TIME_STEP
93 * (Vec2::Y * 10. * config.gravity_scale
94 - self.vel.normalize() * self.vel.length().powi(2) * config.ar_scale
95 + (config.additional_force)(self));
96 self.pos += TIME_STEP * self.vel;
97 t += TIME_STEP;
98 }
99 self.trail.pop_front();
100 self.trail.push_back(self.pos);
101 }
102}
103
104#[derive(Debug, Copy, Clone, PartialEq)]
106pub struct ParticleConfig {
107 pub init_pos: Vec2,
108 pub init_vel: Vec2,
109 pub trail_length: usize,
110 pub life_time: Duration,
112 pub color: (u8, u8, u8),
114}
115
116impl Default for ParticleConfig {
117 fn default() -> Self {
118 Self {
119 init_pos: Vec2::ZERO,
120 init_vel: Vec2::ZERO,
121 trail_length: 2,
122 life_time: Duration::from_secs(3),
123 color: (255, 255, 255),
124 }
125 }
126}
127
128impl ParticleConfig {
129 pub fn new(
131 init_pos: Vec2,
132 init_vel: Vec2,
133 trail_length: usize,
134 life_time: Duration,
135 color: (u8, u8, u8),
136 ) -> Self {
137 Self {
138 init_pos,
139 init_vel,
140 trail_length,
141 life_time,
142 color,
143 }
144 }
145}
146
147fn cal_life_state(life_time: Duration, current_elapsed: Duration) -> LifeState {
148 let p = current_elapsed.as_millis() as f32 / life_time.as_millis() as f32;
149 if p < 0.4 {
150 LifeState::Alive
151 } else if p < 0.65 {
152 LifeState::Declining
153 } else if p < 1. {
154 LifeState::Dying
155 } else {
156 LifeState::Dead
157 }
158}