0

What I'm trying to do is make an object in 2D that is rotated a certain degrees, A degrees, for example, rotate to face the mouse, whose direction FROM that object is B degrees. This is in openGl.

I'm already able to instantly rotate the object, using the glRotatef function, but what I wan't to do is to be able to control the rotation over a certain number of seconds.

I'm using two methods which either increase, or decrease the rotation:

    void GameObject::increaseRot(int millis) {
        rotation += getRotDeg(millis);
    }

    void GameObject::decreaseRot(int millis) {
        rotation -= getRotDeg(millis);
    }

    double GameObject::getRotDeg(int millis) {
        double rot = 360 / this->rotSpeed;
        rot = rot * millis / 1000.0;
        return rot;
    }

Millis comes from a timer which works properly, and so I can cause an object to rotate at a speed of 360 degrees every rotSpeed seconds.

Edit: I've found a solution on the internet, that seems to mostly work. Using the formula of that solution with my own code, the code is

    shortest_angle=((((end - start) % 360) + 540) % 360) - 180;

    /*  check which way to rotate
     *  this part of the code appears to work fine, all it does is
     *    rotate a certain number of degrees, it's my code that I've been
     *   using the whole time
     */
    if(rotateA < 0)
        game.getPlayer().decreaseRot(deltaT);
    else if(rotateA > 0)
        game.getPlayer().increaseRot(deltaT);

However, the code still takes the longer route at certain values, and I can't figure out why . . .

The value I've noticed this happening are:

    45 trying for 135
    225 trying for 315
    315 trying for 45

These are approximate values of course, any values around those areas will screw up. I've been thinking that it's something to do with the limits 90, 180, 270 and 360/0, but I can't figure out what the actual problem is.

Astantos
  • 117
  • 1
  • 12
  • I can't walk you through it, but if you haven't found it yet you might want to be looking at `slerp` - spherical linear interpolation. – Jay Apr 10 '17 at 03:29
  • I considered looking at slerp but stuff like that, and quaternions, are kind of hard for me to understand at this moment. I've tried using them on unity with limited success, but the solution I found online (above) is SO close to working properly that I'm hoping I can fix it up somehow – Astantos Apr 10 '17 at 03:46

2 Answers2

0

I have found and worked out the solution to this question, for anyone else that might also be having the same problem.

Using the code from "user151496", Rotation Interpolation

      shortest_angle=((((end - start) % 360) + 540) % 360) - 180;
      return shortest_angle * amount;

You can change it to apply your own custom rotation speed as I have using functions such as

    if(rotateA < 0 && rotateA >= -180)
        game.getPlayer().decreaseRot(deltaT);
    else if (rotateA < 0 && rotateA <= -180)    //for the crossing of the boundary
        game.getPlayer().increaseRot(deltaT);
    else if(rotateA > 0 && rotateA <= 180)
        game.getPlayer().increaseRot(deltaT);
    else if(rotateA > 0 && rotateA >= 180)
        game.getPlayer().decreaseRot(deltaT);   //for the crossing of the boundary

All this ^ does is check whether the shortest_angle is positive or negative (rotateA). Then it either rotates clockwise (decreaseRot) or counterclockwise(increaseRot) accordingly. Note, however, the two extra lines, checking the -180 and 180 degrees conditions. These are necessary - I find it hard to explain numerically but it has to do with crossing the 0/360 degrees line.

If you dont have those extra conditions, the rotation will rotate on the larger angle whenever you have to cross such a boundary.

Astantos
  • 117
  • 1
  • 12
0

It's possible to interpolate angles without using any conditional branching like so:

template <typename T>
T lerp_fixed_degrees(T a, T b, T x) {
    T d = wrap_degrees(b - a);
    return wrap_degrees(a + std::copysign(std::fmin(std::abs(x), std::abs(d)), d));
}
  • Calculate the difference between a and b over the range [-180, 180) using wrap_degrees(b - a). The function wrap_degrees calculates the equivalent angle in the range [-180, 180) (e.g. wrap_degrees(190) == -170 and wrap_degrees(-190) == 170).
  • Determine the smaller of the step size and the difference using std::fmin(std::abs(x), std::abs(d)). This prevents us overshooting the target (you can use x instead of std::abs(x) if you know x is positive, but I opted for robustness here).
  • Use std::copysign to interpolate in the correct direction.
  • Add the result to the starting angle and return the equivalent angle in the range [-180, 180).

The function wrap_degrees is implemented like so:

template <typename T>
T wrap_degrees(T x) {
    return fmod_floor(x + T(180), T(360)) - T(180);
}
  • Add 180 to the input value.
  • Wrap the value in the range [0, 360) using fmod_floor. The fmod_floor function is like std::fmod, except it produces the remainder of floored division, which always has the same sign as the divisor. This is the behaviour we want (e.g. fmod(-10, 360) == -10 while fmod_floor(-10, 360) == 350).
  • Shift the wrapped value back to the range [-180, 180) by subtracting 180 (this is balanced by the 180 added previously).

The fmod_floor function is implemented like so:

template <typename T>
T fmod_floor(T a, T n) {
    return std::fmod(std::fmod(a, n) + n, n);
}
  • Ensure value is in the range (-n, n) with std::fmod.
  • Shift the value into the range (0, 2n).
  • Ensure the value is in the range [0, n) with std::fmod again.

Here is a quick demo:

double a = 90.;
double prev;
do {
    std::cout << a << "\n";
    prev = a;
    a = lerp_fixed_degrees(a, -100., 45.);
} while (a != prev);

Output:

90
135
-180
-135
-100

You can also perform straightforward linear interpolation using a value from 0 to 1 like so:

template <typename T>
T lerp_degrees(T a, T b, T t) {
    return wrap_degrees(a + wrap_degrees(b - a) * t);
}

I think this one is pretty self-explanatory.

Joseph Thomson
  • 9,888
  • 1
  • 34
  • 38