fastsim_core/vehicle/
chassis.rs1pub use super::*;
2
3#[derive(
5 Clone, Debug, Serialize, Deserialize, PartialEq, IsVariant, derive_more::From, TryInto,
6)]
7pub enum DriveTypes {
8 RWD,
10 FWD,
12 AWD,
14 FourWD,
16}
17
18impl SerdeAPI for DriveTypes {}
19impl Init for DriveTypes {}
20
21#[serde_api]
22#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
23#[non_exhaustive]
24#[serde(deny_unknown_fields)]
25pub struct Chassis {
27 pub drag_coef: si::Ratio,
29 pub frontal_area: si::Area,
31 pub wheel_rr_coef: si::Ratio,
33 pub wheel_inertia: si::MomentOfInertia,
35 pub num_wheels: u8,
37 #[serde(default)]
39 pub wheel_radius: Option<si::Length>,
40 #[serde(default)]
42 pub tire_code: Option<String>,
43 pub cg_height: si::Length,
45 pub wheel_fric_coef: si::Ratio,
47
48 pub drive_type: DriveTypes,
50 pub drive_axle_weight_frac: si::Ratio,
52 pub wheel_base: si::Length,
54
55 pub(super) mass: Option<si::Mass>,
56 pub(super) glider_mass: Option<si::Mass>,
58 #[serde(default)]
60 pub cargo_mass: Option<si::Mass>,
61}
62
63impl SerdeAPI for Chassis {}
64impl Init for Chassis {}
65
66impl TryFrom<&fastsim_2::vehicle::RustVehicle> for Chassis {
67 type Error = anyhow::Error;
68 fn try_from(f2veh: &fastsim_2::vehicle::RustVehicle) -> anyhow::Result<Self> {
69 let drive_type = if f2veh.veh_cg_m < 0. {
70 chassis::DriveTypes::RWD
71 } else {
72 chassis::DriveTypes::FWD
73 };
74
75 Ok(Self {
76 drag_coef: f2veh.drag_coef * uc::R,
77 frontal_area: f2veh.frontal_area_m2 * uc::M2,
78 cg_height: f2veh.veh_cg_m * uc::M,
79 wheel_fric_coef: f2veh.wheel_coef_of_fric * uc::R,
80 drive_type,
81 drive_axle_weight_frac: f2veh.drive_axle_weight_frac * uc::R,
82 wheel_base: f2veh.wheel_base_m * uc::M,
83 wheel_inertia: f2veh.wheel_inertia_kg_m2 * uc::KGM2,
84 wheel_rr_coef: f2veh.wheel_rr_coef * uc::R,
85 num_wheels: f2veh.num_wheels as u8,
86 wheel_radius: Some(f2veh.wheel_radius_m * uc::M),
87 tire_code: None,
88 mass: None,
89 glider_mass: Some(f2veh.glider_kg * uc::KG),
90 cargo_mass: Some(f2veh.cargo_kg * uc::KG),
91 })
92 }
93}
94
95impl Mass for Chassis {
96 fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
97 let derived_mass = self
98 .derived_mass()
99 .with_context(|| anyhow!(format_dbg!()))?;
100 if let (Some(derived_mass), Some(set_mass)) = (derived_mass, self.mass) {
101 ensure!(
102 utils::almost_eq_uom(&set_mass, &derived_mass, None),
103 format!(
104 "{}",
105 format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
106 )
107 );
108 }
109 Ok(self.mass)
110 }
111
112 fn set_mass(
113 &mut self,
114 new_mass: Option<si::Mass>,
115 _side_effect: MassSideEffect,
116 ) -> anyhow::Result<()> {
117 let derived_mass = self
118 .derived_mass()
119 .with_context(|| anyhow!(format_dbg!()))?;
120 if let (Some(derived_mass), Some(new_mass)) = (derived_mass, new_mass) {
121 if derived_mass != new_mass {
122 self.expunge_mass_fields();
123 }
124 } else if new_mass.is_none() {
125 self.expunge_mass_fields();
126 }
127 self.mass = new_mass;
128 Ok(())
129 }
130
131 fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
132 let mass =
133 if let (Some(glider_mass), Some(cargo_mass)) = (self.glider_mass, self.cargo_mass) {
134 Some(glider_mass + cargo_mass)
135 } else if let (None, None) = (self.glider_mass, self.cargo_mass) {
136 None
137 } else {
138 bail!(
139 "`{}` field masses are not consistently set to `Some` or `None`",
140 stringify!(Chassis)
141 )
142 };
143 Ok(mass)
144 }
145
146 fn expunge_mass_fields(&mut self) {
147 self.mass = None;
148 self.glider_mass = None;
149 self.cargo_mass = None;
150 }
151}