Skip to main content

avian3d/dynamics/joints/
motor.rs

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