use crate::error::Result;
use crate::types::basic::Double;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename = "Axles")]
pub struct Axles {
#[serde(rename = "FrontAxle", skip_serializing_if = "Option::is_none")]
pub front_axle: Option<Axle>,
#[serde(rename = "RearAxle")]
pub rear_axle: Axle,
#[serde(rename = "AdditionalAxle", default)]
pub additional_axles: Vec<Axle>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename = "Axle")]
pub struct Axle {
#[serde(rename = "@maxSteering")]
pub max_steering: Double,
#[serde(rename = "@wheelDiameter")]
pub wheel_diameter: Double,
#[serde(rename = "@trackWidth")]
pub track_width: Double,
#[serde(rename = "@positionX")]
pub position_x: Double,
#[serde(rename = "@positionZ")]
pub position_z: Double,
}
impl Axles {
pub fn car() -> Self {
Self {
front_axle: Some(Axle::front_car()),
rear_axle: Axle::rear_car(),
additional_axles: vec![],
}
}
pub fn truck() -> Self {
Self {
front_axle: Some(Axle::front_truck()),
rear_axle: Axle::rear_truck(),
additional_axles: vec![Axle::additional_truck()],
}
}
pub fn trailer() -> Self {
Self {
front_axle: None,
rear_axle: Axle::rear_truck(),
additional_axles: vec![],
}
}
pub fn motorcycle() -> Self {
Self {
front_axle: Some(Axle::front_motorcycle()),
rear_axle: Axle::rear_motorcycle(),
additional_axles: vec![],
}
}
pub fn axle_count(&self) -> usize {
let front_count = if self.front_axle.is_some() { 1 } else { 0 };
1 + front_count + self.additional_axles.len() }
pub fn wheelbase(&self, params: &HashMap<String, String>) -> Result<f64> {
if let Some(front_axle) = &self.front_axle {
let front_x = front_axle.position_x.resolve(params)?;
let rear_x = self.rear_axle.position_x.resolve(params)?;
Ok((front_x - rear_x).abs())
} else {
Ok(0.0) }
}
pub fn is_steerable(&self, params: &HashMap<String, String>) -> Result<bool> {
if let Some(front_axle) = &self.front_axle {
let max_steering = front_axle.max_steering.resolve(params)?;
Ok(max_steering > 0.0)
} else {
Ok(false)
}
}
pub fn all_axles(&self) -> Vec<&Axle> {
let mut axles = Vec::new();
if let Some(front) = &self.front_axle {
axles.push(front);
}
axles.push(&self.rear_axle);
axles.extend(&self.additional_axles);
axles
}
}
impl Axle {
pub fn front_car() -> Self {
Self {
max_steering: Double::literal(0.5236), wheel_diameter: Double::literal(0.65),
track_width: Double::literal(1.6),
position_x: Double::literal(1.4),
position_z: Double::literal(0.3),
}
}
pub fn rear_car() -> Self {
Self {
max_steering: Double::literal(0.0), wheel_diameter: Double::literal(0.65),
track_width: Double::literal(1.6),
position_x: Double::literal(-1.4),
position_z: Double::literal(0.3),
}
}
pub fn front_truck() -> Self {
Self {
max_steering: Double::literal(0.4363), wheel_diameter: Double::literal(1.0),
track_width: Double::literal(2.0),
position_x: Double::literal(3.5),
position_z: Double::literal(0.5),
}
}
pub fn rear_truck() -> Self {
Self {
max_steering: Double::literal(0.0),
wheel_diameter: Double::literal(1.0),
track_width: Double::literal(2.0),
position_x: Double::literal(-1.5),
position_z: Double::literal(0.5),
}
}
pub fn additional_truck() -> Self {
Self {
max_steering: Double::literal(0.0),
wheel_diameter: Double::literal(1.0),
track_width: Double::literal(2.0),
position_x: Double::literal(-3.0),
position_z: Double::literal(0.5),
}
}
pub fn front_motorcycle() -> Self {
Self {
max_steering: Double::literal(0.7854), wheel_diameter: Double::literal(0.6),
track_width: Double::literal(0.0), position_x: Double::literal(0.8),
position_z: Double::literal(0.3),
}
}
pub fn rear_motorcycle() -> Self {
Self {
max_steering: Double::literal(0.0),
wheel_diameter: Double::literal(0.65),
track_width: Double::literal(0.0), position_x: Double::literal(-0.8),
position_z: Double::literal(0.3),
}
}
pub fn wheel_circumference(&self, params: &HashMap<String, String>) -> Result<f64> {
let diameter = self.wheel_diameter.resolve(params)?;
Ok(std::f64::consts::PI * diameter)
}
pub fn max_steering_degrees(&self, params: &HashMap<String, String>) -> Result<f64> {
let radians = self.max_steering.resolve(params)?;
Ok(radians.to_degrees())
}
pub fn turning_radius(&self, wheelbase: f64, params: &HashMap<String, String>) -> Result<f64> {
let max_steering = self.max_steering.resolve(params)?;
if max_steering == 0.0 {
Ok(f64::INFINITY) } else {
Ok(wheelbase / max_steering.tan())
}
}
pub fn has_dual_wheels(&self, params: &HashMap<String, String>) -> Result<bool> {
let track_width = self.track_width.resolve(params)?;
Ok(track_width > 0.0)
}
pub fn wheel_positions(&self, params: &HashMap<String, String>) -> Result<Vec<(f64, f64)>> {
let track_width = self.track_width.resolve(params)?;
let position_x = self.position_x.resolve(params)?;
if track_width > 0.0 {
let half_track = track_width / 2.0;
Ok(vec![
(position_x, -half_track), (position_x, half_track), ])
} else {
Ok(vec![(position_x, 0.0)])
}
}
}
impl Default for Axles {
fn default() -> Self {
Self::car()
}
}
impl Default for Axle {
fn default() -> Self {
Self::rear_car()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_axles_car_configuration() {
let axles = Axles::car();
assert!(axles.front_axle.is_some());
assert_eq!(axles.additional_axles.len(), 0);
assert_eq!(axles.axle_count(), 2);
}
#[test]
fn test_axles_truck_configuration() {
let axles = Axles::truck();
assert!(axles.front_axle.is_some());
assert_eq!(axles.additional_axles.len(), 1);
assert_eq!(axles.axle_count(), 3);
}
#[test]
fn test_axles_trailer_configuration() {
let axles = Axles::trailer();
assert!(axles.front_axle.is_none());
assert_eq!(axles.additional_axles.len(), 0);
assert_eq!(axles.axle_count(), 1);
}
#[test]
fn test_axles_wheelbase() {
let axles = Axles::car();
let params = HashMap::new();
let wheelbase = axles.wheelbase(¶ms).unwrap();
assert_eq!(wheelbase, 2.8); }
#[test]
fn test_axles_is_steerable() {
let axles = Axles::car();
let params = HashMap::new();
assert!(axles.is_steerable(¶ms).unwrap());
let trailer = Axles::trailer();
assert!(!trailer.is_steerable(¶ms).unwrap());
}
#[test]
fn test_axle_wheel_circumference() {
let axle = Axle::front_car();
let params = HashMap::new();
let circumference = axle.wheel_circumference(¶ms).unwrap();
let expected = std::f64::consts::PI * 0.65;
assert!((circumference - expected).abs() < 0.001);
}
#[test]
fn test_axle_max_steering_degrees() {
let axle = Axle::front_car();
let params = HashMap::new();
let degrees = axle.max_steering_degrees(¶ms).unwrap();
assert!((degrees - 30.0).abs() < 0.1); }
#[test]
fn test_axle_turning_radius() {
let axle = Axle::front_car();
let params = HashMap::new();
let wheelbase = 2.8;
let radius = axle.turning_radius(wheelbase, ¶ms).unwrap();
assert!(radius > 0.0 && radius < f64::INFINITY);
let rear_axle = Axle::rear_car();
let rear_radius = rear_axle.turning_radius(wheelbase, ¶ms).unwrap();
assert_eq!(rear_radius, f64::INFINITY);
}
#[test]
fn test_axle_has_dual_wheels() {
let car_axle = Axle::front_car();
let motorcycle_axle = Axle::front_motorcycle();
let params = HashMap::new();
assert!(car_axle.has_dual_wheels(¶ms).unwrap());
assert!(!motorcycle_axle.has_dual_wheels(¶ms).unwrap());
}
#[test]
fn test_axle_wheel_positions() {
let car_axle = Axle::front_car();
let motorcycle_axle = Axle::front_motorcycle();
let params = HashMap::new();
let car_positions = car_axle.wheel_positions(¶ms).unwrap();
assert_eq!(car_positions.len(), 2); assert_eq!(car_positions[0], (1.4, -0.8)); assert_eq!(car_positions[1], (1.4, 0.8));
let motorcycle_positions = motorcycle_axle.wheel_positions(¶ms).unwrap();
assert_eq!(motorcycle_positions.len(), 1); assert_eq!(motorcycle_positions[0], (0.8, 0.0));
}
#[test]
fn test_axles_all_axles() {
let truck = Axles::truck();
let all_axles = truck.all_axles();
assert_eq!(all_axles.len(), 3); }
#[test]
fn test_axle_serialization() {
let axle = Axle::front_car();
let xml = quick_xml::se::to_string(&axle).unwrap();
assert!(xml.contains("maxSteering"));
assert!(xml.contains("wheelDiameter"));
assert!(xml.contains("trackWidth"));
assert!(xml.contains("positionX"));
assert!(xml.contains("positionZ"));
}
#[test]
fn test_axles_serialization() {
let axles = Axles::car();
let xml = quick_xml::se::to_string(&axles).unwrap();
assert!(xml.contains("FrontAxle"));
assert!(xml.contains("RearAxle"));
}
}