moon_engine/
particle.rs

1//! The [`Particle`] and [`ParticleSystem`] structs.
2
3use std::f32::consts::PI;
4
5use crate::component::Component;
6use crate::math::*;
7use crate::renderer::Quad;
8use crate::transform::Transform2D;
9
10/// Maximum [`Particles`](Particle) in a [`ParticleSystem`].
11const MAX_PARTICLES: usize = 100000;
12
13/// A [`ParticleProps`] defines how [`Particles`](Particle) are created.
14///
15/// Reusing a [`ParticleProps`] allows for similar [`Particles`](Particle) to be emitted.
16#[derive(Debug, Clone)]
17pub struct ParticleProps {
18    /// How long the [`Particle`] will last.
19    pub lifetime: f32,
20    /// The base velocity of the [`Particle`].
21    pub velocity: Vec2,
22    /// A modifier field for the velocity of the [`Particle`].
23    pub velocity_modifier: Vec2,
24    /// The start color of the [`Particle`].
25    pub color_start: Color32,
26    /// The end color of the [`Particle`].
27    pub color_end: Color32,
28    /// A modifier field for the color of the [`Particle`].
29    pub color_modifier: Color32,
30    /// How many [`Particles`](Particle) to emit on each update.
31    pub burst_count: u32,
32    /// The size of the [`Particle`].
33    pub size: Vec2,
34}
35
36impl Default for ParticleProps {
37    fn default() -> Self {
38        Self {
39            lifetime: 10.0,
40            velocity: Vec2::new(0.0, -0.3),
41            velocity_modifier: Vec2::new(0.25, 0.2),
42            color_start: Color32(0.0, 0.6, 1.0, 0.9),
43            color_end: Color32(0.95, 0.2, 1.0, 1.0),
44            color_modifier: Color32(0.2, 0.4, 0.1, 0.0),
45            burst_count: 15,
46            size: Vec2::new(0.05, 0.05),
47        }
48    }
49}
50
51impl ParticleProps {
52    /// A Fire [`ParticleProps`] preset.
53    pub const fn fire() -> Self {
54        Self {
55            lifetime: 10.0,
56            velocity: Vec2::new(0.0, -0.2),
57            velocity_modifier: Vec2::new(0.15, 0.1),
58            color_start: Color32(1.0, 1.0, 0.0, 1.0),
59            color_end: Color32(1.0, 0.0, 0.0, 1.0),
60            color_modifier: Color32(0.2, 0.2, 0.3, 0.0),
61            burst_count: 5,
62            size: Vec2::new(0.05, 0.05),
63        }
64    }
65
66    /// A Smoke [`ParticleProps`] preset.
67    pub const fn smoke() -> Self {
68        Self {
69            lifetime: 15.0,
70            velocity: Vec2::new(0.0, -0.4),
71            velocity_modifier: Vec2::new(0.3, 0.2),
72            color_start: Color32(0.7, 0.7, 0.7, 1.0),
73            color_end: Color32::BLACK,
74            color_modifier: Color32(0.4, 0.4, 0.4, 0.0),
75            burst_count: 20,
76            size: Vec2::new(0.1, 0.15),
77        }
78    }
79}
80
81/// A [`Particle`] describes a single emission from a [`ParticleSystem`].
82#[derive(Debug, Clone)]
83pub struct Particle {
84    transform: Transform2D,
85    lifetime: f32,
86    velocity: Vec2,
87    color: Color32,
88    color_start: Color32,
89    color_end: Color32,
90    age: f32,
91    alive: bool,
92}
93
94impl Default for Particle {
95    fn default() -> Self {
96        Self {
97            transform: Transform2D::new_with_scale(0.1, 0.1),
98            lifetime: 10.0,
99            velocity: Vec2::new(0.0, 0.0),
100            color: Color32::ZEROES,
101            color_start: Color32::WHITE,
102            color_end: Color32::WHITE,
103            age: 0.0,
104            alive: false,
105        }
106    }
107}
108
109impl Component for Particle {
110    fn init(&mut self) {
111        self.transform.rotation = f32::random_range_max(PI);
112        self.color = self.color_start;
113        self.alive = true;
114        self.age = 0.0;
115    }
116
117    fn update(&mut self, delta_time: f32) {
118        self.age += delta_time;
119        if self.age > self.lifetime {
120            self.alive = false;
121        } else {
122            self.transform.position += self.velocity * delta_time;
123            self.transform.rotation += f32::random_range(-1.0, 1.0) * delta_time;
124            self.color = Color32::lerp(self.color_start, self.color_end, self.age / self.lifetime);
125        }
126    }
127
128    fn as_any(&self) -> &dyn std::any::Any {
129        self
130    }
131
132    fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
133        self
134    }
135}
136
137impl From<&ParticleProps> for Particle {
138    fn from(properties: &ParticleProps) -> Self {
139        Self {
140            transform: Transform2D::new_with_scale(properties.size.x, properties.size.y),
141            lifetime: properties.lifetime,
142            velocity: {
143                properties.velocity
144                    + Vec2::random_range(
145                        -properties.velocity_modifier,
146                        properties.velocity_modifier,
147                    )
148            },
149            color_start: properties.color_start
150                + Color32::random_range(properties.color_modifier, properties.color_modifier),
151            color_end: properties.color_end
152                + Color32::random_range(properties.color_modifier, properties.color_modifier),
153            ..Default::default()
154        }
155    }
156}
157
158/// A [`ParticleSystem`] deals with the emission, and creation of [`Particles`](Particle).
159#[derive(Debug, Clone)]
160pub struct ParticleSystem {
161    /// The [`Transform2D`] of the [`ParticleSystem`].
162    pub transform: Transform2D,
163    emission: ParticleProps,
164    particles: Vec<Particle>,
165    index: usize,
166    /// A [`ParticleSystem`] needs to be alive to emit and update [`Particles`](Particle).
167    pub alive: bool,
168}
169
170impl Default for ParticleSystem {
171    fn default() -> Self {
172        Self {
173            emission: ParticleProps::default(),
174            particles: Vec::with_capacity(MAX_PARTICLES),
175            index: 0,
176            transform: Transform2D::default(),
177            alive: false,
178        }
179    }
180}
181
182impl Component for ParticleSystem {
183    fn init(&mut self) {
184        self.particles.fill_with(Particle::default);
185        self.alive = true;
186    }
187
188    fn update(&mut self, delta_time: f32) {
189        // Do not update if inactive
190        if !self.alive {
191            return;
192        }
193
194        self.emit_many(self.emission.burst_count);
195        for particle in self.particles.iter_mut() {
196            if particle.alive {
197                particle.update(delta_time);
198            }
199        }
200    }
201    /// Get a [`Vec`] of [`Quad`] from all the [`Particles`](Particle).
202    fn get_quads(&self) -> Option<Vec<Quad>> {
203        Some(
204            self.particles
205                .iter()
206                .filter(|particle| particle.alive)
207                .map(|particle| {
208                    Quad::new_from_position_and_rotation_and_size_and_color(
209                        particle.transform.position.x,
210                        particle.transform.position.y,
211                        particle.transform.rotation,
212                        particle.transform.scale.x,
213                        particle.transform.scale.y,
214                        particle.color,
215                    )
216                })
217                .collect(),
218        )
219    }
220
221    fn as_any(&self) -> &dyn std::any::Any {
222        self
223    }
224
225    fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
226        self
227    }
228}
229
230impl ParticleSystem {
231    /// Create a new [`ParticleSystem`] using a [`ParticleProps`] for the emission.
232    pub fn new_from_emission(emission: ParticleProps) -> Self {
233        Self {
234            emission,
235            ..Default::default()
236        }
237    }
238
239    /// Create a new [`ParticleSystem`] using a [`ParticleProps`] for the emission, and `X` and `Y` components for its position.
240    pub fn new_from_emission_and_position(emission: ParticleProps, pos_x: f32, pos_y: f32) -> Self {
241        Self {
242            emission,
243            transform: Transform2D::new_with_position(pos_x, pos_y),
244            ..Default::default()
245        }
246    }
247
248    /// Toggle the `alive` field of the [`ParticleSystem`].
249    pub fn toggle_alive(&mut self) {
250        self.alive = !self.alive;
251    }
252
253    /// Emit a single [`Particle`], according to the defined [`ParticleProps`] for emission.
254    pub fn emit(&mut self) {
255        if self.index >= MAX_PARTICLES {
256            self.index = 0;
257        }
258
259        let mut new_particle = Particle::from(&self.emission);
260        new_particle.transform = self.transform + new_particle.transform;
261
262        let particle = self.particles.get_mut(self.index);
263
264        if let Some(particle) = particle {
265            particle.transform = new_particle.transform;
266            particle.lifetime = new_particle.lifetime;
267            particle.velocity = new_particle.velocity;
268            particle.color_start = new_particle.color_start;
269            particle.color_end = new_particle.color_end;
270
271            particle.init();
272        } else {
273            new_particle.init();
274            self.particles.push(new_particle);
275        }
276        self.index += 1;
277    }
278
279    /// Emit multiple [`Particles`](Particle), according to the defined [`ParticleProps`] for emission.
280    pub fn emit_many(&mut self, count: u32) {
281        for _ in 0..count {
282            self.emit()
283        }
284    }
285}