First off i'd like to say that this account was created because my real account "SillyNoob" has completely locked up. Make new thread "Sorry, you can't do that", new PM "Sorry you can't do that", post on my own thread and there's no reply button. There's no PM in my inbox and no email in my registered email account so I don't know what's going on here but whatever it is i'm not laughing

Now onto the actual post. Basically I'm a complete idiot for not being able to solve this simple problem. I have a line and I want that line to rotate by either +X or -X degrees when the user presses a button. If they press Z it rotates minus whatever degrees and if they press X it rotates plus whatever degrees. Simple, right?

Well Mr.Stupid here can't implement it because he's attrocious at math and essentially doesn't know what he's doing. There's no cause for alarm in the code overload department because all my problems are in this simple class member function. The function only deals with the co-ordinates of point 2 since point 1 will always be in the same place.

// sets X/Y values to correspond to a rotation of TurrVector by _ degrees
void TurrVector::Rotate(int x, int y, double angle)
{
    // convert angle given into radians for sine & cosine functions
    double angInRad = (angle*PI)/180;
    double xInRad = (x*cos(angInRad)) - (y*sin(angInRad));
    double yInRad = (x*sin(angInRad)) + (y*cos(angInRad));
    // convert radians back into degrees before assigning new X/Y
    p2[0] += (xInRad*180)/PI;
    p2[1] += (yInRad*180)/PI;
}

I'd like to say that I don't take any credit for the code written in this function because it's essentially a copy/paste job from back when I read this page, didn't understand what they were trying to teach me and took the end product.
[http://freespace.virgin.net/hugo.elias/routines/rotate.htm]

As always I thank any of you who take time out of your lives to read and post here to help out idiots like me :)

Edited 4 Years Ago by ZaaZ

I can't get anything from the link, so it's hard to see the context. From what I can see, though, the function seems wrong. When I plug in 0 for an angle, it seems to me that no values should change (rotating a line by 0 degrees should have it end up as the original line, right?) However, that's not the case here. Cosine of 0 is 1, sine of 0 is 0, so assuming non-zero x and y, the p2 array is going to change, which seems wrong. There is a p1 vector representing the fulcrum (hence p1 does not change) and the line is from p1 to p2 and is re-drawn after the call to this function? If this is the case, it seems that the function should take a constant p1 array and a non-constant p2 array and the angle to rotate, so I'm confused about what x and y are here. Regardless, my earlier point stands. A rotation of 0 degrees should result in no change in p2, but this function changes it?

Edited 4 Years Ago by VernonDozier

Sorry about the broken link. There's an extra ] added on the end for some reason. Just remove it in the URL bar and the page will show.

I'm such a complete idiot. No wonder you're confused with my rubbish code. The x and y arguments were not needed at all! In fact the only thing the function needs is the angle and the point 2 (p2[]) array. What you might want to understand here is that the length of the line (referred to as "turret") is always going to be 20 pixels and that the point 1 co-ordinates will never be changed in the rotation. That's why you don't see p1[] used anywhere.

Just to be extra clear:
p2[0] is the X co-ordinate of point 2.
p2[1] is the Y co-ordinate of point 2.

Here's what I got after removing the useless X and Y values.

// sets X/Y values to correspond to a rotation of TurrVector by _ degrees
void TurrVector::Rotate(double angle)
{
    // convert angle given into radians for sine & cosine functions
    double angInRad = (angle*PI)/180;
    double xInRad = (p2[0]*cos(angInRad)) - (p2[1]*sin(angInRad));
    double yInRad = (p2[0]*sin(angInRad)) + (p2[1]*cos(angInRad));
    // convert radians back into degrees before assigning new X/Y
    p2[0] += (xInRad*180)/PI;
    p2[1] += (yInRad*180)/PI;
}

In addition you're absolutely correct about an angle of 0 degrees changing the position of X and Y. p2[0] (X) goes into the function as 335 and p2[1] (Y) goes in as 400. These are the values of all variables at the end of the function when I plug in 0 to be the angle.

angInRad = 0.00000000000000000
xInRad = 335.00000000000000
yInRad = 400.00000000000000
p2[0] = 19529
p2[1] = 23318

What the ... ! Clearly i've seriously messed this up for such crazy values to come out of an angle of 0! If I can't even rotate a straight line then there's just no hope for me :(

After seeing his link, I see what he's doing. You weren't really following it. Notice there's no adding in his function or in my code below. You're simply changing the x that's already there. Don't add the new values to anything. One key point. Use doubles, not ints, if bpossible, or you're looking at some round-off error that might build exponentially. Second key point. He is rotating around the ORIGIN, so p1[]= {0,0}.

To use his code, do this...

#include <iostream>
#include <cmath>
using namespace std;

const double PI = 3.14159265;

void Rotate(double& x, double& y, double angle)
{
    double angInRad = (angle*PI)/180;
    double NewX = x*cos(angInRad) - y*sin(angInRad);
    double NewY = x*sin(angInRad) + y*cos(angInRad);

    x = NewX;
    y = NewY;
}


