lotussim-shared 0.5.1

Shared code for LOTUS scripts and engine.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};

use crate::message::{Coupling, MessageMeta, MessageType};

#[derive(Debug, thiserror::Error)]
pub enum VehicleError {
    #[error("vehicle not found")]
    VehicleNotFound = 256,
    #[error("bogie not found")]
    BogieNotFound = 512,
    #[error("axle not found")]
    AxleNotFound = 1024,
    #[error("coupling not found")]
    CouplingNotFound = 2048,
    #[error("pantograph not found")]
    PantographNotFound = 4096,
    #[error("road axle not found")]
    RoadAxleNotFound = 8192,
    #[error("road wheel not found")]
    RoadWheelNotFound = 16384,
    #[error("unknown error")]
    Unknown = 0,
}

impl From<u32> for VehicleError {
    fn from(value: u32) -> Self {
        match value {
            256 => VehicleError::VehicleNotFound,
            512 => VehicleError::BogieNotFound,
            1024 => VehicleError::AxleNotFound,
            2048 => VehicleError::CouplingNotFound,
            4096 => VehicleError::PantographNotFound,
            8192 => VehicleError::RoadAxleNotFound,
            16384 => VehicleError::RoadWheelNotFound,
            _ => VehicleError::Unknown,
        }
    }
}

#[cfg(feature = "ffi")]
/// Returns `true` if the vehicle was spawned inverted to the train.
pub fn spawned_inverted_to_train() -> bool {
    unsafe { lotus_script_sys::vehicle::spawned_inverted_to_train() == 1 }
}

/// Describes an event that is sent when the train configuration is changed.
/// Please note: When two trains with different directions are coupled,
/// the new direction cannot be predicted!
/// In the vehicles of the train whose direction is inverted when coupling,
/// "reversed_to_train" is inverted and the index order reverses.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TrainConfigurationChanged {
    pub entity_id: u64,
    pub reversed_to_train: bool,
    pub index_in_train: usize,
    pub train_vehicle_count: usize,
}

impl MessageType for TrainConfigurationChanged {
    const MESSAGE_META: MessageMeta = MessageMeta::new("builtin", "vehicle_in_train_changed", None);
}

/// Calculation of the vehicle count in front or behind the vehicle
/// relative to the vehicle.
impl TrainConfigurationChanged {
    pub fn neighbour_vehicle_count(&self, coupling: Coupling) -> usize {
        if (coupling == Coupling::Rear) ^ self.reversed_to_train {
            self.train_vehicle_count - self.index_in_train - 1
        } else {
            self.index_in_train
        }
    }
}

#[cfg(feature = "ffi")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Bogie {
    index: usize,
}

#[cfg(feature = "ffi")]
impl Bogie {
    pub fn get(index: usize) -> Result<Self, VehicleError> {
        match unsafe { lotus_script_sys::vehicle::bogie_is_valid(index as u32) } {
            0 => Ok(Self { index }),
            e => Err(e.into()),
        }
    }

    /// Sets the rail brake force at the given bogie.
    /// The rail brake consists of electromagnets that are set against the rail.
    /// They then slide over the rail with high friction, which allows the vehicle to be braked much more strongly:
    /// While the normal wheel brake only has the axle load available to build up a frictional grip with the rail,
    /// the rail brake can exert much higher frictional forces and thus braking forces,
    /// even relatively independent of the rail condition (moisture and dirt are effectively "ground off").
    pub fn set_rail_brake_force_newton(self, value: f32) {
        unsafe { lotus_script_sys::vehicle::set_rail_brake_force_newton(self.index as u32, value) };
    }
}

#[cfg(feature = "ffi")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Axle {
    bogie_index: usize,
    axle_index: usize,
}

#[cfg(feature = "ffi")]
impl Axle {
    pub fn get(bogie_index: usize, axle_index: usize) -> Result<Self, VehicleError> {
        match unsafe {
            lotus_script_sys::vehicle::axle_is_valid(bogie_index as u32, axle_index as u32)
        } {
            0 => Ok(Self {
                bogie_index,
                axle_index,
            }),
            e => Err(e.into()),
        }
    }

