Dealing with noise and off-center in joystick input

jwatte's picture

A joystick is a nice input device for certain kinds of games, like flight simulators, space games, etc. A mini-version of the joystick is the gamepad, ubiquitous in console games.

Unfortuantely, a joystick is a very messy and noisy kind of input. You want to filter the data you get, so that temporary noise is limited, and you want to allow the user to center the joystick even if the springs don't end up lining it up exactly where the hardware thinks the "center" value is.

Generally, you will end up with a "dead zone" in the center of the controller (say, from -0.1 .. 0.1) where you smash the value to 0. Then you map the range from 0.1 .. 0.85 to 0 .. 1. Anything above 0.85 will be considered "full." This is to compensate for the drifting calibration and mechanical limitations of analog controllers.

If you additionally don't want to support a joystick control that is longer than 1.0 for diagonal control, you end up with something like:

class JoystickFilter
{
  public:
    JoystickFilter() : tx(0), ty(0), x(0), y(0), alpha(0.2f) {}
    void Update(float readX, float readY) {
      tx = tx * (1 - alpha) + readX * alpha;
      ty = ty * (1 - alpha) + readY * alpha;
      if (fabsf(tx) < 0.1f) {
        x = 0;
      }
      else {
        x = (tx > 0) ? (tx - 0.1f) * 1.3333f : (tx + 0.1f) * 1.3333f;
      }
      if (fabsf(ty) < 0.1f) {
        y = 0;
      }
      else {
        y = (ty > 0) ? (ty - 0.1f) * 1.3333f : (ty + 0.1f) * 1.3333f;
      }
      float lenSquared = x*x + y*y;
      if (lenSquared > 1.0f) {
        float div = 1.0f / sqrtf(lenSquared);
        x = x * div;
        y = y * div;
      }
    }
    void GetValue(float &ox, float &oy) const {
      ox = x;
      oy = y;
    }
private:
    float tx, ty;
    float x, y;
    float alpha;
};

The specifics can of course be tweaked -- perhaps 0.2 isn't the right filter value, or perhaps you want to calculate it as the power of 0.01f to the time elapsed since the last update to make it time invariant. Similarly, the 0.1 (for dead zone) and 1.3333 (which is 1.0/(0.85-0.1) for range) can also be tuned to fit the kind of calibration you want to support.