use std::collections::BTreeMap; use std::ops::Bound::{Included, Unbounded}; use bevy::color::{Color, LinearRgba}; use bevy::math::cubic_splines::LinearSpline; use bevy::math::Vec2; use bevy::prelude::Component; use ordered_float::{FloatCore, OrderedFloat}; use rand::Rng; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Component)] pub struct ParticleEffect { // -- lifetime / spawning -- // /// Particle lifetime in seconds pub lifetime_seconds: RandF32, /// Delay inbetween each batch of particles spawned pub batch_spawn_delay_seconds: RandF32, /// Number of distinct particles spawned per batch pub particles_in_batch: RandUsize, // -- velocity -- // /// Initial linear velocity added to the particle's velocity when it is spawned pub initial_linear_velocity: RandVec2, /// Initial angular velocity added to the particle's rotation when it is spawned pub initial_angular_velocity: RandF32, // -- scale -- // // Scale curve over the lifetime of the particle pub scale: LifetimeCurve, // -- color -- // // Color curve over the lifetime of the particle pub color: LifetimeCurve, } #[derive(Serialize, Deserialize)] pub struct LifetimeCurve(pub BTreeMap, P>); impl LifetimeCurve

{ pub fn new<'a>(points: impl IntoIterator) -> Self where P: 'a { Self(BTreeMap::from_iter(points.into_iter().map(|u| (OrderedFloat(u.0), u.1.clone())))) } /// Sample for the value at T. Returns None if the curve has no points, or if T is outside /// the domain of the points specified. pub fn sample(&self, at: f32) -> Option

{ if self.0.is_empty() { return None; } // Find A, the point "to the left" of `at` let mut r1 = self.0.range((Included(OrderedFloat(at)), Unbounded)); let (a_key, a_val) = r1.next()?; // Find B, the point "to the right" of `at` let mut r2 = self.0.range((Unbounded, Included(OrderedFloat(at)))); let (b_key, b_val) = r2.next()?; // Calculate `t` (value from 0 - 1 indicating our progress from A to B) let t = at - *(a_key / (b_key - a_key)); // Lerp Some(a_val.lerp(b_val, t)) } } pub trait Lerp { fn lerp(&self, other: &Self, t: f32) -> Self; } impl Lerp for f32 { fn lerp(&self, other: &Self, t: f32) -> Self { (1.0 - t) * self + t * other } } impl Lerp for Color { fn lerp(&self, other: &Self, t: f32) -> Self { let a: LinearRgba = (*self).into(); let b: LinearRgba = (*other).into(); Color::from(a * (1.0 - t) + b * t) } } #[derive(Deserialize, Serialize)] pub struct RandF32 { pub value: f32, pub randomness: f32 } impl RandF32 { pub fn sample(&self, rng: &mut impl Rng) -> f32 { rng.random_range(self.value-self.randomness .. self.value+self.randomness) } } #[derive(Deserialize, Serialize)] pub struct RandUsize { pub value: usize, pub randomness: usize } impl RandUsize { pub fn sample(&self, rng: &mut impl Rng) -> usize { let lower_bound = self.value.checked_sub(self.randomness).unwrap_or(0); rng.random_range(lower_bound .. self.value+self.randomness) } } #[derive(Deserialize, Serialize)] pub struct RandVec2 { pub x: RandF32, pub y: RandF32, } impl RandVec2 { pub fn sample(&self, rng: &mut impl Rng) -> Vec2 { Vec2::new(self.x.sample(rng), self.y.sample(rng)) } }