Skip to main content

lotus_shared/
axis.rs

1//! Logische Controller-Achsen und Force Feedback, getrennt von Tastenaktionen.
2//!
3//! Logical controller axes and force feedback, separate from button actions.
4
5use serde::{Deserialize, Serialize};
6
7/// Achsenwert ohne Belegung und Ruhelage eines zentrierten Gebers.
8///
9/// Axis value without a binding, and the rest position of a centered control.
10pub const AXIS_CENTER: f32 = 0.5;
11
12/// Beschreibt eine Achse, die bei der Engine registriert werden kann.
13///
14/// Describes an axis that can be registered with the engine.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct RegisterAxis {
17    /// Eindeutige Achsen-ID für Scripts und die Belegung.
18    ///
19    /// Unique axis identifier used by scripts and bindings.
20    pub id: String,
21}
22
23impl RegisterAxis {
24    /// Erstellt einen neuen Registrierungseintrag für eine Achse.
25    ///
26    /// Creates a new axis registration entry.
27    pub fn new(id: impl Into<String>) -> Self {
28        Self { id: id.into() }
29    }
30}
31
32/// Rückstellkraft einer Achse.
33///
34/// Restoring force of an axis.
35///
36/// `coefficient` und `saturation` sind unbeschränkte endliche Stärken.
37/// Der Nennbereich ist `0..=1`. Die Engine multipliziert mit der Geräte-Intensität
38/// und klemmt erst danach auf `0..=1`.
39/// `offset` verschiebt die Federmitte und wird danach auf `-1..=1` geklemmt, ohne Skalierung.
40/// `0` lässt die Federmitte unverändert.
41///
42/// `coefficient` and `saturation` are unrestricted finite strengths.
43/// The nominal range is `0..=1`. The engine multiplies by the device intensity
44/// and only then clamps to `0..=1`.
45/// `offset` shifts the spring center and is then clamped to `-1..=1`, without scaling.
46/// `0` leaves the spring center unchanged.
47#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
48pub struct SpringForce {
49    /// Steifigkeit der Feder.
50    ///
51    /// Spring stiffness.
52    pub coefficient: f32,
53    /// Obere Grenze der Kraft.
54    ///
55    /// Upper bound of the force.
56    pub saturation: f32,
57    /// Verschiebung der Federmitte.
58    ///
59    /// Shift of the spring center.
60    pub offset: f32,
61}
62
63/// Schwingung entlang einer Achse.
64///
65/// Oscillation along an axis.
66#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
67pub struct AxisVibration {
68    /// Stärke als unbeschränkter endlicher Wert. `0` schaltet die Schwingung aus.
69    /// Die Engine multipliziert mit der Geräte-Intensität und klemmt erst danach auf `0..=1`.
70    ///
71    /// Strength as an unrestricted finite value. `0` turns the oscillation off.
72    /// The engine multiplies by the device intensity and only then clamps to `0..=1`.
73    pub magnitude: f32,
74    /// Frequenz in Hertz.
75    ///
76    /// Frequency in hertz.
77    pub frequency_hz: f32,
78}
79
80/// Zuletzt gewünschtes Force Feedback einer logischen Achse.
81///
82/// Latest requested force feedback for one logical axis.
83#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
84pub struct AxisForceFeedback {
85    /// Feder. `None`, solange das Script noch keine gesetzt hat.
86    ///
87    /// Spring. `None` until the script sets one.
88    pub spring: Option<SpringForce>,
89    /// Reibung, `0` heißt aus.
90    ///
91    /// Friction; `0` means off.
92    pub friction: f32,
93    /// Schwingung. `None` heißt aus.
94    ///
95    /// Oscillation. `None` means off.
96    pub vibration: Option<AxisVibration>,
97}
98
99/// Begrenzt einen endlichen Wert auf `0..=1`. Nicht endliche Werte werden 0.
100///
101/// Clamps a finite value to `0..=1`. Non-finite values become 0.
102pub fn clamp_unit(value: f32) -> f32 {
103    if value.is_finite() {
104        value.clamp(0.0, 1.0)
105    } else {
106        0.0
107    }
108}
109
110/// Begrenzt einen endlichen Wert auf `-1..=1`. Nicht endliche Werte werden 0.
111///
112/// Clamps a finite value to `-1..=1`. Non-finite values become 0.
113pub fn clamp_signed_unit(value: f32) -> f32 {
114    if value.is_finite() {
115        value.clamp(-1.0, 1.0)
116    } else {
117        0.0
118    }
119}
120
121/// Begrenzt eine Frequenz auf `>= 0`. Nicht endliche Werte werden 0.
122///
123/// Clamps a frequency to `>= 0`. Non-finite values become 0.
124pub fn clamp_frequency_hz(value: f32) -> f32 {
125    if value.is_finite() {
126        value.max(0.0)
127    } else {
128        0.0
129    }
130}