Skip to main content

ry_anim/
particles.rs

1// crates/rydit-anim/src/particles.rs
2// Particle System - Sistema de partículas para efectos visuales
3
4use std::cell::RefCell;
5use std::rc::Rc;
6
7// ============================================================================
8// ESTRUCTURAS DE PARTÍCULAS
9// ============================================================================
10
11/// Partícula individual
12#[derive(Debug, Clone)]
13pub struct Particle {
14    pub x: f64,
15    pub y: f64,
16    pub vx: f64,
17    pub vy: f64,
18    pub life: f64,
19    pub size: f64,
20    pub color: String,
21}
22
23impl Particle {
24    pub fn new(x: f64, y: f64, vx: f64, vy: f64, life: f64, size: f64, color: &str) -> Self {
25        Self {
26            x,
27            y,
28            vx,
29            vy,
30            life,
31            size,
32            color: color.to_string(),
33        }
34    }
35
36    pub fn update(&mut self, gravity: f64, friction: f64) {
37        self.vy += gravity;
38        self.vx *= friction;
39        self.vy *= friction;
40        self.x += self.vx;
41        self.y += self.vy;
42        self.life -= 0.02;
43    }
44
45    pub fn is_alive(&self) -> bool {
46        self.life > 0.0
47    }
48}
49
50/// Sistema de partículas
51pub struct ParticleSystem {
52    pub particles: Vec<Particle>,
53    pub gravity: f64,
54    pub friction: f64,
55}
56
57impl ParticleSystem {
58    pub fn new() -> Self {
59        Self {
60            particles: Vec::new(),
61            gravity: 0.1,
62            friction: 0.98,
63        }
64    }
65
66    pub fn emit(&mut self, x: f64, y: f64, effect: &str, count: usize) {
67        match effect {
68            "fire" => self.emit_fire(x, y, count),
69            "smoke" => self.emit_smoke(x, y, count),
70            "spark" => self.emit_spark(x, y, count),
71            "explosion" => self.emit_explosion(x, y, count),
72            "rain" => self.emit_rain(x, y, count),
73            _ => self.emit_default(x, y, count),
74        }
75    }
76
77    pub fn update(&mut self) {
78        for particle in &mut self.particles {
79            particle.update(self.gravity, self.friction);
80        }
81        self.particles.retain(|p| p.is_alive());
82    }
83
84    pub fn count(&self) -> usize {
85        self.particles.len()
86    }
87
88    pub fn clear(&mut self) {
89        self.particles.clear();
90    }
91
92    fn emit_fire(&mut self, x: f64, y: f64, count: usize) {
93        for _ in 0..count {
94            let vx = (rand_f64() - 0.5) * 2.0;
95            let vy = -(rand_f64() * 3.0 + 2.0);
96            let life = rand_f64() * 0.5 + 0.5;
97            let size = rand_f64() * 4.0 + 2.0;
98            let color = if rand_f64() > 0.5 {
99                "naranja"
100            } else {
101                "amarillo"
102            };
103            self.particles
104                .push(Particle::new(x, y, vx, vy, life, size, color));
105        }
106    }
107
108    fn emit_smoke(&mut self, x: f64, y: f64, count: usize) {
109        for _ in 0..count {
110            let vx = (rand_f64() - 0.5) * 1.0;
111            let vy = -(rand_f64() * 1.5 + 0.5);
112            let life = rand_f64() * 0.8 + 0.2;
113            let size = rand_f64() * 6.0 + 4.0;
114            self.particles
115                .push(Particle::new(x, y, vx, vy, life, size, "gris"));
116        }
117    }
118
119    fn emit_spark(&mut self, x: f64, y: f64, count: usize) {
120        for _ in 0..count {
121            let angle = rand_f64() * std::f64::consts::PI * 2.0;
122            let speed = rand_f64() * 5.0 + 3.0;
123            let vx = angle.cos() * speed;
124            let vy = angle.sin() * speed;
125            let life = rand_f64() * 0.3 + 0.2;
126            let size = rand_f64() * 2.0 + 1.0;
127            self.particles
128                .push(Particle::new(x, y, vx, vy, life, size, "amarillo"));
129        }
130    }
131
132    fn emit_explosion(&mut self, x: f64, y: f64, count: usize) {
133        for _ in 0..count {
134            let angle = rand_f64() * std::f64::consts::PI * 2.0;
135            let speed = rand_f64() * 6.0 + 4.0;
136            let vx = angle.cos() * speed;
137            let vy = angle.sin() * speed;
138            let life = rand_f64() * 0.4 + 0.2;
139            let size = rand_f64() * 3.0 + 2.0;
140            let color = if rand_f64() > 0.5 { "naranja" } else { "rojo" };
141            self.particles
142                .push(Particle::new(x, y, vx, vy, life, size, color));
143        }
144    }
145
146    fn emit_rain(&mut self, x: f64, y: f64, count: usize) {
147        for _ in 0..count {
148            let vx = (rand_f64() - 0.5) * 0.5;
149            let vy = rand_f64() * 3.0 + 5.0;
150            let life = 1.0;
151            let size = rand_f64() * 8.0 + 4.0;
152            self.particles
153                .push(Particle::new(x, y, vx, vy, life, size, "azul"));
154        }
155    }
156
157    fn emit_default(&mut self, x: f64, y: f64, count: usize) {
158        for _ in 0..count {
159            let vx = (rand_f64() - 0.5) * 2.0;
160            let vy = (rand_f64() - 0.5) * 2.0;
161            let life = rand_f64() * 0.5 + 0.5;
162            let size = rand_f64() * 4.0 + 2.0;
163            self.particles
164                .push(Particle::new(x, y, vx, vy, life, size, "blanco"));
165        }
166    }
167}
168
169impl Default for ParticleSystem {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175fn rand_f64() -> f64 {
176    static mut SEED: u32 = 12345;
177    unsafe {
178        SEED = SEED.wrapping_mul(1664525).wrapping_add(1013904223);
179        (SEED as f64) / (u32::MAX as f64)
180    }
181}
182
183// ============================================================================
184// ESTADO GLOBAL
185// ============================================================================
186
187thread_local! {
188    static PARTICLE_SYSTEM: Rc<RefCell<ParticleSystem>> = Rc::new(RefCell::new(ParticleSystem::new()));
189}
190
191pub fn get_particle_system() -> Rc<RefCell<ParticleSystem>> {
192    PARTICLE_SYSTEM.with(|ps| ps.clone())
193}
194
195// ============================================================================
196// TESTS
197// ============================================================================
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_particle_new() {
205        let p = Particle::new(100.0, 200.0, 1.0, -2.0, 1.0, 5.0, "rojo");
206        assert_eq!(p.x, 100.0);
207        assert_eq!(p.y, 200.0);
208    }
209
210    #[test]
211    fn test_particle_system() {
212        let mut ps = ParticleSystem::new();
213        ps.emit(400.0, 300.0, "fire", 20);
214        assert!(ps.count() > 0);
215        ps.update();
216        ps.clear();
217        assert_eq!(ps.count(), 0);
218    }
219}