use nalgebra::Vector3;
use crate::kepler::params::SolverType;
use crate::outfit_errors::OutfitError;
use crate::GAUSS_GRAV;
use super::params::UniversalKeplerParams;
pub struct UniversalPropagResult {
pub r1: Vector3<f64>,
pub v1: Vector3<f64>,
pub f_lag: f64,
pub g_lag: f64,
pub f_dot: f64,
pub g_dot: f64,
pub psy: f64,
}
pub fn propagate_universal(
position: &Vector3<f64>,
velocity: &Vector3<f64>,
t0: f64,
t1: f64,
solver_type: SolverType,
) -> Result<UniversalPropagResult, OutfitError> {
let gravitational_parameter = GAUSS_GRAV * GAUSS_GRAV;
let initial_radius = position.norm();
if initial_radius < f64::EPSILON {
return Err(OutfitError::DegenerateState(format!(
"initial position vector has zero norm ({initial_radius})"
)));
}
let (radial_velocity_proxy, energy_parameter, eccentricity) =
initial_orbital_state(position, velocity, initial_radius, gravitational_parameter);
let time_of_flight = t1 - t0;
let params = UniversalKeplerParams {
r0: initial_radius,
sig0: radial_velocity_proxy,
mu: gravitational_parameter,
alpha: energy_parameter,
dt: time_of_flight,
e0: eccentricity,
solver_type,
};
let kepler_solution = params.solve()?;
let (s0, s1, s2, _) = kepler_solution.as_raw_stumpff();
let propagated_radius =
initial_radius * s0 + radial_velocity_proxy * s1 + gravitational_parameter * s2;
if propagated_radius < f64::EPSILON {
return Err(OutfitError::DegenerateState(format!(
"propagated radius r1 is zero or negative ({propagated_radius})"
)));
}
let lagrange_f = 1.0 - (gravitational_parameter / initial_radius) * s2;
let lagrange_g = initial_radius * s1 + radial_velocity_proxy * s2;
let lagrange_f_dot = -(gravitational_parameter / (initial_radius * propagated_radius)) * s1;
let lagrange_g_dot = 1.0 - (gravitational_parameter / propagated_radius) * s2;
let propagated_position = lagrange_f * position + lagrange_g * velocity;
let propagated_velocity = lagrange_f_dot * position + lagrange_g_dot * velocity;
Ok(UniversalPropagResult {
r1: propagated_position,
v1: propagated_velocity,
f_lag: lagrange_f,
g_lag: lagrange_g,
f_dot: lagrange_f_dot,
g_dot: lagrange_g_dot,
psy: kepler_solution.universal_anomaly,
})
}
fn initial_orbital_state(
position: &Vector3<f64>,
velocity: &Vector3<f64>,
initial_radius: f64,
gravitational_parameter: f64,
) -> (f64, f64, f64) {
let initial_speed_squared = velocity.norm_squared();
let radial_velocity_proxy = position.dot(velocity) / gravitational_parameter.sqrt();
let energy_parameter = initial_speed_squared - 2.0 * gravitational_parameter / initial_radius;
let angular_momentum_squared = position.cross(velocity).norm_squared();
let eccentricity = (1.0
+ (energy_parameter / gravitational_parameter) * angular_momentum_squared
/ gravitational_parameter)
.sqrt()
.max(0.0);
(radial_velocity_proxy, energy_parameter, eccentricity)
}