int main()
{
    double x = 20;
    double y = 0;

    for(int i = 0; i <= 360; i++)
    {
        cout << i << '\t' << x << '\t' << y << endl;
        Rotate(x, y, 1);
    }

    return 0;
}

See how the numbers slowly go around the circle.

For you to use it, assuming p1 is the origin, you would do this, where p1 is the origin.

Rotate(&p2[0], &p2[1], angle);

Since p1 is NOT the origin, the math is going to be a bit more complicated, but not much. You need to pass an x and y RELATIVE to p1.

double relativeX = p2[0] - p1[0];
double relativeY = p2[1] - p1[1];
Rotate(relativeX, relativeY, angle;
p2[0] = p1[0] + relativeX;
p2[1] = p1[1] + relativeY;

In the original code, it's going to look like this if you're rotating around (35, 25).

#include <iostream>
#include <cmath>
using namespace std;

const double PI = 3.14159265;

void Rotate(double& x, double& y, double angle)
{
    double angInRad = (angle*PI)/180;
    double NewX = x*cos(angInRad) - y*sin(angInRad);
    double NewY = x*sin(angInRad) + y*cos(angInRad);

    x = NewX;
    y = NewY;
}


int main()
{
    const double p1[2] = {35, 25};
    double p2[2] = {p1[0], p1[1] + 20};


    for(int i = 0; i <= 360; i++)
    {
        cout << i << '\t' << p2[0] << '\t' << p2[1] << endl;
        double relativeX = p2[0] - p1[0];
        double relativeY = p2[1] - p1[1];
        Rotate(relativeX, relativeY, 1);
        p2[0] = p1[0] + relativeX;
        p2[1] = p1[1] + relativeY;  
    }

    return 0;
}

You might want to change to pass both points instead of (x,y) and change around the code in other ways, but that's the gyst of things.

There are some serious issues with this. Okay, the line (turret) rotates but the length of the line varies throughout the rotation. Calling the function with an angle of +1 will result in no rotation taking place; the line will not rotate clockwise until an anti-clockwise rotation has been performed. If I rotate long enough the speed of rotation gradually slows until it will no longer rotate in the given direction; although at that point I can still rotate in the opposite direction.

With special importance I want to make it clear that I am working in pixels. The X and Y of both P1 and P2 can NOT take non-integer values. It's impossible for me to draw the line between X.7 pixels, Y.3 pixels and X.4 pixels Y.9 pixels.

In addition to the above I also want to inform you that TurrVector is a class and the function Rotate() is part of that class. It has all of the variables available to it including: p1, p2 and magnitude (length of the line).

Here are all the components of this broken puzzle.

The turret (line) class:

class TurrVector{
friend class Player; // so Player can access all variables & functions in TurrVector
protected:
    TurrVector(); // constructor is private because only player can have a turret
    const int magnitude; // length of line
public:
    int p1[2]; // X/Y of P1
    int p2[2]; // X/Y of P2
    void Rotate(double angle);
};

The player "has" a turret:

class Player
{
public:
    TurrVector turret; // Player has a turret (only object that can have one)
    // member variables
    int x;
    int y;
};

The turret Rotate() function with the problems mentioned in this post.

// sets X/Y values of point 2 to correspond to a rotation of turret by _ degrees
void TurrVector::Rotate(double angle)
{
    // convert angle given into radians for sine & cosine functions
    double angInRad = (angle*PI)/180;
    double relativeX = p2[0] - p1[0];
    double relativeY = p2[1] - p1[1];
    relativeX = (relativeX*cos(angInRad)) - (relativeY*sin(angInRad));
    relativeY = (relativeX*sin(angInRad)) + (relativeY*cos(angInRad));
    p2[0] = p1[0] + relativeX;
    p2[1] = p1[1] + relativeY;
}

I call the member function Rotate() with the angle in degrees when the user presses Z or X. These input checks are performed in a loop which continues as long as player has some lives; although the exit condition doesn't really matter in this scenario.

int main()
{
    while(thePlyr.GetIntVal(Player::HEALTH) != 0){ // until player(user) lives == 0
        // ... lots of other stuff happening ...
        // user turret rotation input checks
        if(Keyboard.isPressed(GwinKeyNames::KZ)){
            thePlyr.turret.Rotate(-1.0);
        }
        if(Keyboard.isPressed(GwinKeyNames::KX)){
            thePlyr.turret.Rotate(1.0);
        }
        // re-drawing everything
   }
}

There is no problem with the re-drawing or clearing of anything. Every other aspect of this program (game) works as intended with exception of this line (turret) rotation.

Here is the raw data from a clockwise rotation of +1 degree.

At start of function P1 X = 335, P1 Y = 435.
At start of function P2 X = 335, P2 Y = 415.

After:

angInRad = 0.017453292522222223
relativeX = 0.34904812879124181
relativeY = -19.990862173316394
P1 unchanged.
P2 X = 335, p2 Y = 415

So...nothing has changed because integer assignment of a float chops off all values after decimal point. That's no surprise but what can be done about this? Now onto the much more interesting anti-clockwise rotation of -1 degree.

At start of function P1 X = 335, P1 Y = 435.
At start of function P2 X = 335, P2 Y = 415.

After:

angInRad = -0.017453292522222223
relativeX = -0.34904812879124181
relativeY = -19.990862173316394
P1 unchanged.
P2 X = 334, p2 Y = 415

You have an error with this line:

relativeX = (relativeX*cos(angInRad)) - (relativeY*sin(angInRad));
relativeY = (relativeX*sin(angInRad)) + (relativeY*cos(angInRad));

because you set the relativeX value and then use it again in the next line. You need to preserve the original value of relativeX until you have computed relativeY. This is probably what explains the weirdness happening when you do a lot of rotations. So, you should do:

double newRelativeX = (relativeX*cos(angInRad)) - (relativeY*sin(angInRad));
relativeY = (relativeX*sin(angInRad)) + (relativeY*cos(angInRad));
relativeX = newRelativeX;

As for your problem with +1 and -1 rotations, this is basically a problem with truncation vs rounding-off. Conversing a double to an integer will truncate the decimal part, not round it off. This means that, in the case of +1 rotation, you will get X: 335.35 -> X: 335, while in the case of -1 rotation, you will get X: 334.65 -> X: 334. If you care about this one pixel difference, then you should use a round-off of the values (the way to do rounding in C++ is to use std::floor(x + 0.5)):

p2[0] = std::floor(p1[0] + relativeX + 0.5);
p2[1] = std::floor(p1[1] + relativeY + 0.5);

I think you should store the values as doubles, do the calculations as doubles, and then convert those doubles to integers only when you're actually doing the drawing since you need integers to draw. Storing and calculating as integers very often results in the compounding rounding errors I mentioned.

Another option is to store the absolute angle. "Rotating" means incrementing or decrementing the absolute angle by the "angle" parameter, then calculating p2 from p1, the absolute angle, and the magnitude. p1 and p2 remain as integers and the compunding round off errors go away.

void TurrVector::Rotate(double angle)
{
    // if angle is positive, rotating counter-clockwise.  if negative, clockwise
    absoluteAngle += (angle / PI); // absoluteAngle is a double stored in radians, angle represents degrees
    p2[0] = (int)(p1[0] + magnitude * cos(absoluteAngle));
    p2[1] = (int)(p1[1] + magnitude - sin(absoluteAngle)); // minus instead of + because unlike in math, when drawing, values DECREASE when you go up    
}

Edited 4 Years Ago by VernonDozier: add typecasts

Thank you thank you thank you thank you! That's the most beautiful thing i've ever programmed! :) Watching that line go round and round in a circle, ah, it's just beautiful! .. annnd I can't take any credit for it. LOL :)

