use crate::math::{Vec3, constants};
pub fn gravitational_force(m1: f64, m2: f64, distance: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
constants::G * m1 * m2 / (distance * distance)
}
pub fn gravitational_force_vec(m1: f64, pos1: Vec3, m2: f64, pos2: Vec3) -> Vec3 {
let r = pos2 - pos1;
let dist = r.magnitude();
if dist == 0.0 {
return Vec3::ZERO;
}
let f_mag = constants::G * m1 * m2 / (dist * dist);
r.normalized() * f_mag
}
pub fn gravitational_potential_energy(m1: f64, m2: f64, distance: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
-constants::G * m1 * m2 / distance
}
pub fn gravitational_field(mass: f64, distance: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
constants::G * mass / (distance * distance)
}
pub fn escape_velocity(mass: f64, radius: f64) -> f64 {
assert!(radius > 0.0, "radius must be positive");
(2.0 * constants::G * mass / radius).sqrt()
}
pub fn orbital_velocity(central_mass: f64, orbital_radius: f64) -> f64 {
assert!(orbital_radius > 0.0, "orbital_radius must be positive");
(constants::G * central_mass / orbital_radius).sqrt()
}
pub fn orbital_period(central_mass: f64, orbital_radius: f64) -> f64 {
assert!(central_mass > 0.0, "central_mass must be positive");
assert!(orbital_radius > 0.0, "orbital_radius must be positive");
2.0 * constants::PI * (orbital_radius.powi(3) / (constants::G * central_mass)).sqrt()
}
pub fn semi_major_axis_from_period(central_mass: f64, period: f64) -> f64 {
assert!(central_mass > 0.0, "central_mass must be positive");
assert!(period > 0.0, "period must be positive");
(constants::G * central_mass * period * period / (4.0 * constants::PI * constants::PI))
.powf(1.0 / 3.0)
}
pub fn schwarzschild_radius(mass: f64) -> f64 {
2.0 * constants::G * mass / (constants::C * constants::C)
}
pub fn gravitational_time_dilation(mass: f64, distance: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
(1.0 - 2.0 * constants::G * mass / (distance * constants::C * constants::C)).sqrt()
}
pub fn roche_limit(primary_radius: f64, primary_density: f64, satellite_density: f64) -> f64 {
assert!(satellite_density > 0.0, "satellite_density must be positive");
primary_radius * (2.0 * primary_density / satellite_density).powf(1.0 / 3.0)
}
pub fn vis_viva(central_mass: f64, distance: f64, semi_major_axis: f64) -> f64 {
assert!(distance > 0.0, "distance must be positive");
assert!(semi_major_axis > 0.0, "semi_major_axis must be positive");
(constants::G * central_mass * (2.0 / distance - 1.0 / semi_major_axis)).sqrt()
}
pub fn specific_orbital_energy(central_mass: f64, semi_major_axis: f64) -> f64 {
assert!(semi_major_axis > 0.0, "semi_major_axis must be positive");
-constants::G * central_mass / (2.0 * semi_major_axis)
}
pub fn hill_sphere_radius(semi_major_axis: f64, orbiting_mass: f64, central_mass: f64) -> f64 {
assert!(central_mass > 0.0, "central_mass must be positive");
semi_major_axis * (orbiting_mass / (3.0 * central_mass)).powf(1.0 / 3.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_rel(a: f64, b: f64, tol: f64) -> bool {
((a - b) / b).abs() < tol
}
#[test]
fn test_gravitational_force() {
let f = gravitational_force(5.97e24, 7.35e22, 3.84e8);
assert!(approx_rel(f, 1.98e20, 0.02));
}
#[test]
fn test_escape_velocity_earth() {
let v = escape_velocity(5.97e24, 6.371e6);
assert!(approx_rel(v, 11186.0, 0.01));
}
#[test]
fn test_orbital_velocity() {
let v = orbital_velocity(5.97e24, 6.771e6);
assert!(approx_rel(v, 7670.0, 0.01));
}
#[test]
fn test_orbital_period() {
let t = orbital_period(5.97e24, 6.771e6);
assert!(approx_rel(t, 5549.0, 0.01));
}
#[test]
fn test_schwarzschild_radius_sun() {
let r = schwarzschild_radius(1.989e30);
assert!(approx_rel(r, 2953.0, 0.01));
}
#[test]
fn test_gravitational_potential_energy() {
let u = gravitational_potential_energy(5.97e24, 1.0, 6.371e6);
assert!(u < 0.0);
}
#[test]
fn test_gravitational_field_earth_surface() {
let g = gravitational_field(5.97e24, 6.371e6);
assert!(approx_rel(g, 9.82, 0.01));
}
#[test]
fn test_gravitational_force_vec() {
let pos1 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let pos2 = Vec3 { x: 1.0, y: 0.0, z: 0.0 };
let f = gravitational_force_vec(1.0, pos1, 1.0, pos2);
assert!(approx_rel(f.x, constants::G, 1e-6));
assert!(f.y.abs() < 1e-30);
assert!(f.z.abs() < 1e-30);
}
#[test]
fn test_gravitational_force_vec_zero_distance() {
let pos = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
let f = gravitational_force_vec(1.0, pos, 1.0, pos);
assert!(f.x.abs() < 1e-30);
assert!(f.y.abs() < 1e-30);
assert!(f.z.abs() < 1e-30);
}
#[test]
fn test_gravitational_time_dilation() {
let factor = gravitational_time_dilation(1.0, 1e20);
assert!(approx_rel(factor, 1.0, 1e-6));
let factor_earth = gravitational_time_dilation(5.97e24, 6.371e6);
assert!(factor_earth < 1.0);
assert!(factor_earth > 0.999_999_999);
}
#[test]
fn test_hill_sphere_radius() {
let r_h = hill_sphere_radius(1.496e11, 5.97e24, 1.989e30);
assert!(approx_rel(r_h, 1.496e9, 0.02));
}
#[test]
fn test_roche_limit() {
let d = roche_limit(6.371e6, 5515.0, 3340.0);
assert!(approx_rel(d, 9.486e6, 0.01));
}
#[test]
fn test_semi_major_axis_from_period() {
let a_recovered = semi_major_axis_from_period(5.97e24, 5549.0);
assert!(approx_rel(a_recovered, 6.771e6, 0.01));
}
#[test]
fn test_specific_orbital_energy() {
let eps = specific_orbital_energy(5.97e24, 6.771e6);
assert!(eps < 0.0);
assert!(approx_rel(eps, -2.943e7, 0.01));
}
#[test]
fn test_vis_viva_circular() {
let v = vis_viva(5.97e24, 6.771e6, 6.771e6);
assert!(approx_rel(v, 7672.0, 0.01));
}
}