use crate::{FlightState, Result, RocketSample, Vec3};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ControlInput {
pub thrust_direction: Vec3,
pub steering_acceleration_mps2: Vec3,
}
impl ControlInput {
pub fn along(thrust_direction: Vec3) -> Self {
Self {
thrust_direction,
steering_acceleration_mps2: Vec3::zeros(),
}
}
}
impl Default for ControlInput {
fn default() -> Self {
Self::along(Vec3::x())
}
}
#[derive(Debug, Clone, Copy)]
pub struct GuidanceContext<'a> {
pub state: &'a FlightState,
pub rocket: RocketSample,
pub step_duration_s: f64,
pub max_steering_accel_mps2: f64,
}
pub trait Guidance {
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput>;
}
impl<F> Guidance for F
where
F: FnMut(GuidanceContext<'_>) -> Result<ControlInput>,
{
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
self(context)
}
}
pub struct BoxedGuidance(pub Box<dyn Guidance>);
impl std::fmt::Debug for BoxedGuidance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BoxedGuidance").finish_non_exhaustive()
}
}
impl Guidance for BoxedGuidance {
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
self.0.command(context)
}
}
#[derive(Debug, Clone, Copy)]
pub struct FixedDirection(pub Vec3);
impl Guidance for FixedDirection {
fn command(&mut self, _context: GuidanceContext<'_>) -> Result<ControlInput> {
Ok(ControlInput::along(self.0))
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProgradeDirection {
pub fallback_axis: Vec3,
}
impl ProgradeDirection {
pub fn new(fallback_axis: Vec3) -> Self {
Self { fallback_axis }
}
}
impl Default for ProgradeDirection {
fn default() -> Self {
Self::new(Vec3::y())
}
}
impl Guidance for ProgradeDirection {
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
let v = context.state.velocity_mps;
let axis = if v.norm() > 1e-9 {
v / v.norm()
} else {
self.fallback_axis
};
Ok(ControlInput::along(axis))
}
}
#[derive(Debug, Clone, Copy)]
pub struct ThrustMisalignment<G> {
inner: G,
tilt_rad: f64,
clock_rad: f64,
basis_u: Option<Vec3>,
}
impl<G> ThrustMisalignment<G> {
pub fn new(inner: G, tilt_rad: f64, clock_rad: f64) -> Result<Self> {
if !tilt_rad.is_finite() || tilt_rad < 0.0 || !clock_rad.is_finite() {
return Err(crate::Error::invalid(
"thrust misalignment",
"tilt must be finite and nonnegative, clock finite",
));
}
Ok(Self {
inner,
tilt_rad,
clock_rad,
basis_u: None,
})
}
pub fn basis_u(&self) -> Option<Vec3> {
self.basis_u
}
pub fn set_basis_u(&mut self, basis_u: Option<Vec3>) {
self.basis_u = basis_u;
}
}
impl<G: Guidance> Guidance for ThrustMisalignment<G> {
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
let command = self.inner.command(context)?;
if self.tilt_rad == 0.0 {
return Ok(command);
}
let axis = command.thrust_direction.normalize();
let u = match self.basis_u {
Some(previous) => {
let transported = previous - axis * axis.dot(&previous);
if transported.norm() > 1e-9 {
transported.normalize()
} else {
let seed = if axis.z.abs() < 0.9 {
Vec3::z()
} else {
Vec3::y()
};
axis.cross(&seed).normalize()
}
}
None => {
let seed = if axis.z.abs() < 0.9 {
Vec3::z()
} else {
Vec3::y()
};
axis.cross(&seed).normalize()
}
};
self.basis_u = Some(u);
let v = axis.cross(&u).normalize();
let tilt_axis = u * self.clock_rad.cos() + v * self.clock_rad.sin();
let misaligned = axis * self.tilt_rad.cos() + tilt_axis * self.tilt_rad.sin();
Ok(ControlInput {
thrust_direction: misaligned,
steering_acceleration_mps2: command.steering_acceleration_mps2,
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct SteeringLag<G> {
inner: G,
tau_s: f64,
current: Vec3,
}
impl<G> SteeringLag<G> {
pub fn new(inner: G, tau_s: f64) -> Result<Self> {
if !tau_s.is_finite() || tau_s <= 0.0 {
return Err(crate::Error::invalid(
"steering lag tau_s",
"must be finite and positive",
));
}
Ok(Self {
inner,
tau_s,
current: Vec3::zeros(),
})
}
pub fn current(&self) -> Vec3 {
self.current
}
pub fn set_current(&mut self, current: Vec3) {
self.current = current;
}
}
impl<G: Guidance> Guidance for SteeringLag<G> {
fn command(&mut self, context: GuidanceContext<'_>) -> Result<ControlInput> {
let mut command = self.inner.command(context)?;
let weight = 1.0 - (-context.step_duration_s / self.tau_s).exp();
self.current += (command.steering_acceleration_mps2 - self.current) * weight;
command.steering_acceleration_mps2 = self.current;
Ok(command)
}
}