1#![allow(non_snake_case)]
2
3use std::f32::consts::PI;
4
5use glam::{Mat3, Quat, Vec3};
6
7pub struct Orbit {
8 pub eccentricity: f32,
9 pub semi_major_axis: f32,
10 pub inclination: f32,
11 pub longitude_of_ascending_node: f32,
12 pub argument_of_periapsis: f32,
13}
14
15pub struct StateVectors {
16 pub position: Vec3,
17 pub velocity: Vec3,
18}
19
20impl StateVectors {
21 pub fn new(position: Vec3, velocity: Vec3) -> Self {
22 Self { position, velocity }
23 }
24}
25
26const G: f32 = 6.67430e-11;
28
29impl Orbit {
30 pub fn state_vectors_to_orbit(state_vectors: StateVectors, central_body_mass: f32) -> Self {
31 let r_mag = state_vectors.position.length();
33 let v_mag = state_vectors.velocity.length();
34
35 let h = state_vectors.position.cross(state_vectors.velocity);
37 let h_mag = h.length();
38
39 let e = (state_vectors.velocity.cross(h) / central_body_mass)
41 - (state_vectors.position / r_mag);
42 let e_mag = e.length();
43
44 let a = 1.0 / (2.0 / r_mag - v_mag * v_mag / (central_body_mass * central_body_mass));
46
47 let i = h.z / h_mag;
49
50 let n = Vec3::new(-h.y, h.x, 0.0).normalize();
52 let o = n.cross(Vec3::Z);
53 let sign = o.z.signum();
54 let cos_omega = n.dot(Vec3::X) / o.length();
55 let sin_omega = sign * o.y / o.length();
56 let omega = sin_omega.atan2(cos_omega);
57
58 let cos_w = e.dot(n) / (e_mag * n.length());
60 let sin_w = e.dot(o) / (e_mag * o.length());
61 let w = sin_w.atan2(cos_w);
62
63 Orbit {
65 eccentricity: e_mag,
66 semi_major_axis: a,
67 inclination: i.asin(),
68 longitude_of_ascending_node: omega,
69 argument_of_periapsis: w,
70 }
71 }
72
73 pub fn standard_gravitational_parameter(mass: f32) -> f32 {
75 G * mass
76 }
77
78 pub fn period(&self, mass: f32) -> f32 {
80 let sm_cubed = self.semi_major_axis * self.semi_major_axis * self.semi_major_axis;
81
82 2.0 * PI * (sm_cubed / Self::standard_gravitational_parameter(mass)).sqrt()
83 }
84
85 pub fn mean_motion(&self, mass: f32) -> f32 {
87 2.0 * PI / self.period(mass)
88 }
89
90 pub fn mean_anomaly(&self, mass: f32, time: f32) -> f32 {
92 self.mean_motion(mass) * time
93 }
94
95 pub fn estimate_eccentric_anomaly(&self, mass: f32, time: f32, tolerance: f32) -> f32 {
106 let mean_anomaly = self.mean_anomaly(mass, time);
107 let mut eccentric_anomaly = mean_anomaly;
108
109 let mut error = 1.0;
110
111 while error > tolerance {
112 let fe =
114 eccentric_anomaly - (self.eccentricity * eccentric_anomaly.sin()) - mean_anomaly;
115
116 let fe_prime = 1.0 - (self.eccentricity * eccentric_anomaly.cos());
118
119 let next_eccentric_anomaly = eccentric_anomaly - (fe / fe_prime);
120
121 error = (next_eccentric_anomaly - eccentric_anomaly).abs();
122 eccentric_anomaly = next_eccentric_anomaly;
123 }
124
125 eccentric_anomaly
126 }
127
128 pub fn state_vectors_at_epoch(&self, mass: f32, time: f32, tolerance: f32) -> StateVectors {
129 let Ω = self.longitude_of_ascending_node;
130 let ω = self.argument_of_periapsis;
131 let i = self.inclination;
132 let a = self.semi_major_axis;
133 let e = self.eccentricity;
134 let μ = Self::standard_gravitational_parameter(mass);
135
136 let E = self.estimate_eccentric_anomaly(mass, time, tolerance);
137
138 let h = f32::sqrt(μ * a * (1.0 - e.powi(2)));
140
141 let x = a * (f32::cos(E) - e);
143 let y = a * f32::sqrt(1.0 - e.powi(2)) * f32::sin(E);
144
145 let vx = -f32::sin(E) * h / (a * (1.0 - e * f32::cos(E)));
147 let vy = f32::sqrt(1.0 - e.powi(2)) * f32::cos(E) * h / (a * (1.0 - e * f32::cos(E)));
148
149 let position_perifocal = Vec3::new(x, 0.0, y);
151 let velocity_perifocal = Vec3::new(vx, 0.0, vy);
152
153 let i = Mat3::from_axis_angle(Vec3::X, i);
155 let o = Mat3::from_axis_angle(Vec3::Z, Ω);
156 let w = Mat3::from_axis_angle(Vec3::Z, ω);
157
158 let q = Quat::from_mat3(&(o * i * w));
160
161 let position = q.mul_vec3(position_perifocal);
162 let velocity = q.mul_vec3(velocity_perifocal);
163
164 StateVectors { position, velocity }
165 }
166}