Skip to main content

lotus_shared/
vehicle.rs

1use serde::{Deserialize, Serialize};
2use serde_repr::{Deserialize_repr, Serialize_repr};
3
4use crate::message::{Coupling, MessageMeta, MessageType};
5
6#[derive(Debug, thiserror::Error)]
7pub enum VehicleError {
8    #[error("vehicle not found")]
9    VehicleNotFound = 256,
10    #[error("bogie not found")]
11    BogieNotFound = 512,
12    #[error("axle not found")]
13    AxleNotFound = 1024,
14    #[error("coupling not found")]
15    CouplingNotFound = 2048,
16    #[error("pantograph not found")]
17    PantographNotFound = 4096,
18    #[error("road axle not found")]
19    RoadAxleNotFound = 8192,
20    #[error("road wheel not found")]
21    RoadWheelNotFound = 16384,
22    #[error("unknown error")]
23    Unknown = 0,
24}
25
26impl From<u32> for VehicleError {
27    fn from(value: u32) -> Self {
28        match value {
29            256 => VehicleError::VehicleNotFound,
30            512 => VehicleError::BogieNotFound,
31            1024 => VehicleError::AxleNotFound,
32            2048 => VehicleError::CouplingNotFound,
33            4096 => VehicleError::PantographNotFound,
34            8192 => VehicleError::RoadAxleNotFound,
35            16384 => VehicleError::RoadWheelNotFound,
36            _ => VehicleError::Unknown,
37        }
38    }
39}
40
41#[cfg(feature = "ffi")]
42/// Returns `true` if the vehicle was spawned inverted to the train.
43pub fn spawned_inverted_to_train() -> bool {
44    unsafe { lotus_script_sys::vehicle::spawned_inverted_to_train() == 1 }
45}
46
47/// Describes an event that is sent when the train configuration is changed.
48/// Please note: When two trains with different directions are coupled,
49/// the new direction cannot be predicted!
50/// In the vehicles of the train whose direction is inverted when coupling,
51/// "reversed_to_train" is inverted and the index order reverses.
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct TrainConfigurationChanged {
54    pub entity_id: u64,
55    pub reversed_to_train: bool,
56    pub index_in_train: usize,
57    pub train_vehicle_count: usize,
58}
59
60impl MessageType for TrainConfigurationChanged {
61    const MESSAGE_META: MessageMeta = MessageMeta::new("builtin", "vehicle_in_train_changed", None);
62}
63
64/// Calculation of the vehicle count in front or behind the vehicle
65/// relative to the vehicle.
66impl TrainConfigurationChanged {
67    pub fn neighbour_vehicle_count(&self, coupling: Coupling) -> usize {
68        if (coupling == Coupling::Rear) ^ self.reversed_to_train {
69            self.train_vehicle_count - self.index_in_train - 1
70        } else {
71            self.index_in_train
72        }
73    }
74}
75
76#[cfg(feature = "ffi")]
77#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
78pub struct Bogie {
79    index: usize,
80}
81
82#[cfg(feature = "ffi")]
83impl Bogie {
84    pub fn get(index: usize) -> Result<Self, VehicleError> {
85        match unsafe { lotus_script_sys::vehicle::bogie_is_valid(index as u32) } {
86            0 => Ok(Self { index }),
87            e => Err(e.into()),
88        }
89    }
90
91    /// Sets the rail brake force at the given bogie.
92    /// The rail brake consists of electromagnets that are set against the rail.
93    /// They then slide over the rail with high friction, which allows the vehicle to be braked much more strongly:
94    /// While the normal wheel brake only has the axle load available to build up a frictional grip with the rail,
95    /// the rail brake can exert much higher frictional forces and thus braking forces,
96    /// even relatively independent of the rail condition (moisture and dirt are effectively "ground off").
97    pub fn set_rail_brake_force_newton(self, value: f32) {
98        unsafe { lotus_script_sys::vehicle::set_rail_brake_force_newton(self.index as u32, value) };
99    }
100}
101
102#[cfg(feature = "ffi")]
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
104pub struct Axle {
105    bogie_index: usize,
106    axle_index: usize,
107}
108
109#[cfg(feature = "ffi")]
110impl Axle {
111    pub fn get(bogie_index: usize, axle_index: usize) -> Result<Self, VehicleError> {
112        match unsafe {
113            lotus_script_sys::vehicle::axle_is_valid(bogie_index as u32, axle_index as u32)
114        } {
115            0 => Ok(Self {
116                bogie_index,
117                axle_index,
118            }),
119            e => Err(e.into()),
120        }
121    }
122
123    pub fn velocity_var_name(self) -> String {
124        format!("v_Axle_mps_{}_{}", self.bogie_index, self.axle_index)
125    }
126
127    pub fn bogie(self) -> Bogie {
128        Bogie {
129            index: self.bogie_index,
130        }
131    }
132
133    pub fn axle_index(self) -> usize {
134        self.axle_index
135    }
136
137    pub fn bogie_index(self) -> usize {
138        self.bogie_index
139    }
140
141    /// Gets the curvature of the track under the given axis.
142    /// 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.
143    /// 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.
144    /// Positive = right, negative = left.
145    pub fn inverse_radius(self) -> f32 {
146        let inverse_radius = unsafe {
147            lotus_script_sys::vehicle::inverse_radius(
148                self.bogie_index as u32,
149                self.axle_index as u32,
150            )
151        };
152        assert!(!inverse_radius.is_nan());
153        assert_ne!(inverse_radius, f32::NEG_INFINITY);
154        assert_ne!(inverse_radius, f32::INFINITY);
155        inverse_radius
156    }
157
158    /// Provides the type of the surface under the given axis.
159    pub fn surface_type(self) -> SurfaceType {
160        let surface_type = unsafe {
161            lotus_script_sys::vehicle::surface_type(self.bogie_index as u32, self.axle_index as u32)
162        };
163        SurfaceType::try_from(surface_type).unwrap()
164    }
165
166    /// Provides the quality of the rails under the given axis.
167    pub fn rail_quality(self) -> RailQuality {
168        let quality = unsafe {
169            lotus_script_sys::vehicle::rail_quality(self.bogie_index as u32, self.axle_index as u32)
170        };
171        RailQuality::try_from(quality).unwrap()
172    }
173
174    /// Sets the traction force in newton.
175    /// 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.
176    /// 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.
177    pub fn set_traction_force_newton(self, value: f32) {
178        unsafe {
179            lotus_script_sys::vehicle::set_traction_force_newton(
180                self.bogie_index as u32,
181                self.axle_index as u32,
182                value,
183            )
184        };
185    }
186
187    /// Sets the brake force in newton.
188    /// This is the torque applied to the axle, already converted to the force acting on the running surface.
189    /// 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.
190    /// The difference to "traction_force_newton" is that brake_force_newton is always positive and always acts in the opposite direction to the travel.
191    /// This means that brake_force_newton can also hold the vehicle stationary like a disc brake.
192    pub fn set_brake_force_newton(self, value: f32) {
193        unsafe {
194            lotus_script_sys::vehicle::set_brake_force_newton(
195                self.bogie_index as u32,
196                self.axle_index as u32,
197                value,
198            )
199        };
200    }
201}
202
203#[cfg(feature = "ffi")]
204#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
205pub struct RoadAxle {
206    index: usize,
207}
208
209#[cfg(feature = "ffi")]
210impl RoadAxle {
211    pub fn get(index: usize) -> Result<Self, VehicleError> {
212        match unsafe { lotus_script_sys::vehicle::road_axle_is_valid(index as u32) } {
213            0 => Ok(Self { index }),
214            e => Err(e.into()),
215        }
216    }
217}
218
219#[cfg(feature = "ffi")]
220#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
221pub struct RoadWheel {
222    axle_index: usize,
223    wheel_index: usize,
224}
225
226#[cfg(feature = "ffi")]
227impl RoadWheel {
228    pub fn get(axle_index: usize, wheel_index: usize) -> Result<Self, VehicleError> {
229        match unsafe {
230            lotus_script_sys::vehicle::road_wheel_is_valid(axle_index as u32, wheel_index as u32)
231        } {
232            0 => Ok(Self {
233                axle_index,
234                wheel_index,
235            }),
236            e => Err(e.into()),
237        }
238    }
239
240    pub fn velocity_var_name(self) -> String {
241        format!("v_wheel_mps_{}_{}", self.axle_index, self.wheel_index)
242    }
243
244    pub fn wheel_index(self) -> usize {
245        self.wheel_index
246    }
247
248    pub fn axle_index(self) -> usize {
249        self.axle_index
250    }
251
252    /// Sets the traction force at the running surface in newton.
253    pub fn set_traction_force_newton(self, value: f32) {
254        unsafe {
255            lotus_script_sys::vehicle::set_wheel_traction_force_newton(
256                self.axle_index as u32,
257                self.wheel_index as u32,
258                value,
259            )
260        };
261    }
262
263    /// Sets the brake force at the running surface in newton.
264    pub fn set_brake_force_newton(self, value: f32) {
265        unsafe {
266            lotus_script_sys::vehicle::set_wheel_brake_force_newton(
267                self.axle_index as u32,
268                self.wheel_index as u32,
269                value,
270            )
271        };
272    }
273
274    /// Sets the factor, which manipulates the spring stiffness.
275    pub fn set_spring_factor(self, value: f32) {
276        unsafe {
277            lotus_script_sys::vehicle::set_wheel_spring_factor(
278                self.axle_index as u32,
279                self.wheel_index as u32,
280                value,
281            )
282        };
283    }
284}
285
286#[cfg(feature = "ffi")]
287#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
288pub struct Pantograph {
289    index: usize,
290}
291
292#[cfg(feature = "ffi")]
293impl Pantograph {
294    pub fn get(index: usize) -> Result<Self, VehicleError> {
295        match unsafe { lotus_script_sys::vehicle::pantograph_is_valid(index as u32) } {
296            0 => Ok(Self { index }),
297            e => Err(e.into()),
298        }
299    }
300
301    /// Returns the height of the lowest contact wire above the pantograph position.
302    pub fn height(self) -> f32 {
303        let height = unsafe { lotus_script_sys::vehicle::pantograph_height(self.index as u32) };
304        assert!(!height.is_nan());
305        assert_ne!(height, f32::INFINITY);
306        height
307    }
308
309    /// The voltage of the contact wire above the pantograph. The value is normalized, i.e. 1.0 means that the target voltage is present.
310    /// However, the script itself must check whether the pantograph is touching the contact wire.
311    pub fn voltage(self) -> f32 {
312        let voltage = unsafe { lotus_script_sys::vehicle::pantograph_voltage(self.index as u32) };
313        assert!(!voltage.is_nan());
314        assert_ne!(voltage, f32::INFINITY);
315        voltage
316    }
317}
318
319/// Provides the quality of the rails under the given axis.
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
321#[repr(u8)]
322pub enum RailQuality {
323    Smooth = 0,
324    Rough = 1,
325    FroggySmooth = 2,
326    FroggyRough = 3,
327    FlatGroove = 4,
328    HighSpeedSmooth = 5,
329    SmoothDirt = 6,
330    RoughDirt = 7,
331    Deraileur = 8,
332}
333
334impl TryFrom<u32> for RailQuality {
335    type Error = VehicleError;
336
337    fn try_from(value: u32) -> Result<Self, Self::Error> {
338        match value {
339            0 => Ok(RailQuality::Smooth),
340            1 => Ok(RailQuality::Rough),
341            2 => Ok(RailQuality::FroggySmooth),
342            3 => Ok(RailQuality::FroggyRough),
343            4 => Ok(RailQuality::FlatGroove),
344            5 => Ok(RailQuality::HighSpeedSmooth),
345            6 => Ok(RailQuality::SmoothDirt),
346            7 => Ok(RailQuality::RoughDirt),
347            8 => Ok(RailQuality::Deraileur),
348            value => Err(value.into()),
349        }
350    }
351}
352
353/// Type of the surface under the given axis.
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
355#[repr(u8)]
356pub enum SurfaceType {
357    Gravel = 0,
358    Street = 1,
359    Grass = 2,
360}
361
362impl TryFrom<u32> for SurfaceType {
363    type Error = VehicleError;
364
365    fn try_from(value: u32) -> Result<Self, Self::Error> {
366        match value {
367            0 => Ok(SurfaceType::Gravel),
368            1 => Ok(SurfaceType::Street),
369            2 => Ok(SurfaceType::Grass),
370            value => Err(value.into()),
371        }
372    }
373}
374
375#[derive(Clone, Copy)]
376pub struct RoadSteeringSpringDamperManipulator {
377    /// The stiffness is added to the default stiffness.
378    pub stiffness_add: f32,
379    /// The stiffness is multiplied by the default stiffness.
380    pub stiffness_mult: f32,
381    /// The damping is added to the default damping.
382    pub damping_add: f32,
383    /// The damping is multiplied by the default damping.
384    pub damping_mult: f32,
385}
386
387impl Default for RoadSteeringSpringDamperManipulator {
388    fn default() -> Self {
389        Self {
390            stiffness_add: 0.0,
391            stiffness_mult: 1.0,
392            damping_add: 0.0,
393            damping_mult: 1.0,
394        }
395    }
396}
397
398impl RoadSteeringSpringDamperManipulator {
399    pub fn new(
400        stiffness_add: f32,
401        stiffness_mult: f32,
402        damping_add: f32,
403        damping_mult: f32,
404    ) -> Self {
405        Self {
406            stiffness_add,
407            stiffness_mult,
408            damping_add,
409            damping_mult,
410        }
411    }
412}