1use crate::math::{add3, scale3, sub3};
2use crate::noise::{Rng, noise3};
3use crate::wind::Wind;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum ParticleKind {
7 Petal,
8 Dust,
9 Steam,
10}
11
12#[derive(Clone, Copy, Debug)]
13pub struct Speck {
14 pub pos: [f32; 3],
15 pub vel: [f32; 3],
16 pub age: f32,
17 pub life: f32,
18 pub seed: u32,
19}
20
21pub struct Emitter {
22 kind: ParticleKind,
23 origin: [f32; 3],
24 rate: f32,
25 carry: f32,
26 max: usize,
27 rng: Rng,
28 specks: Vec<Speck>,
29}
30
31impl Emitter {
32 pub fn new(kind: ParticleKind, origin: [f32; 3], rate: f32, seed: u64) -> Self {
33 Self {
34 kind,
35 origin,
36 rate,
37 carry: 0.0,
38 max: 256,
39 rng: Rng::new(seed),
40 specks: Vec::new(),
41 }
42 }
43
44 pub fn specks(&self) -> &[Speck] {
45 &self.specks
46 }
47
48 pub fn origin(&self) -> [f32; 3] {
49 self.origin
50 }
51
52 pub fn set_origin(&mut self, origin: [f32; 3]) {
53 if origin.iter().all(|value| value.is_finite()) {
54 self.origin = origin;
55 }
56 }
57
58 pub fn step(&mut self, dt: f32, time: f32, wind: &Wind) {
59 if !dt.is_finite() || dt <= 0.0 {
60 return;
61 }
62 self.carry += self.rate * dt;
63 while self.carry >= 1.0 && self.specks.len() < self.max {
64 self.carry -= 1.0;
65 self.spawn();
66 }
67 let kind = self.kind;
68 for speck in &mut self.specks {
69 let blow = wind.sample(speck.pos, time);
70 let (drag, gravity) = match kind {
71 ParticleKind::Petal => (1.6, [0.0, -1.4, 0.0]),
72 ParticleKind::Dust => (6.0, [0.0, -0.02, 0.0]),
73 ParticleKind::Steam => (2.2, [0.0, 0.9, 0.0]),
74 };
75 let mut vel = pull(speck.vel, blow, drag, dt);
76 vel = add3(vel, scale3(gravity, dt));
77 if kind == ParticleKind::Petal {
78 let n = noise3([
79 speck.pos[0] * 3.0 + speck.seed as f32 * 0.001,
80 time * 2.0,
81 speck.pos[2] * 3.0,
82 ]);
83 let m = noise3([
84 speck.pos[2] * 3.0,
85 time * 1.7 + 4.0,
86 speck.pos[0] * 3.0 + speck.seed as f32 * 0.002,
87 ]);
88 vel[0] += (n - 0.5) * dt;
89 vel[2] += (m - 0.5) * dt;
90 }
91 speck.vel = vel;
92 speck.pos = add3(speck.pos, scale3(vel, dt));
93 speck.age += dt;
94 }
95 self.specks.retain(|speck| speck.age < speck.life);
96 }
97
98 fn spawn(&mut self) {
99 let (life, spread, vel) = match self.kind {
100 ParticleKind::Petal => (
101 self.rng.range(4.0, 7.0),
102 0.15,
103 [
104 self.rng.range(-0.2, 0.2),
105 self.rng.range(-0.15, 0.05),
106 self.rng.range(-0.2, 0.2),
107 ],
108 ),
109 ParticleKind::Dust => (
110 self.rng.range(8.0, 12.0),
111 0.4,
112 [
113 self.rng.range(-0.05, 0.05),
114 self.rng.range(-0.02, 0.04),
115 self.rng.range(-0.05, 0.05),
116 ],
117 ),
118 ParticleKind::Steam => (
119 self.rng.range(1.4, 2.8),
120 0.04,
121 [
122 self.rng.range(-0.05, 0.05),
123 self.rng.range(0.15, 0.35),
124 self.rng.range(-0.05, 0.05),
125 ],
126 ),
127 };
128 let seed = self.rng.next_u32();
129 let pos = [
130 self.origin[0] + self.rng.range(-spread, spread),
131 self.origin[1] + self.rng.range(-spread * 0.25, spread * 0.25),
132 self.origin[2] + self.rng.range(-spread, spread),
133 ];
134 self.specks.push(Speck {
135 pos,
136 vel,
137 age: 0.0,
138 life,
139 seed,
140 });
141 }
142}
143
144fn pull(vel: [f32; 3], toward: [f32; 3], drag: f32, dt: f32) -> [f32; 3] {
145 let k = 1.0 - (-drag * dt).exp();
146 add3(vel, scale3(sub3(toward, vel), k))
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 fn run(kind: ParticleKind, seed: u64) -> Vec<Speck> {
154 let wind = Wind::new(11, [1.0, 0.0, 0.2], 1.2);
155 let mut emitter = Emitter::new(kind, [0.0, 2.0, 0.0], 8.0, seed);
156 let dt = 1.0 / 60.0;
157 let mut time = 0.0;
158 for _ in 0..240 {
159 emitter.step(dt, time, &wind);
160 time += dt;
161 }
162 emitter.specks().to_vec()
163 }
164
165 #[test]
166 fn the_same_seed_repeats_every_kind() {
167 for kind in [ParticleKind::Petal, ParticleKind::Dust, ParticleKind::Steam] {
168 let a = run(kind, 99);
169 let b = run(kind, 99);
170 assert!(!a.is_empty());
171 assert_eq!(a.len(), b.len());
172 for (left, right) in a.iter().zip(&b) {
173 assert_eq!(left.pos, right.pos);
174 assert_eq!(left.vel, right.vel);
175 assert_eq!(left.age, right.age);
176 assert!(left.pos.iter().all(|v| v.is_finite()));
177 }
178 }
179 }
180
181 #[test]
182 fn moved_origin_spawns_at_the_new_place() {
183 let wind = Wind::new(4, [1.0, 0.0, 0.0], 0.0);
184 let mut emitter = Emitter::new(ParticleKind::Petal, [0.0, 2.0, 0.0], 60.0, 8);
185 emitter.step(0.1, 0.0, &wind);
186 let before = emitter.specks().len();
187 emitter.set_origin([5.0, 2.0, -3.0]);
188 emitter.set_origin([f32::NAN, 0.0, 0.0]);
189 assert_eq!(emitter.origin(), [5.0, 2.0, -3.0]);
190 emitter.step(0.1, 0.1, &wind);
191 let fresh = &emitter.specks()[before..];
192 assert!(!fresh.is_empty());
193 for speck in fresh {
194 assert!((speck.pos[0] - 5.0).abs() < 0.5);
195 assert!((speck.pos[2] + 3.0).abs() < 0.5);
196 }
197 }
198
199 #[test]
200 fn petals_fall_and_steam_rises() {
201 let wind = Wind::new(4, [1.0, 0.0, 0.0], 0.4);
202 let origin = [0.0, 3.0, 0.0];
203 let mut petals = Emitter::new(ParticleKind::Petal, origin, 6.0, 5);
204 let mut steam = Emitter::new(ParticleKind::Steam, origin, 10.0, 5);
205 let dt = 1.0 / 60.0;
206 let mut time = 0.0;
207 for _ in 0..180 {
208 petals.step(dt, time, &wind);
209 steam.step(dt, time, &wind);
210 time += dt;
211 }
212 let petal = petals
213 .specks()
214 .iter()
215 .max_by(|a, b| a.age.total_cmp(&b.age))
216 .unwrap();
217 let puff = steam
218 .specks()
219 .iter()
220 .max_by(|a, b| a.age.total_cmp(&b.age))
221 .unwrap();
222 assert!(petal.pos[1] < origin[1], "{}", petal.pos[1]);
223 assert!(puff.pos[1] > origin[1], "{}", puff.pos[1]);
224 }
225}