use ry_core::{ModuleError, ModuleResult, RyditModule};
use serde_json::{json, Value};
use std::collections::HashMap;
pub struct PhysicsModule;
impl RyditModule for PhysicsModule {
fn name(&self) -> &'static str {
"physics"
}
fn version(&self) -> &'static str {
"0.7.3"
}
fn register(&self) -> HashMap<&'static str, &'static str> {
let mut cmds = HashMap::new();
cmds.insert("projectile", "Simulación de proyectil");
cmds.insert("nbody_2", "Simulación N-cuerpos (2 cuerpos)");
cmds.insert("nbody_simulate", "Simulación N-cuerpos (múltiples cuerpos)");
cmds
}
fn execute(&self, command: &str, params: Value) -> ModuleResult {
match command {
"projectile" => self.projectile(params),
"nbody_2" => self.nbody_2(params),
"nbody_simulate" => self.nbody_simulate(params),
_ => Err(ModuleError {
code: "UNKNOWN_COMMAND".to_string(),
message: format!("Comando desconocido: {}", command),
}),
}
}
}
impl PhysicsModule {
fn projectile(&self, params: Value) -> ModuleResult {
let arr = params.as_array().ok_or_else(|| ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "Params must be an array".to_string(),
})?;
if arr.len() != 4 {
return Err(ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "physics::projectile requires 4 params: x0, y0, v0, angle".to_string(),
});
}
let x0 = arr[0].as_f64().unwrap_or(0.0);
let y0 = arr[1].as_f64().unwrap_or(0.0);
let v0 = arr[2].as_f64().unwrap_or(0.0);
let angle = arr[3].as_f64().unwrap_or(0.0);
let rad = angle.to_radians();
let vx = v0 * rad.cos();
let vy = v0 * rad.sin();
let g = 9.81;
let flight_time = 2.0 * vy / g;
let max_height = (vy * vy) / (2.0 * g);
let range = vx * flight_time;
Ok(json!([
x0 + vx * flight_time, y0, flight_time, max_height, range ]))
}
fn nbody_2(&self, params: Value) -> ModuleResult {
let arr = params.as_array().ok_or_else(|| ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "Params must be an array".to_string(),
})?;
if arr.len() != 7 {
return Err(ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "physics::nbody_2 requires 7 params: m1, m2, x1, y1, x2, y2, G"
.to_string(),
});
}
let m1 = arr[0].as_f64().unwrap_or(0.0);
let m2 = arr[1].as_f64().unwrap_or(0.0);
let x1 = arr[2].as_f64().unwrap_or(0.0);
let y1 = arr[3].as_f64().unwrap_or(0.0);
let x2 = arr[4].as_f64().unwrap_or(0.0);
let y2 = arr[5].as_f64().unwrap_or(0.0);
let g = arr[6].as_f64().unwrap_or(6.674e-11);
let dx = x2 - x1;
let dy = y2 - y1;
let dist = (dx * dx + dy * dy).sqrt();
if dist > 0.001 {
let force = g * m1 * m2 / (dist * dist);
let fx = force * dx / dist;
let fy = force * dy / dist;
Ok(json!([fx, fy, -fx, -fy, dist]))
} else {
Ok(json!([0.0, 0.0, 0.0, 0.0, dist]))
}
}
pub fn nbody_simulate(&self, params: Value) -> ModuleResult {
let arr = params.as_array().ok_or_else(|| ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "nbody_simulate requiere array de bodies + dt + G".to_string(),
})?;
if arr.len() < 3 {
return Err(ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "nbody_simulate requiere [bodies, dt, G]".to_string(),
});
}
let bodies_arr = arr[0].as_array().ok_or_else(|| ModuleError {
code: "INVALID_PARAMS".to_string(),
message: "bodies debe ser array de [mass, x, y, vx, vy, is_static]".to_string(),
})?;
let dt = arr[1].as_f64().unwrap_or(0.016);
let g = arr[2].as_f64().unwrap_or(6.674e-11);
let n = bodies_arr.len();
if n == 0 {
return Ok(json!([]));
}
struct Body {
mass: f64, x: f64, y: f64, vx: f64, vy: f64, is_static: bool,
ax: f64, ay: f64,
}
let mut bodies: Vec<Body> = bodies_arr.iter().filter_map(|b| {
let a = b.as_array()?;
if a.len() < 6 { return None; }
Some(Body {
mass: a[0].as_f64()?,
x: a[1].as_f64()?,
y: a[2].as_f64()?,
vx: a[3].as_f64()?,
vy: a[4].as_f64()?,
is_static: a[5].as_f64().map(|v| v != 0.0).unwrap_or(false),
ax: 0.0, ay: 0.0,
})
}).collect();
for i in 0..n {
for j in (i + 1)..n {
let dx = bodies[j].x - bodies[i].x;
let dy = bodies[j].y - bodies[i].y;
let dist_sq = dx * dx + dy * dy;
let dist = dist_sq.sqrt();
if dist < 0.001 { continue; }
let force = g * bodies[i].mass * bodies[j].mass / dist_sq;
let ax = force * dx / (dist * bodies[i].mass);
let ay = force * dy / (dist * bodies[i].mass);
let ax_j = -force * dx / (dist * bodies[j].mass);
let ay_j = -force * dy / (dist * bodies[j].mass);
if !bodies[i].is_static { bodies[i].ax += ax; bodies[i].ay += ay; }
if !bodies[j].is_static { bodies[j].ax += ax_j; bodies[j].ay += ay_j; }
}
}
let result: Vec<Value> = bodies.iter().map(|b| {
let vx = if b.is_static { b.vx } else { b.vx + b.ax * dt };
let vy = if b.is_static { b.vy } else { b.vy + b.ay * dt };
let x = if b.is_static { b.x } else { b.x + vx * dt };
let y = if b.is_static { b.y } else { b.y + vy * dt };
json!([x, y, vx, vy])
}).collect();
Ok(json!(result))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_physics_module_name() {
let module = PhysicsModule;
assert_eq!(module.name(), "physics");
assert_eq!(module.version(), "0.7.3");
}
#[test]
fn test_physics_register() {
let module = PhysicsModule;
let cmds = module.register();
assert!(cmds.contains_key("projectile"));
assert!(cmds.contains_key("nbody_2"));
}
#[test]
fn test_projectile() {
let module = PhysicsModule;
let params = json!([0.0, 0.0, 10.0, 45.0]);
let result = module.execute("projectile", params).unwrap();
let arr = result.as_array().unwrap();
assert_eq!(arr.len(), 5);
let flight_time = arr[2].as_f64().unwrap();
assert!(flight_time > 1.4 && flight_time < 1.5);
}
#[test]
fn test_nbody_2() {
let module = PhysicsModule;
let params = json!([100.0, 200.0, 0.0, 0.0, 10.0, 0.0, 1.0]);
let result = module.execute("nbody_2", params).unwrap();
let arr = result.as_array().unwrap();
assert_eq!(arr.len(), 5);
let fx = arr[0].as_f64().unwrap();
assert!((fx - 200.0).abs() < 0.01);
}
#[test]
fn test_nbody_2_close() {
let module = PhysicsModule;
let params = json!([100.0, 200.0, 0.0, 0.0, 0.0001, 0.0, 1.0]);
let result = module.execute("nbody_2", params).unwrap();
let arr = result.as_array().unwrap();
assert_eq!(arr[0].as_f64().unwrap(), 0.0);
assert_eq!(arr[1].as_f64().unwrap(), 0.0);
}
#[test]
fn test_unknown_command() {
let module = PhysicsModule;
let result = module.execute("unknown", json!([]));
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, "UNKNOWN_COMMAND");
}
}