1use eframe::egui::Color32;
7use nalgebra::Vector2;
8use std::collections::VecDeque;
9
10#[derive(Debug, Clone)]
12pub struct FlowParticle {
13 pub source: u16,
15 pub target: u16,
17 pub progress: f32,
19 pub speed: f32,
21 pub color: Color32,
23 pub size: f32,
25 pub suspicious: bool,
27 pub flow_id: uuid::Uuid,
29}
30
31impl FlowParticle {
32 pub fn new(source: u16, target: u16, flow_id: uuid::Uuid) -> Self {
34 Self {
35 source,
36 target,
37 progress: 0.0,
38 speed: 0.25, color: Color32::from_rgba_unmultiplied(100, 200, 160, 150), size: 2.5, suspicious: false,
42 flow_id,
43 }
44 }
45
46 pub fn update(&mut self, dt: f32) -> bool {
48 self.progress += self.speed * dt;
49 self.progress < 1.0
50 }
51
52 pub fn position(&self, source_pos: Vector2<f32>, target_pos: Vector2<f32>) -> Vector2<f32> {
54 let t = self.ease_in_out(self.progress);
56 source_pos + (target_pos - source_pos) * t
57 }
58
59 fn ease_in_out(&self, t: f32) -> f32 {
61 if t < 0.5 {
62 2.0 * t * t
63 } else {
64 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
65 }
66 }
67}
68
69pub struct ParticleSystem {
71 particles: VecDeque<FlowParticle>,
73 pub max_particles: usize,
75 pub spawn_rate: f32,
77 spawn_accumulator: f32,
79 pending_spawns: Vec<(u16, u16, uuid::Uuid, Color32, bool)>,
81 pub paused: bool,
83}
84
85impl ParticleSystem {
86 pub fn new(max_particles: usize) -> Self {
88 Self {
89 particles: VecDeque::with_capacity(max_particles),
90 max_particles,
91 spawn_rate: 0.8, spawn_accumulator: 0.0,
93 pending_spawns: Vec::new(),
94 paused: false,
95 }
96 }
97
98 pub fn queue_flow(
100 &mut self,
101 source: u16,
102 target: u16,
103 flow_id: uuid::Uuid,
104 color: Color32,
105 suspicious: bool,
106 ) {
107 self.pending_spawns
108 .push((source, target, flow_id, color, suspicious));
109 }
110
111 pub fn clear_pending(&mut self) {
113 self.pending_spawns.clear();
114 }
115
116 pub fn update(&mut self, dt: f32) {
118 if self.paused {
119 return;
120 }
121
122 self.particles.retain(|p| p.progress < 1.0);
124
125 for particle in &mut self.particles {
127 particle.update(dt);
128 }
129
130 self.spawn_accumulator += dt;
132 let spawn_interval = 1.0 / self.spawn_rate;
133
134 while self.spawn_accumulator >= spawn_interval && !self.pending_spawns.is_empty() {
135 self.spawn_accumulator -= spawn_interval;
136
137 if let Some((source, target, flow_id, color, suspicious)) = self.pending_spawns.pop() {
139 if self.particles.len() < self.max_particles {
140 let mut particle = FlowParticle::new(source, target, flow_id);
141 particle.color = Color32::from_rgba_unmultiplied(
143 color.r(),
144 color.g(),
145 color.b(),
146 if suspicious { 180 } else { 100 }, );
148 particle.suspicious = suspicious;
149 particle.size = if suspicious { 4.0 } else { 2.5 }; particle.speed = if suspicious { 0.2 } else { 0.25 }; self.particles.push_back(particle);
152 }
153
154 self.pending_spawns
156 .insert(0, (source, target, flow_id, color, suspicious));
157 }
158 }
159 }
160
161 pub fn particles(&self) -> impl Iterator<Item = &FlowParticle> {
163 self.particles.iter()
164 }
165
166 pub fn count(&self) -> usize {
168 self.particles.len()
169 }
170
171 pub fn clear(&mut self) {
173 self.particles.clear();
174 self.pending_spawns.clear();
175 }
176
177 pub fn burst(
179 &mut self,
180 source: u16,
181 target: u16,
182 flow_id: uuid::Uuid,
183 color: Color32,
184 count: usize,
185 ) {
186 for i in 0..count {
187 if self.particles.len() >= self.max_particles {
188 break;
189 }
190
191 let mut particle = FlowParticle::new(source, target, flow_id);
192 particle.color = color;
193 particle.progress = i as f32 * 0.1; particle.speed = 0.3 + (i as f32 * 0.05); self.particles.push_back(particle);
196 }
197 }
198}
199
200impl Default for ParticleSystem {
201 fn default() -> Self {
202 Self::new(300) }
204}
205
206#[derive(Debug, Clone)]
208pub struct ParticleTrail {
209 positions: VecDeque<Vector2<f32>>,
211 max_length: usize,
213 pub color: Color32,
215}
216
217impl ParticleTrail {
218 pub fn new(max_length: usize, color: Color32) -> Self {
220 Self {
221 positions: VecDeque::with_capacity(max_length),
222 max_length,
223 color,
224 }
225 }
226
227 pub fn push(&mut self, position: Vector2<f32>) {
229 self.positions.push_front(position);
230 while self.positions.len() > self.max_length {
231 self.positions.pop_back();
232 }
233 }
234
235 pub fn points(&self) -> impl Iterator<Item = (Vector2<f32>, f32)> + '_ {
237 let len = self.positions.len() as f32;
238 self.positions.iter().enumerate().map(move |(i, &pos)| {
239 let alpha = 1.0 - (i as f32 / len);
240 (pos, alpha)
241 })
242 }
243
244 pub fn clear(&mut self) {
246 self.positions.clear();
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_particle_creation() {
256 let particle = FlowParticle::new(0, 1, uuid::Uuid::new_v4());
257 assert_eq!(particle.progress, 0.0);
258 assert_eq!(particle.source, 0);
259 assert_eq!(particle.target, 1);
260 }
261
262 #[test]
263 fn test_particle_update() {
264 let mut particle = FlowParticle::new(0, 1, uuid::Uuid::new_v4());
265 particle.speed = 1.0;
266 assert!(particle.update(0.5));
267 assert_eq!(particle.progress, 0.5);
268 assert!(!particle.update(0.6));
269 }
270
271 #[test]
272 fn test_particle_system() {
273 let mut system = ParticleSystem::new(100);
274 system.spawn_rate = 2.0; system.queue_flow(0, 1, uuid::Uuid::new_v4(), Color32::WHITE, false);
276 system.update(1.0);
277 assert!(system.count() > 0);
278 }
279}