Skip to main content

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    #[serde(alias = "FourWD")]
16    #[serde(rename = "4WD")]
17    FourWD,
18}
19
20impl SerdeAPI for DriveTypes {}
21impl Init for DriveTypes {}
22
23#[serde_api]
24#[non_exhaustive]
25#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
26#[serde(deny_unknown_fields)]
27/// Struct for simulating vehicle
28pub struct Chassis {
29    /// Aerodynamic drag coefficient
30    pub drag_coef: si::Ratio,
31    /// Projected frontal area for drag calculations
32    pub frontal_area: si::Area,
33    /// Wheel rolling resistance coefficient for the vehicle (i.e. all wheels included)
34    pub wheel_rr_coef: si::Ratio,
35    /// Wheel inertia per wheel
36    pub wheel_inertia: si::MomentOfInertia,
37    /// Number of wheels
38    pub num_wheels: u8,
39    /// Wheel radius
40    #[serde(default)]
41    pub wheel_radius: Option<si::Length>,
42    /// Tire code (optional method of calculating wheel radius)
43    #[serde(default)]
44    pub tire_code: Option<String>,
45    /// Vehicle center of mass height
46    pub cg_height: si::Length,
47    /// Wheel coefficient of friction
48    pub wheel_fric_coef: si::Ratio,
49
50    /// Drive wheel configuration
51    pub drive_type: DriveTypes,
52    /// Fraction of vehicle weight on drive action when stationary
53    pub drive_axle_weight_frac: si::Ratio,
54    /// Wheel base length
55    pub wheel_base: si::Length,
56
57    pub(crate) mass: Option<si::Mass>,
58    /// Vehicle mass excluding cargo, passengers, and powertrain components
59    pub(crate) glider_mass: Option<si::Mass>,
60    /// Cargo mass including passengers
61    #[serde(default)]
62    pub cargo_mass: Option<si::Mass>,
63}
64
65impl SerdeAPI for Chassis {}
66impl Init for Chassis {}
67
68impl Mass for Chassis {
69    fn mass(&self) -> anyhow::Result<Option<si::Mass>> {
70        let derived_mass = self
71            .derived_mass()
72            .with_context(|| anyhow!(format_dbg!()))?;
73        if let (Some(derived_mass), Some(set_mass)) = (derived_mass, self.mass) {
74            ensure!(
75                utils::almost_eq_uom(&set_mass, &derived_mass, None),
76                format!(
77                    "{}",
78                    format_dbg!(utils::almost_eq_uom(&set_mass, &derived_mass, None)),
79                )
80            );
81        }
82        Ok(self.mass)
83    }
84
85    fn set_mass(
86        &mut self,
87        new_mass: Option<si::Mass>,
88        _side_effect: MassSideEffect,
89    ) -> anyhow::Result<()> {
90        let derived_mass = self
91            .derived_mass()
92            .with_context(|| anyhow!(format_dbg!()))?;
93        self.mass = match (new_mass, derived_mass) {
94            // Set using provided `new_mass`, setting constituent mass fields to `None` to match if inconsistent
95            (Some(new_mass), Some(dm)) => {
96                if dm != new_mass {
97                    self.expunge_mass_fields();
98                }
99                Some(new_mass)
100            }
101            (Some(new_mass), None) => Some(new_mass),
102            (None, Some(dm)) => Some(dm),
103            (None, None) => bail!(
104                "Not all mass fields in `{}` are set and no mass was provided.",
105                stringify!(Chassis)
106            ),
107        };
108        ensure!(
109            self.mass > Some(0.0 * uc::KG),
110            "{} mass must be positive",
111            stringify!(Chassis)
112        );
113        Ok(())
114    }
115
116    fn derived_mass(&self) -> anyhow::Result<Option<si::Mass>> {
117        let mass =
118            if let (Some(glider_mass), Some(cargo_mass)) = (self.glider_mass, self.cargo_mass) {
119                Some(glider_mass + cargo_mass)
120            } else if let (None, None) = (self.glider_mass, self.cargo_mass) {
121                None
122            } else {
123                bail!(
124                    "`{}` field masses are not consistently set to `Some` or `None`",
125                    stringify!(Chassis)
126                )
127            };
128        Ok(mass)
129    }
130
131    fn expunge_mass_fields(&mut self) {
132        self.mass = None;
133        self.glider_mass = None;
134        self.cargo_mass = None;
135    }
136}