lotus_shared/
vehicle.rs

1use serde_repr::{Deserialize_repr, Serialize_repr};
2
3#[derive(Debug, thiserror::Error)]
4pub enum VehicleError {
5    #[error("vehicle not found")]
6    VehicleNotFound = 256,
7    #[error("bogie not found")]
8    BogieNotFound = 512,
9    #[error("axle not found")]
10    AxleNotFound = 1024,
11    #[error("coupling not found")]
12    CouplingNotFound = 2048,
13    #[error("pantograph not found")]
14    PantographNotFound = 4096,
15    #[error("unknown error")]
16    Unknown = 0,
17}
18
19impl From<u32> for VehicleError {
20    fn from(value: u32) -> Self {
21        match value {
22            256 => VehicleError::VehicleNotFound,
23            512 => VehicleError::BogieNotFound,
24            1024 => VehicleError::AxleNotFound,
25            2048 => VehicleError::PantographNotFound,
26            _ => VehicleError::Unknown,
27        }
28    }
29}
30
31#[cfg(feature = "ffi")]
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub struct Bogie {
34    index: usize,
35}
36
37#[cfg(feature = "ffi")]
38impl Bogie {
39    pub fn get(index: usize) -> Result<Self, VehicleError> {
40        match unsafe { lotus_script_sys::vehicle::bogie_is_valid(index as u32) } {
41            0 => Ok(Self { index }),
42            e => Err(e.into()),
43        }
44    }
45
46    /// Sets the rail brake force at the given bogie.
47    /// The rail brake consists of electromagnets that are set against the rail.
48    /// They then slide over the rail with high friction, which allows the vehicle to be braked much more strongly:
49    /// While the normal wheel brake only has the axle load available to build up a frictional grip with the rail,
50    /// the rail brake can exert much higher frictional forces and thus braking forces,
51    /// even relatively independent of the rail condition (moisture and dirt are effectively "ground off").
52    pub fn set_rail_brake_force_newton(self, value: f32) {
53        unsafe { lotus_script_sys::vehicle::set_rail_brake_force_newton(self.index as u32, value) };
54    }
55}
56
57#[cfg(feature = "ffi")]
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub struct Axle {
60    bogie_index: usize,
61    axle_index: usize,
62}
63
64#[cfg(feature = "ffi")]
65impl Axle {
66    pub fn get(bogie_index: usize, axle_index: usize) -> Result<Self, VehicleError> {
67        match unsafe {
68            lotus_script_sys::vehicle::axle_is_valid(bogie_index as u32, axle_index as u32)
69        } {
70            0 => Ok(Self {
71                bogie_index,
72                axle_index,
73            }),
74            e => Err(e.into()),
75        }
76    }
77
78    pub fn velocity_var_name(self) -> String {
79        format!("v_Axle_mps_{}_{}", self.bogie_index, self.axle_index)
80    }
81
82    pub fn bogie(self) -> Bogie {
83        Bogie {
84            index: self.bogie_index,
85        }
86    }
87
88    pub fn axle_index(self) -> usize {
89        self.axle_index
90    }
91
92    pub fn bogie_index(self) -> usize {
93        self.bogie_index
94    }
95
96    /// Gets the curvature of the track under the given axis.
97    /// The curvature is the reciprocal of the radius (1/R), which has the advantage that the value does not tend to infinity in a straight line, but tends to 0.
98    /// The values are very small due to this calculation: Even a radius of only 100m leads to a value of 0.01, larger radii lead to even smaller values.
99    /// Positive = right, negative = left.
100    pub fn inverse_radius(self) -> f32 {
101        let inverse_radius = unsafe {
102            lotus_script_sys::vehicle::inverse_radius(
103                self.bogie_index as u32,
104                self.axle_index as u32,
105            )
106        };
107        assert!(!inverse_radius.is_nan());
108        assert_ne!(inverse_radius, f32::NEG_INFINITY);
109        assert_ne!(inverse_radius, f32::INFINITY);
110        inverse_radius
111    }
112
113    /// Provides the type of the surface under the given axis.
114    pub fn surface_type(self) -> SurfaceType {
115        let surface_type = unsafe {
116            lotus_script_sys::vehicle::surface_type(self.bogie_index as u32, self.axle_index as u32)
117        };
118        SurfaceType::try_from(surface_type).unwrap()
119    }
120
121    /// Provides the quality of the rails under the given axis.
122    pub fn rail_quality(self) -> RailQuality {
123        let quality = unsafe {
124            lotus_script_sys::vehicle::rail_quality(self.bogie_index as u32, self.axle_index as u32)
125        };
126        RailQuality::try_from(quality).unwrap()
127    }
128
129    /// Sets the traction force in newton.
130    /// This is the torque applied to the axle, already converted to the force acting on the running surface. This means that as long as the wheel does not slip or spin, this value is equal to the force exerted by the wheel on the rail.
131    /// This force acts independently of the direction of travel. If it acts in the opposite direction to the travel, the vehicle will be braked, but it cannot hold the vehicle stationary.
132    pub fn set_traction_force_newton(self, value: f32) {
133        unsafe {
134            lotus_script_sys::vehicle::set_traction_force_newton(
135                self.bogie_index as u32,
136                self.axle_index as u32,
137                value,
138            )
139        };
140    }
141
142    /// Sets the brake force in newton.
143    /// This is the torque applied to the axle, already converted to the force acting on the running surface.
144    /// This means that as long as the wheel does not slip or spin, this value is equal to the force exerted by the wheel on the rail.
145    /// The difference to "traction_force_newton" is that brake_force_newton is always positive and always acts in the opposite direction to the travel.
146    /// This means that brake_force_newton can also hold the vehicle stationary like a disc brake.
147    pub fn set_brake_force_newton(self, value: f32) {
148        unsafe {
149            lotus_script_sys::vehicle::set_brake_force_newton(
150                self.bogie_index as u32,
151                self.axle_index as u32,
152                value,
153            )
154        };
155    }
156}
157
158#[cfg(feature = "ffi")]
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
160pub struct Pantograph {
161    index: usize,
162}
163
164#[cfg(feature = "ffi")]
165impl Pantograph {
166    pub fn get(index: usize) -> Result<Self, VehicleError> {
167        match unsafe { lotus_script_sys::vehicle::pantograph_is_valid(index as u32) } {
168            0 => Ok(Self { index }),
169            e => Err(e.into()),
170        }
171    }
172
173    /// Returns the height of the lowest contact wire above the pantograph position.
174    pub fn height(self) -> f32 {
175        let height = unsafe { lotus_script_sys::vehicle::pantograph_height(self.index as u32) };
176        assert!(!height.is_nan());
177        assert_ne!(height, f32::INFINITY);
178        height
179    }
180
181    /// The voltage of the contact wire above the pantograph. The value is normalized, i.e. 1.0 means that the target voltage is present.
182    /// However, the script itself must check whether the pantograph is touching the contact wire.
183    pub fn voltage(self) -> f32 {
184        let voltage = unsafe { lotus_script_sys::vehicle::pantograph_voltage(self.index as u32) };
185        assert!(!voltage.is_nan());
186        assert_ne!(voltage, f32::INFINITY);
187        voltage
188    }
189}
190
191/// Provides the quality of the rails under the given axis.
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
193#[repr(u8)]
194pub enum RailQuality {
195    Smooth = 0,
196    Rough = 1,
197    FroggySmooth = 2,
198    FroggyRough = 3,
199    FlatGroove = 4,
200    HighSpeedSmooth = 5,
201    SmoothDirt = 6,
202    RoughDirt = 7,
203    Deraileur = 8,
204}
205
206impl TryFrom<u32> for RailQuality {
207    type Error = VehicleError;
208
209    fn try_from(value: u32) -> Result<Self, Self::Error> {
210        match value {
211            0 => Ok(RailQuality::Smooth),
212            1 => Ok(RailQuality::Rough),
213            2 => Ok(RailQuality::FroggySmooth),
214            3 => Ok(RailQuality::FroggyRough),
215            4 => Ok(RailQuality::FlatGroove),
216            5 => Ok(RailQuality::HighSpeedSmooth),
217            6 => Ok(RailQuality::SmoothDirt),
218            7 => Ok(RailQuality::RoughDirt),
219            8 => Ok(RailQuality::Deraileur),
220            value => Err(value.into()),
221        }
222    }
223}
224
225/// Type of the surface under the given axis.
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
227#[repr(u8)]
228pub enum SurfaceType {
229    Gravel = 0,
230    Street = 1,
231    Grass = 2,
232}
233
234impl TryFrom<u32> for SurfaceType {
235    type Error = VehicleError;
236
237    fn try_from(value: u32) -> Result<Self, Self::Error> {
238        match value {
239            0 => Ok(SurfaceType::Gravel),
240            1 => Ok(SurfaceType::Street),
241            2 => Ok(SurfaceType::Grass),
242            value => Err(value.into()),
243        }
244    }
245}