    pub fn velocity_var_name(self) -> String {
        format!("v_Axle_mps_{}_{}", self.bogie_index, self.axle_index)
    }

    pub fn bogie(self) -> Bogie {
        Bogie {
            index: self.bogie_index,
        }
    }

    pub fn axle_index(self) -> usize {
        self.axle_index
    }

    pub fn bogie_index(self) -> usize {
        self.bogie_index
    }

    /// Gets the curvature of the track under the given axis.
    /// 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.
    /// 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.
    /// Positive = right, negative = left.
    pub fn inverse_radius(self) -> f32 {
        let inverse_radius = unsafe {
            lotus_script_sys::vehicle::inverse_radius(
                self.bogie_index as u32,
                self.axle_index as u32,
            )
        };
        assert!(!inverse_radius.is_nan());
        assert_ne!(inverse_radius, f32::NEG_INFINITY);
        assert_ne!(inverse_radius, f32::INFINITY);
        inverse_radius
    }

    /// Provides the type of the surface under the given axis.
    pub fn surface_type(self) -> SurfaceType {
        let surface_type = unsafe {
            lotus_script_sys::vehicle::surface_type(self.bogie_index as u32, self.axle_index as u32)
        };
        SurfaceType::try_from(surface_type).unwrap()
    }

    /// Provides the quality of the rails under the given axis.
    pub fn rail_quality(self) -> RailQuality {
        let quality = unsafe {
            lotus_script_sys::vehicle::rail_quality(self.bogie_index as u32, self.axle_index as u32)
        };
        RailQuality::try_from(quality).unwrap()
    }

    /// Sets the traction force in newton.
    /// 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.
    /// 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.
    pub fn set_traction_force_newton(self, value: f32) {
        unsafe {
            lotus_script_sys::vehicle::set_traction_force_newton(
                self.bogie_index as u32,
                self.axle_index as u32,
                value,
            )
        };
    }

    /// Sets the brake force in newton.
    /// 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.
    /// The difference to "traction_force_newton" is that brake_force_newton is always positive and always acts in the opposite direction to the travel.
    /// This means that brake_force_newton can also hold the vehicle stationary like a disc brake.
    pub fn set_brake_force_newton(self, value: f32) {
        unsafe {
            lotus_script_sys::vehicle::set_brake_force_newton(
                self.bogie_index as u32,
                self.axle_index as u32,
                value,
            )
        };
    }
}

#[cfg(feature = "ffi")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoadAxle {
    index: usize,
}

#[cfg(feature = "ffi")]
impl RoadAxle {
    pub fn get(index: usize) -> Result<Self, VehicleError> {
        match unsafe { lotus_script_sys::vehicle::road_axle_is_valid(index as u32) } {
            0 => Ok(Self { index }),
            e => Err(e.into()),
        }
    }
}

#[cfg(feature = "ffi")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RoadWheel {
    axle_index: usize,
    wheel_index: usize,
}

#[cfg(feature = "ffi")]
impl RoadWheel {
    pub fn get(axle_index: usize, wheel_index: usize) -> Result<Self, VehicleError> {
        match unsafe {
            lotus_script_sys::vehicle::road_wheel_is_valid(axle_index as u32, wheel_index as u32)
        } {
            0 => Ok(Self {
                axle_index,
                wheel_index,
            }),
            e => Err(e.into()),
        }
    }

    pub fn velocity_var_name(self) -> String {
        format!("v_wheel_mps_{}_{}", self.axle_index, self.wheel_index)
    }

    pub fn wheel_index(self) -> usize {
        self.wheel_index
    }

    pub fn axle_index(self) -> usize {
        self.axle_index
    }

    /// Sets the traction force at the running surface in newton.
    pub fn set_traction_force_newton(self, value: f32) {
        unsafe {
            lotus_script_sys::vehicle::set_wheel_traction_force_newton(
                self.axle_index as u32,
                self.wheel_index as u32,
                value,
            )
        };
    }

