Skip to main content

euv_engine/particle/
struct.rs

1use super::*;
2
3/// A deterministic xorshift64* pseudo-random generator used by particle
4/// emitters.
5///
6/// The engine intentionally avoids both the `rand` crate (dependency weight
7/// for a wasm target) and `js_sys::Math::random` (non-deterministic, and
8/// unusable outside a JS runtime, which would make host-side unit tests
9/// impossible). A seeded generator keeps emitter behavior reproducible.
10#[derive(Clone, Copy, Data, Debug, New, PartialEq, PartialOrd)]
11pub struct ParticleRng {
12    /// The current generator state. Never zero after construction.
13    #[get(type(copy))]
14    #[get_mut(pub(crate))]
15    #[set(pub(crate))]
16    pub(crate) state: u64,
17}
18
19/// The static configuration of a particle emitter, describing how new
20/// particles are spawned and how they evolve over their lifetime.
21///
22/// Every field is publicly settable, so a caller composes a config by
23/// starting from [`ParticleConfig::default`] and overriding the tunables it
24/// cares about before handing it to [`ParticleEmitter::create`].
25#[derive(Clone, Copy, Data, Debug, New, PartialEq, PartialOrd)]
26pub struct ParticleConfig {
27    /// The number of particles spawned per second while the emitter is active.
28    #[get(type(copy))]
29    pub(crate) emission_rate: f64,
30    /// The maximum number of simultaneously live particles.
31    #[get(type(copy))]
32    pub(crate) max_particles: usize,
33    /// The minimum particle lifetime in seconds.
34    #[get(type(copy))]
35    pub(crate) lifetime_min: f64,
36    /// The maximum particle lifetime in seconds.
37    #[get(type(copy))]
38    pub(crate) lifetime_max: f64,
39    /// The minimum initial particle speed in world units per second.
40    #[get(type(copy))]
41    pub(crate) speed_min: f64,
42    /// The maximum initial particle speed in world units per second.
43    #[get(type(copy))]
44    pub(crate) speed_max: f64,
45    /// The central emission direction in radians.
46    #[get(type(copy))]
47    #[new(skip)]
48    pub(crate) angle: f64,
49    /// The total cone width around `angle` in radians within which
50    /// particle directions are uniformly randomized.
51    #[get(type(copy))]
52    #[new(skip)]
53    pub(crate) spread: f64,
54    /// The constant acceleration applied to every live particle.
55    #[get(type(copy))]
56    #[new(skip)]
57    pub(crate) gravity: Vector2D,
58    /// The particle color at birth.
59    #[get(type(copy))]
60    #[new(skip)]
61    pub(crate) color_start: Color,
62    /// The particle color at death. Set its alpha to 0.0 for a fade-out.
63    #[get(type(copy))]
64    #[new(skip)]
65    pub(crate) color_end: Color,
66    /// The particle radius at birth in world units.
67    #[get(type(copy))]
68    #[new(skip)]
69    pub(crate) size_start: f64,
70    /// The particle radius at death in world units.
71    #[get(type(copy))]
72    #[new(skip)]
73    pub(crate) size_end: f64,
74}
75
76/// A single live particle.
77#[derive(Clone, Copy, Data, Debug, New, PartialEq, PartialOrd)]
78pub struct Particle {
79    /// The current world-space position.
80    #[get(type(copy))]
81    #[get_mut(pub(crate))]
82    #[set(pub(crate))]
83    pub(crate) position: Vector2D,
84    /// The current velocity in world units per second.
85    #[get(type(copy))]
86    #[get_mut(pub(crate))]
87    #[set(pub(crate))]
88    pub(crate) velocity: Vector2D,
89    /// The time this particle has been alive, in seconds.
90    #[get(pub(crate), type(copy))]
91    #[get_mut(pub(crate))]
92    #[set(pub(crate))]
93    pub(crate) age: f64,
94    /// The total lifetime of this particle, in seconds.
95    #[get(type(copy))]
96    #[set(pub(crate))]
97    #[get_mut(pub(crate))]
98    pub(crate) lifetime: f64,
99}
100
101/// A point emitter that continuously (or in bursts) spawns particles,
102/// integrates their motion, and records them into a `DrawList` as colored
103/// circles whose color and size interpolate over each particle's lifetime.
104#[derive(Clone, Data, Debug, New, PartialEq)]
105pub struct ParticleEmitter {
106    /// The world-space emission point.
107    #[get(type(copy))]
108    #[get_mut(pub(crate))]
109    #[set(pub(crate))]
110    pub(crate) position: Vector2D,
111    /// The emitter configuration.
112    #[get(type(copy))]
113    #[set(pub(crate))]
114    #[get_mut(pub(crate))]
115    pub(crate) config: ParticleConfig,
116    /// All currently live particles.
117    #[get(type(clone))]
118    #[get_mut(pub(crate))]
119    #[set(pub(crate))]
120    #[new(skip)]
121    #[get(pub(crate))]
122    pub(crate) particles: Vec<Particle>,
123    /// The fractional particle spawn budget carried between updates.
124    #[get(type(copy))]
125    #[get_mut(pub(crate))]
126    #[set(pub(crate))]
127    #[new(skip)]
128    #[get(pub(crate))]
129    pub(crate) emit_accumulator: f64,
130    /// Whether continuous emission is currently enabled.
131    #[get(type(copy))]
132    #[new(skip)]
133    #[get_mut(pub(crate))]
134    pub(crate) active: bool,
135    /// The emitter's deterministic random generator.
136    #[get(type(copy))]
137    #[get_mut(pub(crate))]
138    #[set(pub(crate))]
139    #[new(skip)]
140    #[get(pub(crate))]
141    pub(crate) rng: ParticleRng,
142}