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}