fastsim_core/vehicle/
chassis.rs

1pub use super::*;
2
3/// Possible drive wheel configurations for traction limit calculations
4#[derive(
5    Clone, Debug, Serialize, Deserialize, PartialEq, IsVariant, derive_more::From, TryInto,
6)]
7pub enum DriveTypes {
8    /// Rear-wheel drive
9    RWD,
10    /// Front-wheel drive
11    FWD,
12    /// All-wheel drive
13    AWD,
14    /// 4-wheel drive
15    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)]
25/// Struct for simulating vehicle
26pub struct Chassis {
27    /// Aerodynamic drag coefficient
28    pub drag_coef: si::Ratio,
29    /// Projected frontal area for drag calculations
30    pub frontal_area: si::Area,
31    /// Wheel rolling resistance coefficient for the vehicle (i.e. all wheels included)
32    pub wheel_rr_coef: si::Ratio,
33    /// Wheel inertia per wheel
34    pub wheel_inertia: si::MomentOfInertia,
35    /// Number of wheels
36    pub num_wheels: u8,
37    /// Wheel radius
38    #[serde(default)]
39    pub wheel_radius: Option<si::Length>,
40    /// Tire code (optional method of calculating wheel radius)
41    #[serde(default)]
42    pub tire_code: Option<String>,
43    /// Vehicle center of mass height
44    pub cg_height: si::Length,
45    /// Wheel coefficient of friction
46    pub wheel_fric_coef: si::Ratio,
47
48    /// Drive wheel configuration
49    pub drive_type: DriveTypes,
50    /// Fraction of vehicle weight on drive action when stationary
51    pub drive_axle_weight_frac: si::Ratio,
52    /// Wheel base length
53    pub wheel_base: si::Length,
54
55    pub(super) mass: Option<si::Mass>,
56    /// Vehicle mass excluding cargo, passengers, and powertrain components
57    pub(super) glider_mass: Option<si::Mass>,
58    /// Cargo mass including passengers
59    #[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}