    /// Sets the brake force at the running surface in newton.
    pub fn set_brake_force_newton(self, value: f32) {
        unsafe {
            lotus_script_sys::vehicle::set_wheel_brake_force_newton(
                self.axle_index as u32,
                self.wheel_index as u32,
                value,
            )
        };
    }

    /// Sets the factor, which manipulates the spring stiffness.
    pub fn set_spring_factor(self, value: f32) {
        unsafe {
            lotus_script_sys::vehicle::set_wheel_spring_factor(
                self.axle_index as u32,
                self.wheel_index as u32,
                value,
            )
        };
    }
}

#[cfg(feature = "ffi")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Pantograph {
    index: usize,
}

#[cfg(feature = "ffi")]
impl Pantograph {
    pub fn get(index: usize) -> Result<Self, VehicleError> {
        match unsafe { lotus_script_sys::vehicle::pantograph_is_valid(index as u32) } {
            0 => Ok(Self { index }),
            e => Err(e.into()),
        }
    }

    /// Returns the height of the lowest contact wire above the pantograph position.
    pub fn height(self) -> f32 {
        let height = unsafe { lotus_script_sys::vehicle::pantograph_height(self.index as u32) };
        assert!(!height.is_nan());
        assert_ne!(height, f32::INFINITY);
        height
    }

    /// The voltage of the contact wire above the pantograph. The value is normalized, i.e. 1.0 means that the target voltage is present.
    /// However, the script itself must check whether the pantograph is touching the contact wire.
    pub fn voltage(self) -> f32 {
        let voltage = unsafe { lotus_script_sys::vehicle::pantograph_voltage(self.index as u32) };
        assert!(!voltage.is_nan());
        assert_ne!(voltage, f32::INFINITY);
        voltage
    }
}

/// Provides the quality of the rails under the given axis.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum RailQuality {
    Smooth = 0,
    Rough = 1,
    FroggySmooth = 2,
    FroggyRough = 3,
    FlatGroove = 4,
    HighSpeedSmooth = 5,
    SmoothDirt = 6,
    RoughDirt = 7,
    Deraileur = 8,
}

impl TryFrom<u32> for RailQuality {
    type Error = VehicleError;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(RailQuality::Smooth),
            1 => Ok(RailQuality::Rough),
            2 => Ok(RailQuality::FroggySmooth),
            3 => Ok(RailQuality::FroggyRough),
            4 => Ok(RailQuality::FlatGroove),
            5 => Ok(RailQuality::HighSpeedSmooth),
            6 => Ok(RailQuality::SmoothDirt),
            7 => Ok(RailQuality::RoughDirt),
            8 => Ok(RailQuality::Deraileur),
            value => Err(value.into()),
        }
    }
}

/// Type of the surface under the given axis.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum SurfaceType {
    Gravel = 0,
    Street = 1,
    Grass = 2,
}

impl TryFrom<u32> for SurfaceType {
    type Error = VehicleError;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(SurfaceType::Gravel),
            1 => Ok(SurfaceType::Street),
            2 => Ok(SurfaceType::Grass),
            value => Err(value.into()),
        }
    }
}

#[derive(Clone, Copy)]
pub struct RoadSteeringSpringDamperManipulator {
    /// The stiffness is added to the default stiffness.
    pub stiffness_add: f32,
    /// The stiffness is multiplied by the default stiffness.
    pub stiffness_mult: f32,
    /// The damping is added to the default damping.
    pub damping_add: f32,
    /// The damping is multiplied by the default damping.
    pub damping_mult: f32,
}

impl Default for RoadSteeringSpringDamperManipulator {
    fn default() -> Self {
        Self {
            stiffness_add: 0.0,
            stiffness_mult: 1.0,
            damping_add: 0.0,
            damping_mult: 1.0,
        }
    }
}

impl RoadSteeringSpringDamperManipulator {
    pub fn new(
        stiffness_add: f32,
        stiffness_mult: f32,
        damping_add: f32,
        damping_mult: f32,
    ) -> Self {
        Self {
            stiffness_add,
            stiffness_mult,
            damping_add,
            damping_mult,
        }
    }
}