rapier3d/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#[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<Real>,
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<Real>,
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<Real>,
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<Real>,
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#[derive(Debug, Copy, Clone, PartialEq)]
55pub struct PidController {
56 /// The Proportional-Derivative (PD) part of this PID controller.
57 pub pd: PdController,
58 /// The translational error accumulated through time for the Integral part of the PID controller.
59 pub lin_integral: Vector<Real>,
60 /// The angular error accumulated through time for the Integral part of the PID controller.
61 pub ang_integral: AngVector<Real>,
62 /// The linear gain applied to the Integral part of the PID controller.
63 pub lin_ki: Vector<Real>,
64 /// The angular gain applied to the Integral part of the PID controller.
65 pub ang_ki: AngVector<Real>,
66}
67
68impl Default for PidController {
69 fn default() -> Self {
70 Self::new(60.0, 1.0, 0.8, AxesMask::all())
71 }
72}
73
74/// Position or velocity errors measured for PID control.
75pub struct PdErrors {
76 /// The linear (translational) part of the error.
77 pub linear: Vector<Real>,
78 /// The angular (rotational) part of the error.
79 pub angular: AngVector<Real>,
80}
81
82impl From<RigidBodyVelocity> for PdErrors {
83 fn from(vels: RigidBodyVelocity) -> Self {
84 Self {
85 linear: vels.linvel,
86 angular: vels.angvel,
87 }
88 }
89}
90
91impl PdController {
92 /// Initialized the PD controller with uniform gain.
93 ///
94 /// The same gain are applied on all axes. To configure per-axes gains, construct
95 /// the [`PdController`] by setting its fields explicitly instead.
96 ///
97 /// Only the axes specified in `axes` will be enabled (but the gain values are set
98 /// on all axes regardless).
99 pub fn new(kp: Real, kd: Real, axes: AxesMask) -> PdController {
100 #[cfg(feature = "dim2")]
101 return Self {
102 lin_kp: Vector::repeat(kp),
103 lin_kd: Vector::repeat(kd),
104 ang_kp: kp,
105 ang_kd: kd,
106 axes,
107 };
108
109 #[cfg(feature = "dim3")]
110 return Self {
111 lin_kp: Vector::repeat(kp),
112 lin_kd: Vector::repeat(kd),
113 ang_kp: AngVector::repeat(kp),
114 ang_kd: AngVector::repeat(kd),
115 axes,
116 };
117 }
118
119 /// Calculates the linear correction from positional and velocity errors calculated automatically
120 /// from a rigid-body and the desired positions/velocities.
121 ///
122 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
123 /// the inverse of the simulation step so the returned value is a linear rigid-body velocity
124 /// change.
125 pub fn linear_rigid_body_correction(
126 &self,
127 rb: &RigidBody,
128 target_pos: Point<Real>,
129 target_linvel: Vector<Real>,
130 ) -> Vector<Real> {
131 self.rigid_body_correction(
132 rb,
133 Isometry::from(target_pos),
134 RigidBodyVelocity {
135 linvel: target_linvel,
136 #[allow(clippy::clone_on_copy)]
137 angvel: rb.angvel().clone(),
138 },
139 )
140 .linvel
141 }
142
143 /// Calculates the angular correction from positional and velocity errors calculated automatically
144 /// from a rigid-body and the desired positions/velocities.
145 ///
146 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
147 /// the inverse of the simulation step so the returned value is an angular rigid-body velocity
148 /// change.
149 pub fn angular_rigid_body_correction(
150 &self,
151 rb: &RigidBody,
152 target_rot: Rotation<Real>,
153 target_angvel: AngVector<Real>,
154 ) -> AngVector<Real> {
155 self.rigid_body_correction(
156 rb,
157 Isometry::from_parts(na::one(), target_rot),
158 RigidBodyVelocity {
159 linvel: *rb.linvel(),
160 angvel: target_angvel,
161 },
162 )
163 .angvel
164 }
165
166 /// Calculates the linear and angular correction from positional and velocity errors calculated
167 /// automatically from a rigid-body and the desired poses/velocities.
168 ///
169 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
170 /// the inverse of the simulation step so the returned value is a rigid-body velocity
171 /// change.
172 pub fn rigid_body_correction(
173 &self,
174 rb: &RigidBody,
175 target_pose: Isometry<Real>,
176 target_vels: RigidBodyVelocity,
177 ) -> RigidBodyVelocity {
178 let pose_errors = RigidBodyPosition {
179 position: rb.pos.position,
180 next_position: target_pose,
181 }
182 .pose_errors(rb.local_center_of_mass());
183 let vels_errors = target_vels - rb.vels;
184 self.correction(&pose_errors, &vels_errors.into())
185 }
186
187 /// Mask where each component is 1.0 or 0.0 depending on whether
188 /// the corresponding linear axis is enabled.
189 fn lin_mask(&self) -> Vector<Real> {
190 #[cfg(feature = "dim2")]
191 return Vector::new(
192 self.axes.contains(AxesMask::LIN_X) as u32 as Real,
193 self.axes.contains(AxesMask::LIN_Y) as u32 as Real,
194 );
195 #[cfg(feature = "dim3")]
196 return Vector::new(
197 self.axes.contains(AxesMask::LIN_X) as u32 as Real,
198 self.axes.contains(AxesMask::LIN_Y) as u32 as Real,
199 self.axes.contains(AxesMask::LIN_Z) as u32 as Real,
200 );
201 }
202
203 /// Mask where each component is 1.0 or 0.0 depending on whether
204 /// the corresponding angular axis is enabled.
205 fn ang_mask(&self) -> AngVector<Real> {
206 #[cfg(feature = "dim2")]
207 return self.axes.contains(AxesMask::ANG_Z) as u32 as Real;
208 #[cfg(feature = "dim3")]
209 return Vector::new(
210 self.axes.contains(AxesMask::ANG_X) as u32 as Real,
211 self.axes.contains(AxesMask::ANG_Y) as u32 as Real,
212 self.axes.contains(AxesMask::ANG_Z) as u32 as Real,
213 );
214 }
215
216 /// Calculates the linear and angular correction from the given positional and velocity errors.
217 ///
218 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
219 /// the inverse of the simulation step so the returned value is a rigid-body velocity
220 /// change.
221 pub fn correction(&self, pose_errors: &PdErrors, vel_errors: &PdErrors) -> RigidBodyVelocity {
222 let lin_mask = self.lin_mask();
223 let ang_mask = self.ang_mask();
224
225 RigidBodyVelocity {
226 linvel: (pose_errors.linear.component_mul(&self.lin_kp)
227 + vel_errors.linear.component_mul(&self.lin_kd))
228 .component_mul(&lin_mask),
229 #[cfg(feature = "dim2")]
230 angvel: (pose_errors.angular * self.ang_kp + vel_errors.angular * self.ang_kd)
231 * ang_mask,
232 #[cfg(feature = "dim3")]
233 angvel: (pose_errors.angular.component_mul(&self.ang_kp)
234 + vel_errors.angular.component_mul(&self.ang_kd))
235 .component_mul(&ang_mask),
236 }
237 }
238}
239
240impl PidController {
241 /// Initialized the PDI controller with uniform gain.
242 ///
243 /// The same gain are applied on all axes. To configure per-axes gains, construct
244 /// the [`PidController`] by setting its fields explicitly instead.
245 ///
246 /// Only the axes specified in `axes` will be enabled (but the gain values are set
247 /// on all axes regardless).
248 pub fn new(kp: Real, ki: Real, kd: Real, axes: AxesMask) -> PidController {
249 #[cfg(feature = "dim2")]
250 return Self {
251 pd: PdController::new(kp, kd, axes),
252 lin_integral: na::zero(),
253 ang_integral: na::zero(),
254 lin_ki: Vector::repeat(ki),
255 ang_ki: ki,
256 };
257
258 #[cfg(feature = "dim3")]
259 return Self {
260 pd: PdController::new(kp, kd, axes),
261 lin_integral: na::zero(),
262 ang_integral: na::zero(),
263 lin_ki: Vector::repeat(ki),
264 ang_ki: AngVector::repeat(ki),
265 };
266 }
267
268 /// Set the axes errors and corrections are computed for.
269 ///
270 /// This doesn’t modify any of the gains.
271 pub fn set_axes(&mut self, axes: AxesMask) {
272 self.pd.axes = axes;
273 }
274
275 /// Get the axes errors and corrections are computed for.
276 pub fn axes(&self) -> AxesMask {
277 self.pd.axes
278 }
279
280 /// Resets to zero the accumulated linear and angular errors used by
281 /// the Integral part of the controller.
282 pub fn reset_integrals(&mut self) {
283 self.lin_integral = na::zero();
284 self.ang_integral = na::zero();
285 }
286
287 /// Calculates the linear correction from positional and velocity errors calculated automatically
288 /// from a rigid-body and the desired positions/velocities.
289 ///
290 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
291 /// the inverse of the simulation step so the returned value is a linear rigid-body velocity
292 /// change.
293 ///
294 /// This method is mutable because of the need to update the accumulated positional
295 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
296 /// an immutable API is needed.
297 pub fn linear_rigid_body_correction(
298 &mut self,
299 dt: Real,
300 rb: &RigidBody,
301 target_pos: Point<Real>,
302 target_linvel: Vector<Real>,
303 ) -> Vector<Real> {
304 self.rigid_body_correction(
305 dt,
306 rb,
307 Isometry::from(target_pos),
308 RigidBodyVelocity {
309 linvel: target_linvel,
310 #[allow(clippy::clone_on_copy)]
311 angvel: rb.angvel().clone(),
312 },
313 )
314 .linvel
315 }
316
317 /// Calculates the angular correction from positional and velocity errors calculated automatically
318 /// from a rigid-body and the desired positions/velocities.
319 ///
320 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
321 /// the inverse of the simulation step so the returned value is an angular rigid-body velocity
322 /// change.
323 ///
324 /// This method is mutable because of the need to update the accumulated positional
325 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
326 /// an immutable API is needed.
327 pub fn angular_rigid_body_correction(
328 &mut self,
329 dt: Real,
330 rb: &RigidBody,
331 target_rot: Rotation<Real>,
332 target_angvel: AngVector<Real>,
333 ) -> AngVector<Real> {
334 self.rigid_body_correction(
335 dt,
336 rb,
337 Isometry::from_parts(na::one(), target_rot),
338 RigidBodyVelocity {
339 linvel: *rb.linvel(),
340 #[allow(clippy::clone_on_copy)]
341 angvel: target_angvel.clone(),
342 },
343 )
344 .angvel
345 }
346
347 /// Calculates the linear and angular correction from positional and velocity errors calculated
348 /// automatically from a rigid-body and the desired poses/velocities.
349 ///
350 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
351 /// the inverse of the simulation step so the returned value is a rigid-body velocity
352 /// change.
353 ///
354 /// This method is mutable because of the need to update the accumulated positional
355 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
356 /// an immutable API is needed.
357 pub fn rigid_body_correction(
358 &mut self,
359 dt: Real,
360 rb: &RigidBody,
361 target_pose: Isometry<Real>,
362 target_vels: RigidBodyVelocity,
363 ) -> RigidBodyVelocity {
364 let pose_errors = RigidBodyPosition {
365 position: rb.pos.position,
366 next_position: target_pose,
367 }
368 .pose_errors(rb.local_center_of_mass());
369 let vels_errors = target_vels - rb.vels;
370 self.correction(dt, &pose_errors, &vels_errors.into())
371 }
372
373 /// Calculates the linear and angular correction from the given positional and velocity errors.
374 ///
375 /// The unit of the returned value depends on the gain values. In general, `kd` is proportional to
376 /// the inverse of the simulation step so the returned value is a rigid-body velocity
377 /// change.
378 ///
379 /// This method is mutable because of the need to update the accumulated positional
380 /// errors for the Integral part of this controller. Prefer the [`PdController`] instead if
381 /// an immutable API is needed.
382 pub fn correction(
383 &mut self,
384 dt: Real,
385 pose_errors: &PdErrors,
386 vel_errors: &PdErrors,
387 ) -> RigidBodyVelocity {
388 self.lin_integral += pose_errors.linear * dt;
389 self.ang_integral += pose_errors.angular * dt;
390
391 let lin_mask = self.pd.lin_mask();
392 let ang_mask = self.pd.ang_mask();
393
394 RigidBodyVelocity {
395 linvel: (pose_errors.linear.component_mul(&self.pd.lin_kp)
396 + vel_errors.linear.component_mul(&self.pd.lin_kd)
397 + self.lin_integral.component_mul(&self.lin_ki))
398 .component_mul(&lin_mask),
399 #[cfg(feature = "dim2")]
400 angvel: (pose_errors.angular * self.pd.ang_kp
401 + vel_errors.angular * self.pd.ang_kd
402 + self.ang_integral * self.ang_ki)
403 * ang_mask,
404 #[cfg(feature = "dim3")]
405 angvel: (pose_errors.angular.component_mul(&self.pd.ang_kp)
406 + vel_errors.angular.component_mul(&self.pd.ang_kd)
407 + self.ang_integral.component_mul(&self.ang_ki))
408 .component_mul(&ang_mask),
409 }
410 }
411}