pub use super::*;
#[derive(
Clone, Debug, Serialize, Deserialize, PartialEq, IsVariant, derive_more::From, TryInto,
)]
pub enum DriveTypes {
RWD,
FWD,
AWD,
FourWD,
}
impl SerdeAPI for DriveTypes {}
impl Init for DriveTypes {}
#[serde_api]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(deny_unknown_fields)]
pub struct Chassis {
pub drag_coef: si::Ratio,
pub frontal_area: si::Area,
pub wheel_rr_coef: si::Ratio,
pub wheel_inertia: si::MomentOfInertia,
pub num_wheels: u8,
#[serde(default)]
pub wheel_radius: Option<si::Length>,
#[serde(default)]
pub tire_code: Option<String>,
pub cg_height: si::Length,
pub wheel_fric_coef: si::Ratio,
pub drive_type: DriveTypes,
pub drive_axle_weight_frac: si::Ratio,
pub wheel_base: si::Length,
pub(super) mass: Option<si::Mass>,
pub(super) glider_mass: Option<si::Mass>,
#[serde(default)]
pub cargo_mass: Option<si::Mass>,
}
impl SerdeAPI for Chassis {}
impl Init for Chassis {}
impl TryFrom<&fastsim_2::vehicle::RustVehicle> for Chassis {
type Error = anyhow::Error;
fn try_from(f2veh: &fastsim_2::vehicle::RustVehicle) -> anyhow::Result<Self> {
let drive_type = if f2veh.veh_cg_m < 0. {
chassis::DriveTypes::RWD
} else {
chassis::DriveTypes::FWD
};
Ok(Self {
drag_coef: f2veh.drag_coef * uc::R,
frontal_area: f2veh.frontal_area_m2 * uc::M2,
cg_height: f2veh.veh_cg_m * uc::M,
wheel_fric_coef: f2veh.wheel_coef_of_fric * uc::R,
drive_type,
drive_axle_weight_frac: f2veh.drive_axle_weight_frac * uc::R,
wheel_base: f2veh.wheel_base_m * uc::M,
wheel_inertia: f2veh.wheel_inertia_kg_m2 * uc::KGM2,
wheel_rr_coef: f2veh.wheel_rr_coef * uc::R,
num_wheels: f2veh.num_wheels as u8,
wheel_radius: Some(f2veh.wheel_radius_m * uc::M),
tire_code: None,
mass: None,
glider_mass: Some(f2veh.glider_kg * uc::KG),
cargo_mass: Some(f2veh.cargo_kg * uc::KG),
})
}
}
impl Mass for Chassis {
fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
let derived_mass = self
.derived_mass()
.with_context(|| anyhow!(format_dbg!()))?;
if let (Some(derived_mass), Some(set_mass)) = (derived_mass, self.mass) {
ensure!(
utils::almost_eq_uom(&set_mass, &derived_mass, None),
format!(
"{}",
format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
)
);
}
Ok(self.mass)
}
fn set_mass(
&mut self,
new_mass: Option<si::Mass>,
_side_effect: MassSideEffect,
) -> anyhow::Result<()> {
let derived_mass = self
.derived_mass()
.with_context(|| anyhow!(format_dbg!()))?;
if let (Some(derived_mass), Some(new_mass)) = (derived_mass, new_mass) {
if derived_mass != new_mass {
self.expunge_mass_fields();
}
} else if new_mass.is_none() {
self.expunge_mass_fields();
}
self.mass = new_mass;
Ok(())
}
fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
let mass =
if let (Some(glider_mass), Some(cargo_mass)) = (self.glider_mass, self.cargo_mass) {
Some(glider_mass + cargo_mass)
} else if let (None, None) = (self.glider_mass, self.cargo_mass) {
None
} else {
bail!(
"`{}` field masses are not consistently set to `Some` or `None`",
stringify!(Chassis)
)
};
Ok(mass)
}
fn expunge_mass_fields(&mut self) {
self.mass = None;
self.glider_mass = None;
self.cargo_mass = None;
}
}