4

I am trying to rotate a rectangle using the matrix (x cos θ - y sin θ, x sin θ + y cos θ). The problem is that the rectangle is getting smal and reaching the origin point per time (I am using timer). Here is my code.

void WINAPI Rotate(POINT arr[5])
{
    static POINT origin = { 400, 400 };
    static int i;
    static const double angle = 0.1;
    for (i = 0; i < 5; ++i)
    {
            // translate
        arr[i].x -= origin.x;
        arr[i].y -= origin.y;
            // rotate
        arr[i].x = arr[i].x * cos(angle) - arr[i].y * sin(angle);
        arr[i].y = arr[i].x * sin(angle) + arr[i].y * cos(angle);
            // translate
        arr[i].x += origin.x;
        arr[i].y += origin.y;
    }
}

So I want the points in arr to be fixed distance from the origin point after rotation. I don't want them to reach the origin point per time.

Initially:

    arr[0].x = 200;
    arr[0].y = 100;
    arr[1].x = 100;
    arr[1].y = 100;
    arr[2].x = 100;
    arr[2].y = 200;
    arr[3].x = 200;
    arr[3].y = 200;
    arr[4].x = arr[0].x;
    arr[4].y = arr[0].y;

If this is the wrong way for rotation, does someone know a correct method to rotate a rectangle about its origin, without affecting its size?

Here are snapshots while it rotates:

enter image description here enter image description here enter image description here

3 Answers3

2

In your rotation,

arr[i].x = arr[i].x * cos(angle) - arr[i].y * sin(angle);
arr[i].y = arr[i].x * sin(angle) + arr[i].y * cos(angle);

you use the new x-coordinate to compute the new y-coordinate, but you should use the old one. To fix it, use a temporary,

double temp = arr[i].x * cos(angle) - arr[i].y * sin(angle);
arr[i].y = arr[i].x * sin(angle) + arr[i].y * cos(angle);
arr[i].x = temp;
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
  • Nice catch, but the math is wrong! :-) Save `old_x` and use _that_ for the calculations. – Adam Liss Oct 27 '12 at 12:34
  • Oh, rats, too replace-happy. Should of course use `arr[i].x` in the caomputation. – Daniel Fischer Oct 27 '12 at 12:37
  • I _hate_ when I do that! :-) I've added an alternative to my own answer, which I hope will be easier to understand. – Adam Liss Oct 27 '12 at 12:39
  • @AdamLiss, Daneil Thank you. The rectangle is still getting disappear also :) – user1234524521 Oct 27 '12 at 12:42
  • @user1234524521 With the corrected code, I don't get any distortion or shrinking noticeable with bare eyes for tens of millions of iterations. If your rectangle still shrinks, there must be something else going on. – Daniel Fischer Oct 27 '12 at 13:11
  • @user1234524521 That rotation code looks correct, but I just had an idea: are the coordinates of a `POINT` perhaps integers? In that case, you'd get shrinking from truncation. You need to work with `double` coordinates, and maybe convert to `int` for display, if necessary. – Daniel Fischer Oct 27 '12 at 13:28
1

The origin is set to (400, 400), so the rotation takes place around that point. If you want the object to rotate around the object's center, which is (150, 150), set the origin to that point.

Then, as Daniel Fischer noted in his answer, use the original position in the calculations:

double old_x = arr[i].x;
double old_y = arr[i].y;

arr[i].x = old_x * cos(angle) - old_y * sin(angle);
arr[i].y = old_x * sin(angle) + old_y * cos(angle);
Community
  • 1
  • 1
Adam Liss
  • 47,594
  • 12
  • 108
  • 150
0

For each point rotate it by using this matrix:

enter image description here

that is what you are doing and it is OK.

And translate it by using

enter image description here

And that what you are doing and that is ok as well.

That is your code with the fixed code, mentioned by the others:

#include <Windows.h>
#include <math.h>
#define ID_TIMER 1

