use eframe::egui::Color32;
use nalgebra::Vector2;
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub struct FlowParticle {
pub source: u16,
pub target: u16,
pub progress: f32,
pub speed: f32,
pub color: Color32,
pub size: f32,
pub suspicious: bool,
pub flow_id: uuid::Uuid,
}
impl FlowParticle {
pub fn new(source: u16, target: u16, flow_id: uuid::Uuid) -> Self {
Self {
source,
target,
progress: 0.0,
speed: 0.25, color: Color32::from_rgba_unmultiplied(100, 200, 160, 150), size: 2.5, suspicious: false,
flow_id,
}
}
pub fn update(&mut self, dt: f32) -> bool {
self.progress += self.speed * dt;
self.progress < 1.0
}
pub fn position(&self, source_pos: Vector2<f32>, target_pos: Vector2<f32>) -> Vector2<f32> {
let t = self.ease_in_out(self.progress);
source_pos + (target_pos - source_pos) * t
}
fn ease_in_out(&self, t: f32) -> f32 {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
}
}
}
pub struct ParticleSystem {
particles: VecDeque<FlowParticle>,
pub max_particles: usize,
pub spawn_rate: f32,
spawn_accumulator: f32,
pending_spawns: Vec<(u16, u16, uuid::Uuid, Color32, bool)>,
pub paused: bool,
}
impl ParticleSystem {
pub fn new(max_particles: usize) -> Self {
Self {
particles: VecDeque::with_capacity(max_particles),
max_particles,
spawn_rate: 0.8, spawn_accumulator: 0.0,
pending_spawns: Vec::new(),
paused: false,
}
}
pub fn queue_flow(
&mut self,
source: u16,
target: u16,
flow_id: uuid::Uuid,
color: Color32,
suspicious: bool,
) {
self.pending_spawns
.push((source, target, flow_id, color, suspicious));
}
pub fn clear_pending(&mut self) {
self.pending_spawns.clear();
}
pub fn update(&mut self, dt: f32) {
if self.paused {
return;
}
self.particles.retain(|p| p.progress < 1.0);
for particle in &mut self.particles {
particle.update(dt);
}
self.spawn_accumulator += dt;
let spawn_interval = 1.0 / self.spawn_rate;
while self.spawn_accumulator >= spawn_interval && !self.pending_spawns.is_empty() {
self.spawn_accumulator -= spawn_interval;
if let Some((source, target, flow_id, color, suspicious)) = self.pending_spawns.pop() {
if self.particles.len() < self.max_particles {
let mut particle = FlowParticle::new(source, target, flow_id);
particle.color = Color32::from_rgba_unmultiplied(
color.r(),
color.g(),
color.b(),
if suspicious { 180 } else { 100 }, );
particle.suspicious = suspicious;
particle.size = if suspicious { 4.0 } else { 2.5 }; particle.speed = if suspicious { 0.2 } else { 0.25 }; self.particles.push_back(particle);
}
self.pending_spawns
.insert(0, (source, target, flow_id, color, suspicious));
}
}
}
pub fn particles(&self) -> impl Iterator<Item = &FlowParticle> {
self.particles.iter()
}
pub fn count(&self) -> usize {
self.particles.len()
}
pub fn clear(&mut self) {
self.particles.clear();
self.pending_spawns.clear();
}
pub fn burst(
&mut self,
source: u16,
target: u16,
flow_id: uuid::Uuid,
color: Color32,
count: usize,
) {
for i in 0..count {
if self.particles.len() >= self.max_particles {
break;
}
let mut particle = FlowParticle::new(source, target, flow_id);
particle.color = color;
particle.progress = i as f32 * 0.1; particle.speed = 0.3 + (i as f32 * 0.05); self.particles.push_back(particle);
}
}
}
impl Default for ParticleSystem {
fn default() -> Self {
Self::new(300) }
}
#[derive(Debug, Clone)]
pub struct ParticleTrail {
positions: VecDeque<Vector2<f32>>,
max_length: usize,
pub color: Color32,
}
impl ParticleTrail {
pub fn new(max_length: usize, color: Color32) -> Self {
Self {
positions: VecDeque::with_capacity(max_length),
max_length,
color,
}
}
pub fn push(&mut self, position: Vector2<f32>) {
self.positions.push_front(position);
while self.positions.len() > self.max_length {
self.positions.pop_back();
}
}
pub fn points(&self) -> impl Iterator<Item = (Vector2<f32>, f32)> + '_ {
let len = self.positions.len() as f32;
self.positions.iter().enumerate().map(move |(i, &pos)| {
let alpha = 1.0 - (i as f32 / len);
(pos, alpha)
})
}
pub fn clear(&mut self) {
self.positions.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_particle_creation() {
let particle = FlowParticle::new(0, 1, uuid::Uuid::new_v4());
assert_eq!(particle.progress, 0.0);
assert_eq!(particle.source, 0);
assert_eq!(particle.target, 1);
}
#[test]
fn test_particle_update() {
let mut particle = FlowParticle::new(0, 1, uuid::Uuid::new_v4());
particle.speed = 1.0;
assert!(particle.update(0.5));
assert_eq!(particle.progress, 0.5);
assert!(!particle.update(0.6));
}
#[test]
fn test_particle_system() {
let mut system = ParticleSystem::new(100);
system.spawn_rate = 2.0; system.queue_flow(0, 1, uuid::Uuid::new_v4(), Color32::WHITE, false);
system.update(1.0);
assert!(system.count() > 0);
}
}