particula_rs/
lib.rs

1use std::marker::PhantomData;
2
3/// A collection of particles and emitters
4pub trait ParticleSystem {
5    /// The type of particle that this system will contain
6    type ParticleType: Particle;
7
8    /// The type of emitter that this system will contain
9    type EmitterType: ParticleEmitter<ParticleType = Self::ParticleType>;
10
11    /// Returns an iterator over all currently alive particles in the system
12    fn iter_particles(&self) -> impl Iterator<Item = &Self::ParticleType>;
13
14    /// Returns a mutable iterator over all currently alive particles in the system
15    fn iter_particles_mut(&mut self) -> impl Iterator<Item = &mut Self::ParticleType>;
16
17    /// Returns an iterator over all currently alive particle emitters in the system
18    fn iter_emitters(&self) -> impl Iterator<Item = &Self::EmitterType>;
19
20    /// Returns a mutable iterator over all currently alive particle emitters in the system
21    fn iter_emitters_mut(&mut self) -> impl Iterator<Item = &mut Self::EmitterType>;
22
23    /// Adds a particle to the system
24    fn add_particle(&mut self, particle: Self::ParticleType);
25
26    /// Adds an emitter to the system
27    fn add_emitter(&mut self, emitter: Self::EmitterType);
28
29    /// Iterates over all currently alive particles in the system and calls their update method
30    fn update_particles(&mut self, dt: f64) {
31        for particle in self.iter_particles_mut() {
32            particle.update(dt);
33        }
34    }
35
36    /// Iterates over all currently alive particle emitters in the system and calls their update method, returning the vector of new particles to add to the system
37    fn update_emitters(&mut self, dt: f64) -> Vec<Self::ParticleType> {
38        self.iter_emitters_mut()
39            .flat_map(|emitter| emitter.update(dt))
40            .collect()
41    }
42
43    /// Removes dead particles from the system
44    fn clean_particles(&mut self);
45
46    /// Removes dead emitters from the system
47    fn clean_emitters(&mut self);
48
49    /// Updates the particle system
50    ///
51    /// This method is comprised of 3 steps:
52    /// 1. Update emitters and add the new particles to the system
53    /// 2. Update all particles in the system
54    /// 3. Remove dead particles and emitters from the system
55    fn update(&mut self, dt: f64) {
56        let new_particles = self.update_emitters(dt);
57
58        for new_particle in new_particles {
59            self.add_particle(new_particle);
60        }
61
62        self.update_particles(dt);
63
64        self.clean_particles();
65        self.clean_emitters();
66    }
67}
68
69/// A base particle system using vectors to store the particles and emitters
70///
71/// This should suffice for most particle system needs
72#[derive(Debug, Clone)]
73pub struct VecParticleSystem<P, E> {
74    particles: Vec<P>,
75    emitters: Vec<E>,
76}
77
78impl<P, E> Default for VecParticleSystem<P, E>
79{
80    fn default() -> Self {
81        Self {
82            particles: Vec::default(),
83            emitters: Vec::default(),
84        }
85    }
86}
87
88impl<P: Particle, E: ParticleEmitter<ParticleType = P>> ParticleSystem for VecParticleSystem<P, E> {
89    /// This system can hold any particle that implements `Particle` with the same `Coordinate` type
90    type ParticleType = P;
91
92    /// This system can hold any emitter that emits any particle that implements `Particle` with the same `Coordinate` type
93    type EmitterType = E;
94
95    fn iter_particles(&self) -> impl Iterator<Item = &Self::ParticleType> {
96        self.particles.iter()
97    }
98
99    fn iter_particles_mut(&mut self) -> impl Iterator<Item = &mut Self::ParticleType> {
100        self.particles.iter_mut()
101    }
102
103    fn iter_emitters(&self) -> impl Iterator<Item = &Self::EmitterType> {
104        self.emitters.iter()
105    }
106
107    fn iter_emitters_mut(&mut self) -> impl Iterator<Item = &mut Self::EmitterType> {
108        self.emitters.iter_mut()
109    }
110
111    fn add_particle(&mut self, particle: Self::ParticleType) {
112        self.particles.push(particle);
113    }
114
115    fn add_emitter(&mut self, emitter: Self::EmitterType) {
116        self.emitters.push(emitter);
117    }
118
119    fn clean_particles(&mut self) {
120        self.particles.retain(|particle| particle.is_alive());
121    }
122
123    fn clean_emitters(&mut self) {
124        self.emitters.retain(|emitter| emitter.is_alive());
125    }
126}
127
128pub type BaseParticleSystem<C> = VecParticleSystem<
129    Box<dyn Particle<Coordinate = C>>,
130    Box<dyn ParticleEmitter<ParticleType = Box<dyn Particle<Coordinate = C>>>>,
131>;
132
133/// Creates new particles
134pub trait ParticleEmitter {
135    /// The type of the particles to be emitted
136    type ParticleType: Particle;
137
138    /// Update the state of the emitter and return a vector of particles to add to the system
139    fn update(&mut self, dt: f64) -> Vec<Self::ParticleType>;
140
141    /// Returns false if the emitter should be removed from the system
142    fn is_alive(&self) -> bool;
143}
144
145impl<E: ParticleEmitter + ?Sized> ParticleEmitter for Box<E> {
146    type ParticleType = E::ParticleType;
147
148    fn update(&mut self, dt: f64) -> Vec<Self::ParticleType> {
149        E::update(self, dt)
150    }
151
152    fn is_alive(&self) -> bool {
153        E::is_alive(self)
154    }
155}
156
157/// A particle emitter that never emits particles and is never alive.
158/// Useful for when you don't really need emitters in your particle system.
159#[derive(Debug, Clone, Copy)]
160pub struct NullParticleEmitter<P> {
161    phantom: PhantomData<P>,
162}
163
164impl<P: Particle> ParticleEmitter for NullParticleEmitter<P> {
165    type ParticleType = P;
166
167    fn update(&mut self, _dt: f64) -> Vec<Self::ParticleType> {
168        vec![]
169    }
170
171    fn is_alive(&self) -> bool {
172        false
173    }
174}
175
176/// A representation of some particle space
177pub trait Particle {
178    /// The position type of the particle
179    type Coordinate;
180
181    /// The position of the particle in space
182    fn get_position(&self) -> Self::Coordinate;
183
184    /// Updates the state of the particle
185    fn update(&mut self, dt: f64);
186
187    /// Returns false if the particle should be removed from the system
188    fn is_alive(&self) -> bool;
189}
190
191impl<P: Particle + ?Sized> Particle for Box<P> {
192    type Coordinate = P::Coordinate;
193
194    fn get_position(&self) -> Self::Coordinate {
195        P::get_position(self)
196    }
197
198    fn update(&mut self, dt: f64) {
199        P::update(self, dt);
200    }
201
202    fn is_alive(&self) -> bool {
203        P::is_alive(self)
204    }
205}
206
207/// Tracks age in a particle
208pub trait Aging {
209    /// Gets the current age of the particle
210    fn get_age(&self) -> f64;
211
212    /// Sets the current age of the particle
213    fn set_age(&mut self, age: f64);
214}
215
216/// Represents a particle that dies after a set amount of time
217pub trait MaxAging: Aging {
218    /// Gets the max age of the particle
219    fn get_max_age(&self) -> f64;
220
221    /// Gets the age of the particle from 0.0 to 1.0
222    fn get_age_percent(&self) -> f64 {
223        self.get_age() / self.get_max_age()
224    }
225
226    /// Returns false if the particle's age percent is greater than or equal to 1.0
227    fn is_alive(&self) -> bool {
228        self.get_age_percent() < 1.0
229    }
230}