LRESULT CALLBACK WndProc(HWND , UINT , WPARAM , LPARAM );
void WINAPI Rotate(POINT arr[5]);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int cmdShow)
{
    TCHAR szAppName[] = TEXT("HelloWin32");
        HWND hWnd;
        MSG msg;
        WNDCLASS wndClass;
        wndClass.style = CS_HREDRAW | CS_VREDRAW;
        wndClass.lpfnWndProc = WndProc;
        wndClass.cbClsExtra = wndClass.cbWndExtra = 0;
        wndClass.hInstance = hInstance;
        wndClass.hIcon = LoadIcon(0, IDI_APPLICATION);
        wndClass.hCursor = LoadCursor(0, IDC_ARROW);
        wndClass.hbrBackground = (HBRUSH) GetStockObject(1);
        wndClass.lpszMenuName = 0;
        wndClass.lpszClassName = szAppName;

        if (!RegisterClass(&wndClass))
        {
                MessageBox(0, TEXT("Failed to register window class"), TEXT("Error"), MB_OK | MB_DEFBUTTON1 | MB_ICONERROR);
                return 1;
        }
        hWnd = CreateWindow(szAppName, TEXT("Hello World Win32!!"), WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                NULL, NULL, hInstance, NULL);
        if (!hWnd)
        {
                MessageBox(NULL, TEXT("Failed to create the window"), TEXT("Win32 Error"), MB_ICONERROR);
                return 1;
        }
        SetTimer(hWnd, ID_TIMER, 400, NULL);

        ShowWindow(hWnd, cmdShow);
        UpdateWindow(hWnd);

        while (GetMessage(&msg, NULL, 0, 0))
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
        return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM w, LPARAM l)
{
        static HDC hdc;
        static PAINTSTRUCT ps;
        static POINT arr[5];
        static int i;
        static RECT rect;
        switch (message)
        {
        case WM_CREATE:
                GetClientRect(hWnd, &rect);
                arr[0].x = 200;
                arr[0].y = 100;
                arr[1].x = 200;
                arr[1].y = 200;
                arr[2].x = 100;
                arr[2].y = 200;
                arr[3].x = 100;
                arr[3].y = 100;
                arr[4].x = arr[0].x;
                arr[4].y = arr[0].y;
                return 0;
        case WM_TIMER:
                Rotate(arr);
                InvalidateRect(hWnd, &rect, TRUE);
                return 0;
        case WM_DESTROY:
                KillTimer(hWnd, ID_TIMER);
                PostQuitMessage(0);
                return 0;
        case WM_PAINT:
                hdc = BeginPaint(hWnd, &ps);
                MoveToEx(hdc, arr[0].x, arr[0].y, NULL);
                for (i = 1; i < 5; ++i) LineTo(hdc, arr[i].x, arr[i].y);
                EndPaint(hWnd, &ps);
                return 0;
        case WM_SIZE:
                GetClientRect(hWnd, &rect);
                return 0;
        default:
                return DefWindowProc(hWnd, message, w, l);
        }
}

void WINAPI Rotate(POINT arr[5])
{
        static const POINT origin = { 150, 150 };
        static int i;
        static const double angle = 0.3;
        static const int direction = 1; // 1 or -1

        for (i = 0; i < 5; ++i)
        {
             POINT temp;
             arr[i].x -= origin.x;
             arr[i].y -= origin.y;
             temp.x = arr[i].x;
             temp.y = arr[i].y;
             arr[i].x = ceil(arr[i].x * cos(angle)) - ceil(direction * arr[i].y * sin(angle));
             arr[i].y = ceil(direction * temp.x * sin(angle))  + ceil(temp.y * cos(angle));
             arr[i].x += origin.x;
             arr[i].y += origin.y;
        }

}

The changes I made:

for (i = 0; i < 5; ++i)
        {
             POINT temp;
             arr[i].x -= origin.x;
             arr[i].y -= origin.y;
             temp.x = arr[i].x;
             temp.y = arr[i].y;
             arr[i].x = ceil(arr[i].x * cos(angle)) - ceil(direction * arr[i].y * sin(angle));
             arr[i].y = ceil(direction * temp.x * sin(angle))  + ceil(temp.y * cos(angle));
             arr[i].x += origin.x;
             arr[i].y += origin.y;
        }

while declare the points like that:

 arr[0].x = 200;
                arr[0].y = 100;
                arr[1].x = 200;
                arr[1].y = 200;
                arr[2].x = 100;
                arr[2].y = 200;
                arr[3].x = 100;
                arr[3].y = 100;
                arr[4].x = arr[0].x;
                arr[4].y = arr[0].y;

That is a win32 project in visual studio

Comment:

Could be nice solution for people who look for:

How to rotate a rectangular in win32 application using WINAPI

0x90
  • 39,472
  • 36
  • 165
  • 245
  • I have added the output to the question – user1234524521 Oct 27 '12 at 12:31
  • won't the rectangle be getting bigger and bigger now, as it rotates, because of your use of `ceil`? The right solution is to not *represent* your rectangle with `int`s - i.e. `POINT` struct - but to use doubles for representation and calculation, and only for display have them converted to `int`s. – Will Ness Oct 28 '12 at 06:59
  • @WillNess it is better to use round instead... for a long time the expectation is it will remain in the origin length – 0x90 Oct 29 '12 at 07:16
  • that is a wrong solution. the behaviour will be unpredictable, and there's no real reason why to not use the real solution. which is to use doubles representation for points, and translate to ints only for show. – Will Ness Oct 29 '12 at 07:55
  • @WillNess I totally disagree rounding a double before casting it to int is always better!!!!! – 0x90 Oct 29 '12 at 08:25
  • I didn't claim otherwise. you can "translate doubles to ints for show" by rounding. But if you use ints as representation and round the double calculations, the behaviour becomes unpredictable, even if not as drastically as when using ceil/floor. – Will Ness Oct 29 '12 at 09:50