use crate::emitter::Emitter;
use crate::rules::Rule;
use glam::Vec3;
use std::ops::Range;
#[derive(Default, Clone)]
pub struct Lifecycle {
lifetime_fixed: Option<f32>,
lifetime_range: Option<Range<f32>>,
fade_out: bool,
shrink_out: bool,
color_over_life: Option<(Vec3, Vec3)>,
emitters: Vec<Emitter>,
start_dead: bool,
}
impl Lifecycle {
pub fn new() -> Self {
Self::default()
}
pub fn fire(position: Vec3, rate: f32) -> Self {
Self {
lifetime_fixed: Some(1.5),
fade_out: true,
shrink_out: true,
color_over_life: Some((
Vec3::new(1.0, 0.9, 0.3), Vec3::new(0.8, 0.2, 0.0), )),
emitters: vec![Emitter::Cone {
position,
direction: Vec3::Y,
speed: 0.8,
spread: 0.4,
rate,
}],
start_dead: true,
..Default::default()
}
}
pub fn fountain(position: Vec3, rate: f32) -> Self {
Self {
lifetime_fixed: Some(3.0),
fade_out: true,
shrink_out: false,
color_over_life: Some((
Vec3::new(0.7, 0.85, 1.0), Vec3::new(0.2, 0.4, 0.8), )),
emitters: vec![Emitter::Cone {
position,
direction: Vec3::Y,
speed: 2.5,
spread: 0.2,
rate,
}],
start_dead: true,
..Default::default()
}
}
pub fn explosion(position: Vec3, count: u32) -> Self {
Self {
lifetime_fixed: Some(1.2),
fade_out: true,
shrink_out: true,
color_over_life: Some((
Vec3::new(1.0, 1.0, 0.8), Vec3::new(1.0, 0.3, 0.0), )),
emitters: vec![Emitter::Burst {
position,
count,
speed: 3.0,
}],
start_dead: true,
..Default::default()
}
}
pub fn smoke(position: Vec3, rate: f32) -> Self {
Self {
lifetime_fixed: Some(4.0),
fade_out: true,
shrink_out: false, color_over_life: Some((
Vec3::new(0.4, 0.4, 0.4), Vec3::new(0.15, 0.15, 0.15), )),
emitters: vec![Emitter::Cone {
position,
direction: Vec3::Y,
speed: 0.3,
spread: 0.6,
rate,
}],
start_dead: true,
..Default::default()
}
}
pub fn sparkler(position: Vec3, rate: f32) -> Self {
Self {
lifetime_fixed: Some(0.5),
fade_out: true,
shrink_out: true,
color_over_life: Some((
Vec3::new(1.0, 1.0, 1.0), Vec3::new(1.0, 0.6, 0.1), )),
emitters: vec![Emitter::Sphere {
center: position,
radius: 0.02,
speed: 2.0,
rate,
}],
start_dead: true,
..Default::default()
}
}
pub fn rain(rate: f32) -> Self {
Self {
lifetime_fixed: Some(2.0),
fade_out: false,
shrink_out: false,
color_over_life: Some((
Vec3::new(0.6, 0.7, 0.9), Vec3::new(0.4, 0.5, 0.7), )),
emitters: vec![Emitter::Box {
min: Vec3::new(-1.0, 0.9, -1.0),
max: Vec3::new(1.0, 1.0, 1.0),
velocity: Vec3::new(0.0, -2.0, 0.0),
rate,
}],
start_dead: true,
..Default::default()
}
}
pub fn lifetime(mut self, seconds: f32) -> Self {
self.lifetime_fixed = Some(seconds);
self.lifetime_range = None;
self
}
pub fn lifetime_range(mut self, range: Range<f32>) -> Self {
self.lifetime_range = Some(range.clone());
self.lifetime_fixed = Some((range.start + range.end) / 2.0);
self
}
pub fn fade_out(mut self) -> Self {
self.fade_out = true;
self
}
pub fn shrink_out(mut self) -> Self {
self.shrink_out = true;
self
}
pub fn color_over_life(mut self, start: Vec3, end: Vec3) -> Self {
self.color_over_life = Some((start, end));
self
}
pub fn emitter(mut self, emitter: Emitter) -> Self {
self.emitters.push(emitter);
self
}
pub fn start_dead(mut self) -> Self {
self.start_dead = true;
self
}
pub fn get_lifetime(&self) -> Option<f32> {
self.lifetime_fixed
}
pub(crate) fn build(self) -> (Vec<Rule>, Vec<Emitter>, bool) {
let mut rules = Vec::new();
let has_lifecycle = self.lifetime_fixed.is_some()
|| self.fade_out
|| self.shrink_out
|| self.color_over_life.is_some();
if has_lifecycle {
rules.push(Rule::Age);
}
if let Some(duration) = self.lifetime_fixed {
rules.push(Rule::Lifetime(duration));
if self.fade_out {
rules.push(Rule::FadeOut(duration));
}
if self.shrink_out {
rules.push(Rule::ShrinkOut(duration));
}
if let Some((start, end)) = self.color_over_life {
rules.push(Rule::ColorOverLife {
start,
end,
duration,
});
}
}
(rules, self.emitters, self.start_dead)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fire_preset() {
let lifecycle = Lifecycle::fire(Vec3::ZERO, 500.0);
let (rules, emitters, start_dead) = lifecycle.build();
assert!(start_dead);
assert_eq!(emitters.len(), 1);
assert!(rules.iter().any(|r| matches!(r, Rule::Age)));
assert!(rules.iter().any(|r| matches!(r, Rule::Lifetime(_))));
assert!(rules.iter().any(|r| matches!(r, Rule::FadeOut(_))));
assert!(rules.iter().any(|r| matches!(r, Rule::ShrinkOut(_))));
}
#[test]
fn test_builder_chain() {
let lifecycle = Lifecycle::new()
.lifetime(2.0)
.fade_out()
.emitter(Emitter::Point {
position: Vec3::ZERO,
rate: 100.0,
speed: 1.0,
});
let (rules, emitters, _) = lifecycle.build();
assert_eq!(emitters.len(), 1);
assert!(rules.iter().any(|r| matches!(r, Rule::Age)));
assert!(rules.iter().any(|r| matches!(r, Rule::Lifetime(2.0))));
assert!(rules.iter().any(|r| matches!(r, Rule::FadeOut(2.0))));
}
}