rockets_engine/
guidance.rs1use crate::{FlightState, Result, RocketSample, Vec3};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct ControlInput {
6 pub thrust_direction: Vec3,
9 pub steering_acceleration_mps2: Vec3,
13}
14
15impl ControlInput {
16 pub fn along(thrust_direction: Vec3) -> Self {
19 Self {
20 thrust_direction,
21 steering_acceleration_mps2: Vec3::zeros(),
22 }
23 }
24}
25
26impl Default for ControlInput {
27 fn default() -> Self {
28 Self::along(Vec3::x())
29 }
30}
31
32#[derive(Debug, Clone, Copy)]
34pub struct GuidanceContext<'a> {
35 pub state: &'a FlightState,
37 pub rocket: RocketSample,
39 pub step_duration_s: f64,
41 pub max_steering_accel_mps2: f64,
43}
44
45pub trait Guidance {
52 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput>;
54}
55
56impl<F> Guidance for F
57where
58 F: FnMut(GuidanceContext<'_>) -> Result<ControlInput>,
59{
60 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
61 self(context)
62 }
63}
64
65pub struct BoxedGuidance(pub Box<dyn Guidance>);
69
70impl std::fmt::Debug for BoxedGuidance {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("BoxedGuidance").finish_non_exhaustive()
73 }
74}
75
76impl Guidance for BoxedGuidance {
77 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
78 self.0.command(context)
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
85pub struct FixedDirection(pub Vec3);
86
87impl Guidance for FixedDirection {
88 fn command(&mut self, _context: GuidanceContext<'_>) -> Result<ControlInput> {
89 Ok(ControlInput::along(self.0))
90 }
91}
92
93#[derive(Debug, Clone, Copy)]
97pub struct ProgradeDirection {
98 pub fallback_axis: Vec3,
100}
101
102impl ProgradeDirection {
103 pub fn new(fallback_axis: Vec3) -> Self {
105 Self { fallback_axis }
106 }
107}
108
109impl Default for ProgradeDirection {
110 fn default() -> Self {
111 Self::new(Vec3::y())
112 }
113}
114
115impl Guidance for ProgradeDirection {
116 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
117 let v = context.state.velocity_mps;
118 let axis = if v.norm() > 1e-9 {
119 v / v.norm()
120 } else {
121 self.fallback_axis
122 };
123 Ok(ControlInput::along(axis))
124 }
125}
126
127#[derive(Debug, Clone, Copy)]
133pub struct ThrustMisalignment<G> {
134 inner: G,
135 tilt_rad: f64,
136 clock_rad: f64,
137}
138
139impl<G> ThrustMisalignment<G> {
140 pub fn new(inner: G, tilt_rad: f64, clock_rad: f64) -> Result<Self> {
143 if !tilt_rad.is_finite() || tilt_rad < 0.0 || !clock_rad.is_finite() {
144 return Err(crate::Error::invalid(
145 "thrust misalignment",
146 "tilt must be finite and nonnegative, clock finite",
147 ));
148 }
149 Ok(Self {
150 inner,
151 tilt_rad,
152 clock_rad,
153 })
154 }
155}
156
157impl<G: Guidance> Guidance for ThrustMisalignment<G> {
158 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
159 let command = self.inner.command(context)?;
160 if self.tilt_rad == 0.0 {
161 return Ok(command);
162 }
163 let axis = command.thrust_direction.normalize();
164 let seed = if axis.z.abs() < 0.9 {
166 Vec3::z()
167 } else {
168 Vec3::y()
169 };
170 let u = axis.cross(&seed).normalize();
171 let v = axis.cross(&u).normalize();
172 let tilt_axis = u * self.clock_rad.cos() + v * self.clock_rad.sin();
173 let misaligned = axis * self.tilt_rad.cos() + tilt_axis * self.tilt_rad.sin();
174 Ok(ControlInput {
175 thrust_direction: misaligned,
176 steering_acceleration_mps2: command.steering_acceleration_mps2,
177 })
178 }
179}
180
181#[derive(Debug, Clone, Copy)]
187pub struct SteeringLag<G> {
188 inner: G,
189 tau_s: f64,
190 current: Vec3,
191}
192
193impl<G> SteeringLag<G> {
194 pub fn new(inner: G, tau_s: f64) -> Result<Self> {
196 if !tau_s.is_finite() || tau_s <= 0.0 {
197 return Err(crate::Error::invalid(
198 "steering lag tau_s",
199 "must be finite and positive",
200 ));
201 }
202 Ok(Self {
203 inner,
204 tau_s,
205 current: Vec3::zeros(),
206 })
207 }
208 pub fn current(&self) -> Vec3 {
210 self.current
211 }
212 pub fn set_current(&mut self, current: Vec3) {
214 self.current = current;
215 }
216}
217
218impl<G: Guidance> Guidance for SteeringLag<G> {
219 fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
220 let mut command = self.inner.command(context)?;
221 let weight = 1.0 - (-context.step_duration_s / self.tau_s).exp();
222 self.current += (command.steering_acceleration_mps2 - self.current) * weight;
223 command.steering_acceleration_mps2 = self.current;
224 Ok(command)
225 }
226}