0

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?

Paul R
  • 208,748
  • 37
  • 389
  • 560
T. Lane
  • 13
  • 3
  • Break down the fixed point conversions into separate steps and look at the intermediate values. Strongly suspect that's were you will find the reduction in precision. – doug Nov 15 '16 at 22:47
  • 2
    Why such a crude approximation to pi in the radians conversions? The closest double to pi is 3.141592653589793 – Patricia Shanahan Nov 15 '16 at 22:50
  • If you can only move NSEW why are you using float at all? – stark Nov 15 '16 at 23:06
  • @stark You can move in more than 4 directions. You can change which direction is being faced with 15 degree intervals using keys A and D; sorry if I didn't make it clearer :) – T. Lane Nov 15 '16 at 23:09
  • @PatriciaShanahan Good point, although I'm not sure that's the issue. I'll rectify it in the morning but it shouldn't make so much of a difference. – T. Lane Nov 15 '16 at 23:11
  • @doug Thanks, I'll try that tomorrow :) – T. Lane Nov 15 '16 at 23:11
  • To reinforce the point by Patricia Shanahan: The stated reason that you changed to a fixed point library, "The cosine of 90 degrees returned in radians was 0.00775146, rather than 0;" is because your conversion factor from degree to radians was that much off. -- Use a named constant for `Pi` (instead of copying some magic number everywhere) with some more digits (there exist web pages with millions of digits), or use `Pi=4*atan(1)`. – Lutz Lehmann Nov 16 '16 at 11:01

1 Answers1

1

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);
samgak
  • 23,944
  • 4
  • 60
  • 82
  • Thanks, I wasn't aware of the error accumulation! I opted for fixed-point versions mostly down to the small arithmetic issues with floating point to see if it would help fix the problem; and it did seem to, a little bit, but obviously the issue hadn't disappeared completely. Thanks again for the answer! I'll implement your solution tomorrow as it's late where I am currently. I'll get back and accept your answer then :) – T. Lane Nov 15 '16 at 23:27
  • @T.Lane You are going to have to accept some rounding error, because numbers like pi simply don't have exact fixed point values. The trick is to limit accumulation of errors, especially errors in the same direction. – Patricia Shanahan Nov 15 '16 at 23:35
  • Yes, the player's x and y are both integer values. Your suggestion about maintaining 2 doubles would have never occurred to me, so many thanks! – T. Lane Nov 15 '16 at 23:51
  • @PatriciaShanahan Thanks for the info, I'll bear error accumulation in mind whenever I deal with floating point variables in future! – T. Lane Nov 15 '16 at 23:53
  • @samgak Thank you so much for helping me with this! Your final suggestion was what fixed it, although I did implement your other suggestions as well to be sure. Cheers! – T. Lane Nov 16 '16 at 18:46