Skip to main content

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.

info

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. 6060 for a timestep of 1/601 / 60 seconds). The derivative gain is applied to the velocity errors and is usually set in [0,1][0, 1], where 00 means no damping and 11 means that the velocity errors are corrected within a single timestep.

The PID controller is the R3PidController object, created by r3NewPidController and freed by r3FreePidController. It is created with a proportional gain of 6060, an integral gain of 11, and a derivative gain of 0.80.8, 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 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 simulation (with r3PidController_SetAxes, from a combination of the R3_AXES_MASK_* bits, the current ones being given by r3PidController_Axes):

// 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);
note

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.