1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
//! `firework` module provides functions to define, create and update fireworks
use std::time::{Duration, SystemTime};
use glam::Vec2;
use rand::{seq::IteratorRandom, thread_rng};
use crate::particle::{LifeState, Particle, ParticleConfig};
/// Struct representing a single firework
pub struct Firework {
/// The `SystemTime` when the object is initialized/defined
pub init_time: SystemTime,
/// Firework spawns after `spawn_after` from `init_time`
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 {
/// Update the `Firework`
///
/// # Arguments
///
/// * `now` - `SystemTime` of now
/// * `delta_time` - `Duration` since last update
pub fn update(&mut self, now: SystemTime, delta_time: Duration) {
// Spawn particles
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));
// Clean the dead pariticles
let p = self.current_particles.clone();
self.current_particles = p
.into_iter()
.filter(|p| p.life_state != LifeState::Dead)
.collect();
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;
}
}
}
}
/// Return true if the `FireworkState` is `Gone`
pub fn is_gone(&self) -> bool {
self.state == FireworkState::Gone
}
/// Reset `Firework` to its initial state
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;
}
}
}
}
/// Struct representing state of a `Firework`
///
/// State goes from `Waiting` -> `Alive` -> `Gone`
///
/// # Notes
///
/// - `Firework` turns to `Alive` when it is spawned
/// - `Firework` turns to `Gone` when all of its `Particles` are `Dead`
#[derive(Debug, PartialEq)]
pub enum FireworkState {
Waiting,
Alive,
Gone,
}
impl Default for FireworkState {
fn default() -> Self {
FireworkState::Waiting
}
}
/// Enum that represents whether the `Firework` make one instantaneous explosion or continuously emit particles
#[derive(Debug, PartialEq, Eq)]
pub enum ExplosionForm {
Instant {
used: bool,
},
Sustained {
/// `Duration` that the sustained firework will last
lasts: Duration,
/// Time interval between two particle spawn
time_interval: Duration,
timer: Duration,
},
}
/// Struct representing the configuration of a single `Firework`
///
/// This applies to all `Particle` in the `Firework`
pub struct FireworkConfig {
/// Larger `gravity_scale` tends to pull particles down
pub gravity_scale: f32,
/// Air resistance scale
/// Warning: too large or too small `ar_scale` may lead to unexpected behavior of `Particles`
pub ar_scale: f32,
pub additional_force: Box<dyn Fn(&Particle) -> Vec2>,
/// This field is a function that takes a float between 0 and 1, returns a float representing all `Particle`s' gradient
///
/// `Particle`s' gradient changes according to its elapsed time and lifetime
/// The input `f32` equals to `time_elapsed`/`life_time`, which returns a `f32` affecting its color gradient
/// `gradient_scale` returns 1. means`Particle` will have the same colors as defined all over its lifetime
pub gradient_scale: fn(f32) -> f32,
/// Set wheter or not firework has color gradient
///
/// # Notes
///
/// - It is recommanded that your terminal window is non-transparent and has black bg color to get better visual effects
/// - Otherwise set it to `false`
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 {
/// Set `gradient_scale`
#[inline]
#[must_use]
pub fn with_gradient_scale(mut self, f: fn(f32) -> f32) -> Self {
self.gradient_scale = f;
self
}
/// Set `gravity_scale`
#[inline]
#[must_use]
pub fn with_gravity_scale(mut self, s: f32) -> Self {
self.gravity_scale = s;
self
}
/// Set `ar_scale`
#[inline]
#[must_use]
pub fn with_ar_scale(mut self, s: f32) -> Self {
self.ar_scale = s;
self
}
/// Set `additional_force`
#[inline]
#[must_use]
pub fn with_additional_force(mut self, af: impl Fn(&Particle) -> Vec2 + 'static) -> Self {
self.additional_force = Box::new(af);
self
}
/// Set `enable_gradient`
pub fn set_enable_gradient(&mut self, enable_gradient: bool) {
self.enable_gradient = enable_gradient;
}
}
/// `FireworkManager` manages all `Firework`s
pub struct FireworkManager {
pub fireworks: Vec<Firework>,
/// If this is `true`, the whole fireworks show will restart when all the `Firework`s are `Gone`
pub enable_loop: bool,
}
impl Default for FireworkManager {
fn default() -> Self {
Self {
fireworks: Vec::new(),
enable_loop: false,
}
}
}
impl FireworkManager {
/// Create a new `FireworkManager` with `enable_loop` set to `false`
pub fn new(fireworks: Vec<Firework>) -> Self {
Self {
fireworks,
enable_loop: false,
}
}
/// Add a `Firework` to `FireworkManager`
#[inline]
#[must_use]
pub fn add_firework(mut self, firework: Firework) -> Self {
self.fireworks.push(firework);
self
}
// Add a vector of `Firework`s to `FireworkManager`
#[inline]
#[must_use]
pub fn add_fireworks(mut self, mut fireworks: Vec<Firework>) -> Self {
self.fireworks.append(&mut fireworks);
self
}
/// Set `enable_loop` to `true`
#[inline]
#[must_use]
pub fn enable_loop(mut self) -> Self {
self.enable_loop = true;
self
}
/// Set `enable_loop` to `false`
#[inline]
#[must_use]
pub fn disable_loop(mut self) -> Self {
self.enable_loop = false;
self
}
/// Reset the whole fireworks show
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;
}
/// The main update function
pub fn update(&mut self, now: SystemTime, delta_time: Duration) {
for ele in self.fireworks.iter_mut() {
ele.update(now, delta_time);
}
if self.enable_loop {
if self
.fireworks
.iter()
.fold(true, |acc, x| acc && x.is_gone())
{
self.reset();
}
}
}
}
fn init_trail(init_pos: Vec2, n: usize) -> Vec<Vec2> {
let mut res = Vec::new();
(0..n).for_each(|_| res.push(init_pos));
res
}