rapier2d/control/pid_controller.rs
1use crate::dynamics::{AxesMask, RigidBody, RigidBodyPosition, RigidBodyVelocity};
2use crate::math::{AngVector, Pose, Real, Rotation, Vector};
3
4/// A Proportional-Derivative (PD) controller.
5///
6/// This is useful for controlling a rigid-body at the velocity level so it matches a target
7/// pose.
8///
9/// This is a [PID controller](https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller)
10/// without the Integral part to keep the API immutable, while having a behaviour generally
11/// sufficient for games.
12#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
13#[derive(Debug, Copy, Clone, PartialEq)]
14pub struct PdController {
15 /// The Proportional gain applied to the instantaneous linear position errors.
16 ///
17 /// This is usually set to a multiple of the inverse of simulation step time
18 /// (e.g. `60` if the delta-time is `1.0 / 60.0`).
19 pub lin_kp: Vector,
20 /// The Derivative gain applied to the instantaneous linear velocity errors.
21 ///
22 /// This is usually set to a value in `[0.0, 1.0]` where `0.0` implies no damping
23 /// (no correction of velocity errors) and `1.0` implies complete damping (velocity errors
24 /// are corrected in a single simulation step).
25 pub lin_kd: Vector,
26 /// The Proportional gain applied to the instantaneous angular position errors.
27 ///
28 /// This is usually set to a multiple of the inverse of simulation step time
29 /// (e.g. `60` if the delta-time is `1.0 / 60.0`).
30 pub ang_kp: AngVector,
31 /// The Derivative gain applied to the instantaneous angular velocity errors.
32 ///
33 /// This is usually set to a value in `[0.0, 1.0]` where `0.0` implies no damping
34 /// (no correction of velocity errors) and `1.0` implies complete damping (velocity errors
35 /// are corrected in a single simulation step).
36 pub ang_kd: AngVector,
37 /// The axes affected by this controller.
38 ///
39 /// Only coordinate axes with a bit flags set to `true` will be taken into
40 /// account when calculating the errors and corrections.
41 pub axes: AxesMask,
42}
43
44impl Default for PdController {
45 fn default() -> Self {
46 Self::new(60.0, 0.8, AxesMask::all())
47 }
48}
49
50/// A Proportional-Integral-Derivative (PID) controller.
51///
52/// For video games, the Proportional-Derivative [`PdController`] is generally sufficient and
53/// offers an immutable API.
54#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
55#[derive(Debug, Copy, Clone, PartialEq)]
56pub struct PidController {
57 /// The Proportional-Derivative (PD) part of this PID controller.
58 pub pd: PdController,
59 /// The translational error accumulated through time for the Integral part of the PID controller.
60 pub lin_integral: Vector,
61 /// The angular error accumulated through time for the Integral part of the PID controller.
62 pub ang_integral: AngVector,
63 /// The linear gain applied to the Integral part of the PID controller.
64 pub lin_ki: Vector,
65 /// The angular gain applied to the Integral part of the PID controller.
66 pub ang_ki: AngVector,
67}
68
69impl Default for PidController {
70 fn default() -> Self {
71 Self::new(60.0, 1.0, 0.8, AxesMask::all())
72 }
73}
74
75/// Position or velocity errors measured for PID control.
76pub struct PdErrors {
77 /// The linear (translational) part of the error.
78 pub linear: Vector,
79 /// The angular (rotational) part of the error.
80 pub angular: AngVector,
81}
82
83impl From<RigidBodyVelocity<Real>> for PdErrors {
84 fn from(vels: RigidBodyVelocity<Real>) -> Self {
85 Self {
86 #[cfg(feature = "dim2")]
87 linear: Vector::new(vels.linvel.x, vels.linvel.y),
88 #[cfg(feature = "dim3")]
89 linear: Vector::new(vels.linvel.x, vels.linvel.y, vels.linvel.z),
90 #[cfg(feature = "dim2")]
91 angular: vels.angvel,
92 #[cfg(feature = "dim3")]
93 angular: AngVector::new(vels.angvel.x, vels.angvel.y, vels.angvel.z),
94 }
95 }
96}
97
98impl PdController {
99 /// Initialized the PD controller with uniform gain.
100 ///
101 /// The same gain are applied on all axes. To configure per-axes gains, construct
102 /// the [`PdController`] by setting its fields explicitly instead.
103 ///
104 /// Only the axes specified in `axes` will be enabled (but the gain values are set
105 /// on all axes regardless).
106 pub fn new(kp: Real, kd: Real, axes: AxesMask) -> PdController {
107 #[cfg(feature = "dim2")]
108 return Self {
109 lin_kp: Vector::splat(kp),
110 lin_kd: Vector::splat(kd),
111 ang_kp: kp,
112 ang_kd: kd,
113 axes,
114 };
115
116 #[cfg(feature = "dim3")]
117 return Self {
118 lin_kp: Vector::splat(kp),
119 lin_kd: Vector::splat(kd),
120 ang_kp: AngVector::splat(kp),
121 ang_kd: AngVector::splat(kd),
122 axes,
123 };
124 }
125
126 /// Calculates the linear correction from positional and velocity errors calculated automatically
127 /// from a rigid-body and the desired positions/velocities.
128 ///
129 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
130 /// the inverse of the simulation step so the returned value is a linear rigid-body velocity
131 /// change.
132 pub fn linear_rigid_body_correction(
133 &self,
134 rb: &RigidBody,
135 target_pos: Vector,
136 target_linvel: Vector,
137 ) -> Vector {
138 let angvel = rb.angvel();
139
140 self.rigid_body_correction(
141 rb,
142 Pose::from_translation(target_pos),
143 RigidBodyVelocity {
144 linvel: target_linvel,
145 angvel,
146 },
147 )
148 .linvel
149 }
150
151 /// Calculates the angular correction from positional and velocity errors calculated automatically
152 /// from a rigid-body and the desired positions/velocities.
153 ///
154 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
155 /// the inverse of the simulation step so the returned value is an angular rigid-body velocity
156 /// change.
157 pub fn angular_rigid_body_correction(
158 &self,
159 rb: &RigidBody,
160 target_rot: Rotation,
161 target_angvel: AngVector,
162 ) -> AngVector {
163 self.rigid_body_correction(
164 rb,
165 Pose::from_parts(Vector::ZERO, target_rot),
166 RigidBodyVelocity {
167 linvel: rb.linvel(),
168 angvel: target_angvel,
169 },
170 )
171 .angvel
172 }
173
174 /// Calculates the linear and angular correction from positional and velocity errors calculated
175 /// automatically from a rigid-body and the desired poses/velocities.
176 ///
177 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
178 /// the inverse of the simulation step so the returned value is a rigid-body velocity
179 /// change.
180 pub fn rigid_body_correction(
181 &self,
182 rb: &RigidBody,
183 target_pose: Pose,
184 target_vels: RigidBodyVelocity<Real>,
185 ) -> RigidBodyVelocity<Real> {
186 let pose_errors = RigidBodyPosition {
187 position: rb.pos.position,
188 next_position: target_pose,
189 }
190 .pose_errors(rb.local_center_of_mass());
191 let vels_errors = target_vels - rb.vels;
192 self.correction(&pose_errors, &vels_errors.into())
193 }
194
195 /// Mask where each component is 1.0 or 0.0 depending on whether
196 /// the corresponding linear axis is enabled.
197 fn lin_mask(&self) -> Vector {
198 #[cfg(feature = "dim2")]
199 return Vector::new(
200 self.axes.contains(AxesMask::LIN_X) as u32 as Real,
201 self.axes.contains(AxesMask::LIN_Y) as u32 as Real,
202 );
203 #[cfg(feature = "dim3")]
204 return Vector::new(
205 self.axes.contains(AxesMask::LIN_X) as u32 as Real,
206 self.axes.contains(AxesMask::LIN_Y) as u32 as Real,
207 self.axes.contains(AxesMask::LIN_Z) as u32 as Real,
208 );
209 }
210
211 /// Mask where each component is 1.0 or 0.0 depending on whether
212 /// the corresponding angular axis is enabled.
213 fn ang_mask(&self) -> AngVector {
214 #[cfg(feature = "dim2")]
215 return self.axes.contains(AxesMask::ANG_Z) as u32 as Real;
216 #[cfg(feature = "dim3")]
217 return Vector::new(
218 self.axes.contains(AxesMask::ANG_X) as u32 as Real,
219 self.axes.contains(AxesMask::ANG_Y) as u32 as Real,
220 self.axes.contains(AxesMask::ANG_Z) as u32 as Real,
221 );
222 }
223
224 /// Calculates the linear and angular correction from the given positional and velocity errors.
225 ///
226 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
227 /// the inverse of the simulation step so the returned value is a rigid-body velocity
228 /// change.
229 pub fn correction(
230 &self,
231 pose_errors: &PdErrors,
232 vel_errors: &PdErrors,
233 ) -> RigidBodyVelocity<Real> {
234 let lin_mask = self.lin_mask();
235 let ang_mask = self.ang_mask();
236
237 let linvel =
238 (pose_errors.linear * self.lin_kp + vel_errors.linear * self.lin_kd) * lin_mask;
239 let angvel =
240 (pose_errors.angular * self.ang_kp + vel_errors.angular * self.ang_kd) * ang_mask;
241
242 RigidBodyVelocity { linvel, angvel }
243 }
244}
245
246impl PidController {
247 /// Initialized the PDI controller with uniform gain.
248 ///
249 /// The same gain are applied on all axes. To configure per-axes gains, construct
250 /// the [`PidController`] by setting its fields explicitly instead.
251 ///
252 /// Only the axes specified in `axes` will be enabled (but the gain values are set
253 /// on all axes regardless).
254 pub fn new(kp: Real, ki: Real, kd: Real, axes: AxesMask) -> PidController {
255 #[cfg(feature = "dim2")]
256 return Self {
257 pd: PdController::new(kp, kd, axes),
258 lin_integral: Vector::ZERO,
259 ang_integral: 0.0,
260 lin_ki: Vector::splat(ki),
261 ang_ki: ki,
262 };
263
264 #[cfg(feature = "dim3")]
265 return Self {
266 pd: PdController::new(kp, kd, axes),
267 lin_integral: Vector::ZERO,
268 ang_integral: AngVector::ZERO,
269 lin_ki: Vector::splat(ki),
270 ang_ki: AngVector::splat(ki),
271 };
272 }
273
274 /// Set the axes errors and corrections are computed for.
275 ///
276 /// This doesn’t modify any of the gains.
277 pub fn set_axes(&mut self, axes: AxesMask) {
278 self.pd.axes = axes;
279 }
280
281 /// Get the axes errors and corrections are computed for.
282 pub fn axes(&self) -> AxesMask {
283 self.pd.axes
284 }
285
286 /// Resets to zero the accumulated linear and angular errors used by
287 /// the Integral part of the controller.
288 pub fn reset_integrals(&mut self) {
289 self.lin_integral = Vector::ZERO;
290 #[cfg(feature = "dim2")]
291 {
292 self.ang_integral = 0.0;
293 }
294 #[cfg(feature = "dim3")]
295 {
296 self.ang_integral = AngVector::ZERO;
297 }
298 }
299
300 /// Calculates the linear correction from positional and velocity errors calculated automatically
301 /// from a rigid-body and the desired positions/velocities.
302 ///
303 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
304 /// the inverse of the simulation step so the returned value is a linear rigid-body velocity
305 /// change.
306 ///
307 /// This method is mutable because of the need to update the accumulated positional
308 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
309 /// an immutable API is needed.
310 pub fn linear_rigid_body_correction(
311 &mut self,
312 dt: Real,
313 rb: &RigidBody,
314 target_pos: Vector,
315 target_linvel: Vector,
316 ) -> Vector {
317 self.rigid_body_correction(
318 dt,
319 rb,
320 Pose::from_translation(target_pos),
321 RigidBodyVelocity {
322 linvel: target_linvel,
323 angvel: rb.angvel(),
324 },
325 )
326 .linvel
327 }
328
329 /// Calculates the angular correction from positional and velocity errors calculated automatically
330 /// from a rigid-body and the desired positions/velocities.
331 ///
332 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
333 /// the inverse of the simulation step so the returned value is an angular rigid-body velocity
334 /// change.
335 ///
336 /// This method is mutable because of the need to update the accumulated positional
337 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
338 /// an immutable API is needed.
339 pub fn angular_rigid_body_correction(
340 &mut self,
341 dt: Real,
342 rb: &RigidBody,
343 target_rot: Rotation,
344 target_angvel: AngVector,
345 ) -> AngVector {
346 self.rigid_body_correction(
347 dt,
348 rb,
349 Pose::from_parts(Vector::ZERO, target_rot),
350 RigidBodyVelocity {
351 linvel: rb.linvel(),
352 angvel: target_angvel,
353 },
354 )
355 .angvel
356 }
357
358 /// Calculates the linear and angular correction from positional and velocity errors calculated
359 /// automatically from a rigid-body and the desired poses/velocities.
360 ///
361 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
362 /// the inverse of the simulation step so the returned value is a rigid-body velocity
363 /// change.
364 ///
365 /// This method is mutable because of the need to update the accumulated positional
366 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
367 /// an immutable API is needed.
368 pub fn rigid_body_correction(
369 &mut self,
370 dt: Real,
371 rb: &RigidBody,
372 target_pose: Pose,
373 target_vels: RigidBodyVelocity<Real>,
374 ) -> RigidBodyVelocity<Real> {
375 let pose_errors = RigidBodyPosition {
376 position: rb.pos.position,
377 next_position: target_pose,
378 }
379 .pose_errors(rb.local_center_of_mass());
380 let vels_errors = target_vels - rb.vels;
381 self.correction(dt, &pose_errors, &vels_errors.into())
382 }
383
384 /// Calculates the linear and angular correction from the given positional and velocity errors.
385 ///
386 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
387 /// the inverse of the simulation step so the returned value is a rigid-body velocity
388 /// change.
389 ///
390 /// This method is mutable because of the need to update the accumulated positional
391 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
392 /// an immutable API is needed.
393 pub fn correction(
394 &mut self,
395 dt: Real,
396 pose_errors: &PdErrors,
397 vel_errors: &PdErrors,
398 ) -> RigidBodyVelocity<Real> {
399 self.lin_integral += pose_errors.linear * dt;
400 self.ang_integral += pose_errors.angular * dt;
401
402 let lin_mask = self.pd.lin_mask();
403 let ang_mask = self.pd.ang_mask();
404
405 let linvel = (pose_errors.linear * self.pd.lin_kp
406 + vel_errors.linear * self.pd.lin_kd
407 + self.lin_integral * self.lin_ki)
408 * lin_mask;
409 #[cfg(feature = "dim2")]
410 let angvel = (pose_errors.angular * self.pd.ang_kp
411 + vel_errors.angular * self.pd.ang_kd
412 + self.ang_integral * self.ang_ki)
413 * ang_mask;
414 #[cfg(feature = "dim3")]
415 let angvel = (pose_errors.angular * self.pd.ang_kp
416 + vel_errors.angular * self.pd.ang_kd
417 + self.ang_integral * self.ang_ki)
418 * ang_mask;
419
420 RigidBodyVelocity { linvel, angvel }
421 }
422}