Being so excited over being able to finally make a line spin in a circle after a week of trial and error with help from expert programmers. Well, there goes my confidence... argh, whatever I can't be all doom and gloom because the important thing is that it works. I'm not going to try and coax any of you into telling me why or how it works but i'm just greatful that it finally really actually works! (I'm sure that attitude will get me far as a programmer, lol).

Just a few things before I wrap this thread up. First, even after I changed the ridiculously obvious

relativeX = (relativeXcos(angInRad)) - (relativeYsin(angInRad));
relativeY = (relativeXsin(angInRad)) + (relativeYcos(angInRad));

error it didn't make much of a difference to the behaviour of the rotation. It was only after I fixed both the relativeX problem, the truncation problem and stored the precise double value for each calculation that it changed and became "magically" working correctly.

It must have been a combination of all the errors that caused such wild results when rotating just + or - 1 degrees. The float/integer conversion problem was massive compared to how bad I thought it would be. This was probably because the loop cycles so fast that hitting Z or X even as fast as possible will count at least 5 or 6 times so each real world key press actually moves the line 6 times (degrees).

When you're calling something 360 times within a few seconds even the tiniest loss of data has a huge impact. I'll deffinitely be much more careful with my mixed data types in assignment operations in the future.

Here's the beauty:

// rotates turret (line) by _ degrees
void TurrVector::Rotate(double angle)
{
    // convert angle given into radians for sine & cosine functions
    double angInRad = (angle*PI)/180;
    double relativeX = absoluteP2[0] - p1[0];
    double relativeY = absoluteP2[1] - p1[1];
    // calculate new X/Y of P2 relative to P1
    double nRelativeX = (relativeX*cos(angInRad)) - (relativeY*sin(angInRad));
    relativeY = (relativeX*sin(angInRad)) + (relativeY*cos(angInRad));
    // add relative X/Y to P1 X/Y to get new absoluteP2
    absoluteP2[0] = p1[0] + nRelativeX;
    absoluteP2[1] = p1[1] + relativeY;
    p2[0] = std::floor(absoluteP2[0] + 0.5);
    p2[1] = std::floor(absoluteP2[1] + 0.5);
}

Now i'm off to lock myself in the basement with all the Trigonometry material i've collected. Should be ready to emerge some time next spring. :(

This question has already been answered. Start a new discussion instead.