Skip to main content

rydit_physics/
lib.rs

1//! RyDit Physics - Módulo de Física para RyDit
2//! 
3//! Proporciona funcionalidad de:
4//! - Proyectil 2D (trayectoria, altura máxima, alcance)
5//! - Gravedad N-cuerpos (2 cuerpos)
6
7use rydit_core::{RyditModule, ModuleResult, ModuleError};
8use serde_json::{Value, json};
9use std::collections::HashMap;
10
11/// Módulo de Física - Proyectil y Gravedad
12pub struct PhysicsModule;
13
14impl RyditModule for PhysicsModule {
15    fn name(&self) -> &'static str {
16        "physics"
17    }
18
19    fn version(&self) -> &'static str {
20        "0.7.3"
21    }
22
23    fn register(&self) -> HashMap<&'static str, &'static str> {
24        let mut cmds = HashMap::new();
25        cmds.insert("projectile", "Simulación de proyectil");
26        cmds.insert("nbody_2", "Simulación N-cuerpos (2 cuerpos)");
27        cmds
28    }
29
30    fn execute(&self, command: &str, params: Value) -> ModuleResult {
31        match command {
32            "projectile" => self.projectile(params),
33            "nbody_2" => self.nbody_2(params),
34            _ => Err(ModuleError {
35                code: "UNKNOWN_COMMAND".to_string(),
36                message: format!("Comando desconocido: {}", command),
37            }),
38        }
39    }
40}
41
42impl PhysicsModule {
43    /// Simulación de proyectil 2D
44    /// 
45    /// # Params
46    /// - x0, y0: Posición inicial
47    /// - v0: Velocidad inicial (m/s)
48    /// - angle: Ángulo en grados
49    /// 
50    /// # Returns
51    /// [x_final, y_final, flight_time, max_height, range]
52    fn projectile(&self, params: Value) -> ModuleResult {
53        let arr = params.as_array().ok_or_else(|| ModuleError {
54            code: "INVALID_PARAMS".to_string(),
55            message: "Params must be an array".to_string(),
56        })?;
57
58        if arr.len() != 4 {
59            return Err(ModuleError {
60                code: "INVALID_PARAMS".to_string(),
61                message: "physics::projectile requires 4 params: x0, y0, v0, angle".to_string(),
62            });
63        }
64
65        let x0 = arr[0].as_f64().unwrap_or(0.0);
66        let y0 = arr[1].as_f64().unwrap_or(0.0);
67        let v0 = arr[2].as_f64().unwrap_or(0.0);
68        let angle = arr[3].as_f64().unwrap_or(0.0);
69
70        let rad = angle.to_radians();
71        let vx = v0 * rad.cos();
72        let vy = v0 * rad.sin();
73        let g = 9.81;
74
75        let flight_time = 2.0 * vy / g;
76        let max_height = (vy * vy) / (2.0 * g);
77        let range = vx * flight_time;
78
79        Ok(json!([
80            x0 + vx * flight_time,  // x final
81            y0,                      // y final
82            flight_time,            // tiempo vuelo
83            max_height,             // altura máxima
84            range                   // alcance horizontal
85        ]))
86    }
87
88    /// Gravedad entre 2 cuerpos (Ley de Newton)
89    /// 
90    /// # Params
91    /// - m1, m2: Masas de los cuerpos
92    /// - x1, y1: Posición del cuerpo 1
93    /// - x2, y2: Posición del cuerpo 2
94    /// - G: Constante gravitacional (default: 6.674e-11)
95    /// 
96    /// # Returns
97    /// [fx1, fy1, fx2, fy2, distancia]
98    fn nbody_2(&self, params: Value) -> ModuleResult {
99        let arr = params.as_array().ok_or_else(|| ModuleError {
100            code: "INVALID_PARAMS".to_string(),
101            message: "Params must be an array".to_string(),
102        })?;
103
104        if arr.len() != 7 {
105            return Err(ModuleError {
106                code: "INVALID_PARAMS".to_string(),
107                message: "physics::nbody_2 requires 7 params: m1, m2, x1, y1, x2, y2, G".to_string(),
108            });
109        }
110
111        let m1 = arr[0].as_f64().unwrap_or(0.0);
112        let m2 = arr[1].as_f64().unwrap_or(0.0);
113        let x1 = arr[2].as_f64().unwrap_or(0.0);
114        let y1 = arr[3].as_f64().unwrap_or(0.0);
115        let x2 = arr[4].as_f64().unwrap_or(0.0);
116        let y2 = arr[5].as_f64().unwrap_or(0.0);
117        let g = arr[6].as_f64().unwrap_or(6.674e-11);
118
119        let dx = x2 - x1;
120        let dy = y2 - y1;
121        let dist = (dx * dx + dy * dy).sqrt();
122
123        if dist > 0.001 {
124            let force = g * m1 * m2 / (dist * dist);
125            let fx = force * dx / dist;
126            let fy = force * dy / dist;
127
128            Ok(json!([fx, fy, -fx, -fy, dist]))
129        } else {
130            Ok(json!([0.0, 0.0, 0.0, 0.0, dist]))
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_physics_module_name() {
141        let module = PhysicsModule;
142        assert_eq!(module.name(), "physics");
143        assert_eq!(module.version(), "0.7.3");
144    }
145
146    #[test]
147    fn test_physics_register() {
148        let module = PhysicsModule;
149        let cmds = module.register();
150
151        assert!(cmds.contains_key("projectile"));
152        assert!(cmds.contains_key("nbody_2"));
153    }
154
155    #[test]
156    fn test_projectile() {
157        let module = PhysicsModule;
158        // x0=0, y0=0, v0=10, angle=45
159        let params = json!([0.0, 0.0, 10.0, 45.0]);
160        let result = module.execute("projectile", params).unwrap();
161
162        let arr = result.as_array().unwrap();
163        assert_eq!(arr.len(), 5);
164        // flight_time ≈ 2*vy/g = 2*(10*sin(45))/9.81 ≈ 1.44s
165        let flight_time = arr[2].as_f64().unwrap();
166        assert!(flight_time > 1.4 && flight_time < 1.5);
167    }
168
169    #[test]
170    fn test_nbody_2() {
171        let module = PhysicsModule;
172        // m1=100, m2=200, x1=0, y1=0, x2=10, y2=0, G=1.0
173        let params = json!([100.0, 200.0, 0.0, 0.0, 10.0, 0.0, 1.0]);
174        let result = module.execute("nbody_2", params).unwrap();
175
176        let arr = result.as_array().unwrap();
177        assert_eq!(arr.len(), 5);
178        // fx = G*m1*m2/dist^2 * dx/dist = 1*100*200/100 * 10/10 = 200
179        let fx = arr[0].as_f64().unwrap();
180        assert!((fx - 200.0).abs() < 0.01);
181    }
182
183    #[test]
184    fn test_nbody_2_close() {
185        let module = PhysicsModule;
186        // Cuerpos muy cercanos (dist < 0.001)
187        let params = json!([100.0, 200.0, 0.0, 0.0, 0.0001, 0.0, 1.0]);
188        let result = module.execute("nbody_2", params).unwrap();
189
190        let arr = result.as_array().unwrap();
191        assert_eq!(arr[0].as_f64().unwrap(), 0.0);
192        assert_eq!(arr[1].as_f64().unwrap(), 0.0);
193    }
194
195    #[test]
196    fn test_unknown_command() {
197        let module = PhysicsModule;
198        let result = module.execute("unknown", json!([]));
199
200        assert!(result.is_err());
201        let err = result.unwrap_err();
202        assert_eq!(err.code, "UNKNOWN_COMMAND");
203    }
204}