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}