Search code examples
c++mathdoublesdl-2fixed-point

Trigonometry/Floating point issue


The issue:

When using the math.h trigonometry functions with a simple SDL 2.0.4 application (top-down movement/rotation attempt), I discovered that there were some slight errors in the trigonometry calculations, resulting in the 'player' not moving exactly in the direction being faced, which bugged me a lot. I searched up why this could be, and the main reason seems to be that of floating point arithmetic.

I resorted to using a fixed-point maths library called libfixmath - and the problem was solved, but only to some extent. The cosine of 90 degrees returned in radians was 0.00775146, rather than 0; however, a cosine of 270 degrees did return 0 radians! I must admit this problem has got me stuck and I need a bit of help (my mathematics skills aren't great, which doesn't help).

The variables used in fixed-point arithmetic:

double direction = 0.0;
double sinRadians = 0.0;
double cosRadians = 1.0; // presuming player starts facing direction of 0 degrees!

And then the part of 'int main(int argc, char* argv[])' involving these variables:

    if (keyDownW == true)
    {
        Player.setXPos(Player.getXPos() + (10 * sinRadians));
        Player.setYPos(Player.getYPos() - (10 * cosRadians)); // +- = -
        cout << "Cosine and Sine (in radians): " << cosRadians << ", " << sinRadians << endl;
        cout << direction << " degrees \n" << endl;
    }
    else if (keyDownS == true)
    {
        Player.setXPos(Player.getXPos() - (6 * sinRadians));
        Player.setYPos(Player.getYPos() + (6 * cosRadians)); // -- = +
    }
    if (keyDownA == true)
    {
        if (direction <= 0)
        {
            direction = 345;
        }
        else
        {
            direction -= 15;
        }
        direction = direction * (3.14159 / 180); // convert to radians
        cosRadians = fix16_to_dbl(fix16_cos(fix16_from_dbl(direction)));
        sinRadians = fix16_to_dbl(fix16_sin(fix16_from_dbl(direction)));
        direction = direction * (180 / 3.14159); // convert back to degrees
    }
    else if (keyDownD == true)
    {
        if (direction >= 345)
        {
            direction = 0;
        }
        else
        {
            direction += 15;
        }
        direction = direction * (3.14159 / 180); // convert to radians
        cosRadians = fix16_to_dbl(fix16_cos(fix16_from_dbl(direction)));
        sinRadians = fix16_to_dbl(fix16_sin(fix16_from_dbl(direction)));
        direction = direction * (180 / 3.14159); // convert back to degrees
    }

When cosRadians and sinRadians are assigned, what's happening is that direction (which has been converted from degrees to radians) is converted to a fixed-point number, which is then used to calculate the cosine and sine individually, then converted from a fixed-point number back to a double for assignment, all with the use of the libfixmath library.

Here's the program currently (compiled as an .exe with the necessary .dll files; I statically linked the libfixmath library) so you can see the issue for yourself: https://mega.nz/#!iJggRbxY!ySbl-2X_oiJKFACyp_kLg9yuLcEsFM07lTRqLtKCsy4

Any ideas as to why this is happening?


Solution

  • One problem is that you are converting back and forth from degrees to radians and back every frame, and you will lose precision each time. Instead, do the conversion into a temporary variable, like this:

        double direction_radians = direction * (3.14159 / 180); // convert to radians
        cosRadians = fix16_to_dbl(fix16_cos(fix16_from_dbl(direction_radians)));
        sinRadians = fix16_to_dbl(fix16_sin(fix16_from_dbl(direction_radians)));
    

    This way the errors don't accumulate.

    Also, is there any reason you can't just use the normal sin and cos functions from math.h instead of fixed point versions? If this is only being done for the player once per frame it shouldn't be a speed critical calculation.

    One more thing: is the player x and y position stored as an integer or a double? If it's an integer then you won't be able to move in the precise direction you want because you have to move integral amounts each frame. To solve this you can maintain 2 double variables for the x, y position, and add your movement vector on to them each frame. Then set the x and y by converting to int. This will stop you losing the fractional part of your position each time.

    e.g.

    playerX += (10 * sinRadians);
    playerY -= (10 * cosRadians);
    Player.setXPos((int)playerX);
    Player.setYPos((int)playerY);