avian3d/dynamics/joints/
motor.rs

1use crate::prelude::*;
2use bevy::prelude::*;
3
4/// Determines how the joint motor force/torque is computed.
5///
6/// Different models offer trade-offs between ease of tuning and physical accuracy.
7/// The default is a [`SpringDamper`](MotorModel::SpringDamper) model that provides
8/// stable, predictable behavior across different configurations.
9#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
10#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
12#[reflect(Debug, PartialEq)]
13pub enum MotorModel {
14    /// A spring-damper model using implicit Euler integration.
15    ///
16    /// Unlike the other models, this is unconditionally stable: the implicit formulation
17    /// naturally limits the response as frequency increases, preventing overshoot and
18    /// oscillation even with aggressive parameters. This makes it easier to tune than
19    /// the other models, which can become unstable with high stiffness values.
20    ///
21    /// This is the recommended model for most use cases.
22    ///
23    /// # Parameters
24    ///
25    /// - `frequency`: The natural frequency of the spring in Hz. Higher values create stiffer springs.
26    /// - `damping_ratio`: The damping ratio.
27    ///   - 0.0 = no damping (oscillates forever)
28    ///   - 1.0 = critically damped (fastest approach without overshoot)
29    ///   - \> 1.0 = overdamped (slower approach without overshoot)
30    ///   - < 1.0 = underdamped (overshoots and oscillates)
31    SpringDamper {
32        /// The natural frequency of the spring in Hz.
33        frequency: Scalar,
34        /// The damping ratio.
35        damping_ratio: Scalar,
36    },
37
38    /// The motor force/torque is computed directly from the stiffness and damping parameters.
39    ///
40    /// The model can be described by the following formula:
41    ///
42    /// ```text
43    /// force = (stiffness * position_error) + (damping * velocity_error)
44    /// ```
45    ///
46    /// This produces physically accurate forces/torques, but requires careful tuning of the
47    /// stiffness and damping parameters based on the masses of the connected bodies.
48    /// High stiffness values can cause instability (overshoot, oscillation, or divergence),
49    /// so parameters must be chosen appropriately for your timestep and mass configuration.
50    ///
51    /// # Parameters
52    ///
53    /// - `stiffness`: The stiffness coefficient for position control. Set to zero for pure velocity control.
54    /// - `damping`: The damping coefficient for velocity control.
55    ForceBased {
56        /// The stiffness coefficient for position control.
57        stiffness: Scalar,
58        /// The damping coefficient for velocity control.
59        damping: Scalar,
60    },
61
62    /// The motor force/torque is computed based on the acceleration required to reach the target.
63    ///
64    /// The model can be described by the following formula:
65    ///
66    /// ```text
67    /// acceleration = (stiffness * position_error) + (damping * velocity_error)
68    /// ```
69    ///
70    /// This automatically scales the motor force/torque based on the masses of the bodies,
71    /// resulting in consistent behavior across different mass configurations.
72    /// It is therefore easier to tune compared to the [`ForceBased`](MotorModel::ForceBased) model,
73    /// which requires manual adjustment of stiffness and damping based on mass.
74    ///
75    /// Note that high stiffness values can still cause instability. For unconditionally
76    /// stable behavior, use the [`SpringDamper`](MotorModel::SpringDamper) model instead.
77    ///
78    /// # Parameters
79    ///
80    /// - `stiffness`: The stiffness coefficient for position control. Set to zero for pure velocity control.
81    /// - `damping`: The damping coefficient for velocity control.
82    AccelerationBased {
83        /// The stiffness coefficient for position control.
84        stiffness: Scalar,
85        /// The damping coefficient for velocity control.
86        damping: Scalar,
87    },
88}
89
90impl Default for MotorModel {
91    /// The default motor model: a critically damped spring-damper with 5 Hz frequency.
92    fn default() -> Self {
93        Self::DEFAULT
94    }
95}
96
97impl MotorModel {
98    /// The default motor model: a critically damped spring-damper with 5 Hz frequency.
99    pub const DEFAULT: Self = Self::SpringDamper {
100        frequency: 5.0,
101        damping_ratio: 1.0,
102    };
103}
104
105/// A motor for driving the angular motion of a [`RevoluteJoint`].
106///
107/// Motors are configured as part of a joint, applying torque to drive
108/// the joint towards a target velocity and/or position.
109///
110/// ```ignore
111/// RevoluteJoint::new(entity1, entity2)
112///     .with_motor(
113///         AngularMotor::new(MotorModel::SpringDamper {
114///             frequency: 2.0,
115///             damping_ratio: 1.0,
116///         })
117///         .with_target_position(target_angle)
118///     )
119/// ```
120#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
121#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
122#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
123#[reflect(Debug, PartialEq)]
124pub struct AngularMotor {
125    /// Whether the motor is enabled.
126    pub enabled: bool,
127    /// The target angular velocity (rad/s).
128    pub target_velocity: Scalar,
129    /// The target angle (rad) for position control.
130    pub target_position: Scalar,
131    /// The maximum torque the motor can apply (N·m).
132    pub max_torque: Scalar,
133    /// The motor model used for computing the motor torque.
134    pub motor_model: MotorModel,
135}
136
137impl Default for AngularMotor {
138    fn default() -> Self {
139        Self::new(MotorModel::DEFAULT)
140    }
141}
142
143impl AngularMotor {
144    /// Creates a new angular motor with the given motor model.
145    #[inline]
146    pub const fn new(motor_model: MotorModel) -> Self {
147        Self {
148            enabled: true,
149            target_velocity: 0.0,
150            target_position: 0.0,
151            max_torque: Scalar::MAX,
152            motor_model,
153        }
154    }
155
156    /// Creates a new disabled angular motor with the given motor model.
157    ///
158    /// To enable the motor later, use [`set_enabled`](Self::set_enabled).
159    #[inline]
160    pub const fn new_disabled(motor_model: MotorModel) -> Self {
161        Self {
162            enabled: false,
163            ..Self::new(motor_model)
164        }
165    }
166
167    /// Enables or disables the motor.
168    #[inline]
169    pub const fn set_enabled(&mut self, enabled: bool) -> &mut Self {
170        self.enabled = enabled;
171        self
172    }
173
174    /// Sets the target angular velocity in radians per second.
175    #[inline]
176    pub const fn with_target_velocity(mut self, velocity: Scalar) -> Self {
177        self.target_velocity = velocity;
178        self
179    }
180
181    /// Sets the target position.
182    #[inline]
183    pub const fn with_target_position(mut self, target_position: Scalar) -> Self {
184        self.target_position = target_position;
185        self
186    }
187
188    /// Sets the maximum torque the motor can apply.
189    #[inline]
190    pub const fn with_max_torque(mut self, max_torque: Scalar) -> Self {
191        self.max_torque = max_torque;
192        self
193    }
194
195    /// Sets the motor model used for computing the motor torque.
196    #[inline]
197    pub const fn with_motor_model(mut self, motor_model: MotorModel) -> Self {
198        self.motor_model = motor_model;
199        self
200    }
201}
202
203/// A motor for driving the linear motion of a [`PrismaticJoint`].
204///
205/// Motors are configured as part of a joint, applying force to drive
206/// the joint towards a target velocity and/or position.
207///
208/// # Spring-Damper Model
209///
210/// For stable position control that behaves consistently across different configurations,
211/// use [`MotorModel::SpringDamper`]. This uses implicit Euler integration for
212/// unconditional stability.
213///
214/// ```ignore
215/// PrismaticJoint::new(entity1, entity2)
216///     .with_motor(
217///         LinearMotor::new(MotorModel::SpringDamper {
218///             frequency: 2.0,
219///             damping_ratio: 1.0,
220///         })
221///         .with_target_position(target_position)
222///     )
223/// ```
224#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
225#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
226#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
227#[reflect(Debug, PartialEq)]
228pub struct LinearMotor {
229    /// Whether the motor is enabled.
230    pub enabled: bool,
231    /// The target linear velocity (m/s).
232    pub target_velocity: Scalar,
233    /// The target position (m) for position control.
234    pub target_position: Scalar,
235    /// The maximum force the motor can apply (N).
236    pub max_force: Scalar,
237    /// The motor model used for computing the motor force.
238    pub motor_model: MotorModel,
239}
240
241impl Default for LinearMotor {
242    fn default() -> Self {
243        Self::new(MotorModel::DEFAULT)
244    }
245}
246
247impl LinearMotor {
248    /// Creates a new linear motor with the given motor model.
249    #[inline]
250    pub const fn new(motor_model: MotorModel) -> Self {
251        Self {
252            enabled: true,
253            target_velocity: 0.0,
254            target_position: 0.0,
255            max_force: Scalar::MAX,
256            motor_model,
257        }
258    }
259
260    /// Creates a new disabled linear motor with the given motor model.
261    ///
262    /// To enable the motor later, use [`set_enabled`](Self::set_enabled).
263    #[inline]
264    pub const fn new_disabled(motor_model: MotorModel) -> Self {
265        Self {
266            enabled: false,
267            ..Self::new(motor_model)
268        }
269    }
270
271    /// Enables or disables the motor.
272    #[inline]
273    pub const fn set_enabled(&mut self, enabled: bool) -> &mut Self {
274        self.enabled = enabled;
275        self
276    }
277
278    /// Sets the target linear velocity in meters per second.
279    #[inline]
280    pub const fn with_target_velocity(mut self, velocity: Scalar) -> Self {
281        self.target_velocity = velocity;
282        self
283    }
284
285    /// Sets the target position.
286    #[inline]
287    pub const fn with_target_position(mut self, target_position: Scalar) -> Self {
288        self.target_position = target_position;
289        self
290    }
291
292    /// Sets the maximum force the motor can apply.
293    #[inline]
294    pub const fn with_max_force(mut self, max_force: Scalar) -> Self {
295        self.max_force = max_force;
296        self
297    }
298
299    /// Sets the motor model used for computing the motor force.
300    #[inline]
301    pub const fn with_motor_model(mut self, motor_model: MotorModel) -> Self {
302        self.motor_model = motor_model;
303        self
304    }
305}