pid_controller
It is generally not recommended to move a rigid-body by setting its pose directly: teleporting it would ignore every obstacle on the way. The recommended alternative is generally to push it with a force (or an impulse) that is strong enough to reach the target. However, pushing with a single constant force/impulse will generally overshoot the target. Thus, ideally, the force or impulse should be carefully selected and updated each frame as the rigid-body gets closer to its target.
This is what a PID controller (Proportional-Integral-Derivative) is designed to calculate: given the target pose, it computes the ideal velocity change bringing the body closer to it. This is the building block of the velocity-based character controllers, but it is useful for anything that must follow a target without being teleported: a dynamic moving platform, an object held by the player, a following camera, etc.
The gains of the controller are what makes it reach its target quickly or smoothly. The proportional gain is applied to the position errors and is usually set to a multiple of the inverse of the timestep length (e.g. for a timestep of seconds). The derivative gain is applied to the velocity errors and is usually set in , where means no damping and means that the velocity errors are corrected within a single timestep.
The PID controller is the PidController component, added to the entity of a (non-fixed)
rigid-body. Its target field is a PidTarget holding the world-space pose (and optionally the velocities) the
rigid-body must be driven toward. Before each simulation step, the plugin computes the velocity correction bringing
the rigid-body closer to its target, and adds it to its velocity. So moving the target is just a matter of modifying
the PidController::target field.
The PID controller is the R3PidController object, created by r3NewPidController and freed by
r3FreePidController. It is created with a proportional gain of , an integral gain of , and a derivative gain
of , on every coordinate axis (all of them being controlled). Its gains are set per coordinate axis with
r3PidController_SetGains, from an R3PidGains structure (its current gains being given by r3PidController_Gains).
At each frame, r3PidController_RigidBodyCorrection computes the velocity correction bringing a rigid-body closer to
its target pose (and target velocities): it is up to you to add it to the velocities of that rigid-body before the
next r3Step.
The PID controller is the PidController class. It is created with a proportional gain of , an integral gain of
, and a derivative gain of , on every coordinate axis (all of them being controlled), unless other gains are
given to the Kp, Ki, and Kd arguments of its constructor (either a single float for every linear and angular
coordinate axis, or one gain per coordinate axis). They can be read and modified afterwards, per coordinate axis, with
its lin_kp, ang_kp, lin_ki, ang_ki, lin_kd, and ang_kd properties (each one is a Vec3, and can be set
from a vector or from a single float for every axis). At each frame, PidController.rigid_body_correction computes
the velocity correction (a PidCorrection, with its linear and angular parts) bringing a rigid-body closer to its
target pose (and to its target velocities, given as an optional RigidBodyVelocity to its target_vels argument,
zero by default): it is up to you to add it to the linvel and angvel of that rigid-body before the next
PhysicsWorld.step.
The coordinate axes (linear and/or angular) controlled by the controller can be selected in order, for example, to
only control the translations of a body while leaving its rotations to the simulationaxes field, an
AxesMask)r3PidController_SetAxes, from a combination of the R3_AXES_MASK_* bits, the current ones being given by r3PidController_Axes)axes argument of its constructor or its axes property, a combination of the AxesMask flags)
- Example 2D
- Example 3D
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let axes = AxesMask::LIN_X | AxesMask::LIN_Y;
let mut pid = PidController::new(60.0, 0.0, 0.8, axes);
let target = Vector::new(3.0, 2.0);
for _ in 0..200 {
let dt = world.integration_parameters.dt;
let body = &mut world.bodies[body_handle];
// The correction is the velocity change bringing the body closer to its target pose.
let correction = pid.rigid_body_correction(
dt,
body,
Pose::from_translation(target),
RigidBodyVelocity::zero(),
);
let new_velocities = *body.vels() + correction;
body.set_vels(new_velocities, true);
world.step();
}
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let axes = AxesMask::LIN_X | AxesMask::LIN_Y | AxesMask::LIN_Z;
let mut pid = PidController::new(60.0, 0.0, 0.8, axes);
let target = Vector::new(3.0, 2.0, 0.0);
for _ in 0..200 {
let dt = world.integration_parameters.dt;
let body = &mut world.bodies[body_handle];
// The correction is the velocity change bringing the body closer to its target pose.
let correction = pid.rigid_body_correction(
dt,
body,
Pose::from_translation(target),
RigidBodyVelocity::zero(),
);
let new_velocities = *body.vels() + correction;
body.set_vels(new_velocities, true);
world.step();
}
- Example 2D
- Example 3D
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let pid = world.createPidController(60.0, 0.0, 0.8, RAPIER.PidAxesMask.AllLin);
let target = { x: 3.0, y: 2.0 };
for (let k = 0; k < 200; ++k) {
// The correction is applied to the velocity of the rigid-body.
pid.applyLinearCorrection(body, target, { x: 0.0, y: 0.0 });
world.step();
}
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let pid = world.createPidController(60.0, 0.0, 0.8, RAPIER.PidAxesMask.AllLin);
let target = { x: 3.0, y: 2.0, z: 0.0 };
for (let k = 0; k < 200; ++k) {
// The correction is applied to the velocity of the rigid-body.
pid.applyLinearCorrection(body, target, { x: 0.0, y: 0.0, z: 0.0 });
world.step();
}
- Example 2D
- Example 3D
fn setup_physics(mut commands: Commands) {
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let pid = PidController::new(60.0, 0.0, 0.8, AxesMask::LIN_AXES)
.with_target(PidTarget::from_translation(Vec2::new(300.0, 200.0)));
commands.spawn((
Transform::from_xyz(0.0, 100.0, 0.0),
RigidBody::Dynamic,
Collider::ball(50.0),
pid,
));
}
/* Move the target of the controller inside of a system. */
fn update_target(time: Res<Time>, mut controllers: Query<&mut PidController>) {
let t = time.elapsed_secs();
for mut controller in controllers.iter_mut() {
// The plugin drives the rigid-body toward this pose before each simulation step.
controller.target = PidTarget::from_translation(Vec2::new(300.0 * t.cos(), 200.0));
}
}
fn setup_physics(mut commands: Commands) {
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
let pid = PidController::new(60.0, 0.0, 0.8, AxesMask::LIN_AXES)
.with_target(PidTarget::from_translation(Vec3::new(3.0, 2.0, 0.0)));
commands.spawn((
Transform::from_xyz(0.0, 1.0, 0.0),
RigidBody::Dynamic,
Collider::ball(0.5),
pid,
));
}
/* Move the target of the controller inside of a system. */
fn update_target(time: Res<Time>, mut controllers: Query<&mut PidController>) {
let t = time.elapsed_secs();
for mut controller in controllers.iter_mut() {
// The plugin drives the rigid-body toward this pose before each simulation step.
controller.target = PidTarget::from_translation(Vec3::new(3.0 * t.cos(), 2.0, 0.0));
}
}
- Example 2D
- Example 3D
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
R2PidController *pid = r2NewPidController();
R2PidGains gains = r2PidController_Gains(pid);
gains.lin_kp = r2Vector(60.0, 60.0);
gains.lin_ki = r2Vector(0.0, 0.0);
gains.lin_kd = r2Vector(0.8, 0.8);
r2PidController_SetGains(pid, gains);
r2PidController_SetAxes(pid, R2_AXES_MASK_LIN_X | R2_AXES_MASK_LIN_Y);
R2Vector target = r2Vector(3.0, 2.0);
for (int i = 0; i < 200; i++) {
R2Real dt = r2TimeStep(world);
// The correction is the velocity change bringing the body closer to its target pose.
R2VelocityCorrection correction = r2PidController_RigidBodyCorrection(
pid, dt, body_handle,
r2TranslationPose(target), // The target pose.
r2Vector(0.0, 0.0), // The target linear velocity.
0.0); // The target angular velocity.
R2Vector linvel = r2VectorAdd(r2RigidBody_Linvel(body_handle), correction.linear);
R2AngVector angvel = r2RigidBody_Angvel(body_handle) + correction.angularVelocity;
r2RigidBody_SetLinvel(body_handle, linvel, 1);
r2RigidBody_SetAngvel(body_handle, angvel, 1);
r2Step(world, NULL, NULL);
}
r2FreePidController(pid);
// The proportional, integral, and derivative gains of the controller, acting on the linear
// axes only: the body is pushed toward its target without its rotation being controlled.
R3PidController *pid = r3NewPidController();
R3PidGains gains = r3PidController_Gains(pid);
gains.lin_kp = r3Vector(60.0, 60.0, 60.0);
gains.lin_ki = r3Vector(0.0, 0.0, 0.0);
gains.lin_kd = r3Vector(0.8, 0.8, 0.8);
r3PidController_SetGains(pid, gains);
r3PidController_SetAxes(pid, R3_AXES_MASK_LIN_X | R3_AXES_MASK_LIN_Y | R3_AXES_MASK_LIN_Z);
R3Vector target = r3Vector(3.0, 2.0, 0.0);
for (int i = 0; i < 200; i++) {
R3Real dt = r3TimeStep(world);
// The correction is the velocity change bringing the body closer to its target pose.
R3VelocityCorrection correction = r3PidController_RigidBodyCorrection(
pid, dt, body_handle,
r3TranslationPose(target), // The target pose.
r3Vector(0.0, 0.0, 0.0), // The target linear velocity.
r3Vector(0.0, 0.0, 0.0)); // The target angular velocity.
R3Vector linvel = r3VectorAdd(r3RigidBody_Linvel(body_handle), correction.linear);
R3AngVector angvel = r3VectorAdd(r3RigidBody_Angvel(body_handle), correction.angularVelocity);
r3RigidBody_SetLinvel(body_handle, linvel, 1);
r3RigidBody_SetAngvel(body_handle, angvel, 1);
r3Step(world, NULL, NULL);
}
r3FreePidController(pid);
# The proportional, integral, and derivative gains of the controller, acting on the linear
# axes only: the body is pushed toward its target without its rotation being controlled.
axes = rp.AxesMask.LIN_X | rp.AxesMask.LIN_Y | rp.AxesMask.LIN_Z
pid = rp.PidController(axes=axes, Kp=60.0, Ki=0.0, Kd=0.8)
target = rp.Isometry3.from_translation(3.0, 2.0, 0.0)
for _ in range(200):
dt = world.integration_parameters.dt
body = world.rigid_bodies[body_handle]
# The correction is the velocity change bringing the body closer to its target pose
# (and to its target velocities, zero here).
correction = pid.rigid_body_correction(dt, body, target, target_vels=rp.RigidBodyVelocity())
body.linvel = body.linvel + correction.linear
body.angvel = body.angvel + correction.angular
world.step()
The integral part of the controller accumulates the position errors of the previous timesteps, which is what allows it
to compensate a permanent perturbation (e.g. the gravity applied to a hovering body). This is also what makes its API
mutable, and what has to be reset with PidController::reset_integrals whenever the controller is given a target it
never had a chance to reach. The PdController is the variant without that integral part: its API is immutable, and
its behavior is generally good enough for games.
The integral part of the controller accumulates the position errors of the previous timesteps, which is what allows it
to compensate a permanent perturbation (e.g. the gravity applied to a hovering body). These accumulated errors are
stored in the PidController::lin_integral and PidController::ang_integral fields updated by the plugin, and have
to be reset with PidController::reset_integrals whenever the controller is given a target it never had a chance to
reach. The PdController component is the variant without that integral part, and its behavior is generally good
enough for games. Use either one or the other on a given entity, but not both.
The integral part of the controller accumulates the position errors of the previous timesteps, which is what allows it
to compensate a permanent perturbation (e.g. the gravity applied to a hovering body). This is also what makes
r3PidController_RigidBodyCorrection modify the controller, and what has to be reset with
r3PidController_ResetIntegrals whenever the controller is given a target it never had a chance to reach. The
R3PdController is the variant without that integral part: it is a plain structure (initialized by
r3DefaultPdController) that r3PdController_RigidBodyCorrection doesn't modify, and its behavior is generally good
enough for games.
The integral part of the controller accumulates the position errors of the previous timesteps, which is what allows it
to compensate a permanent perturbation (e.g. the gravity applied to a hovering body). These accumulated errors are
readable with the PidController.lin_integral and PidController.ang_integral properties. This is also what makes
PidController.rigid_body_correction modify the controller, and what has to be reset with PidController.reset
whenever the controller is given a target it never had a chance to reach. The PdController is the variant without
that integral part (its constructor takes no Ki argument): its rigid_body_correction method doesn’t modify it (and
doesn’t need the timestep length), and its behavior is generally good enough for games.