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}