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