поворот производится операцией сдвига и умножения на матрицу. немного теории:
в 2-мерном векторном (евклидовом) пространстве все элементы можно представить как векторы от точки {0,0} до точки {a,b}. Ещё над векторами есть операции: сложение/вычитание и скалярное умножение (свёртка). Ещё есть так называемое тензорное (внешнее) умножение. Оно превращает 2 вектора в матрицу, 3 и больше векторов в более сложные объекты. Есть группа простейших преобразований: масштабирование, сдвиг и поворот. Они все описываются скалярным умножением вектора на матрицу. И ещё последовательные операции можно комбинировать в одну матрицу.
Короче:
{{Cos[a], Sin[a]}, {-Sin[a], Cos[a]}} . {x,y} - поворот вектора на угол a вокруг точки {0,0}
{c,d} + {{Cos[a], Sin[a]}, {-Sin[a], Cos[a]}} . {x,y} - поворот вектора на угол a и сдвиг точки на {c,d}
есть волшебная функция, которая задаёт это преобразование SetWorldTransform и которая накладывает 2 преобразования ModifyWorldTransform. Они использут структурку:
Код:
x' = x * eM11 + y * eM21 + eDx,
y' = x * eM12 + y * eM22 + eDy,
typedef struct _XFORM { // xfrm
FLOAT eM11;
FLOAT eM12;
FLOAT eM21;
FLOAT eM22;
FLOAT eDx;
FLOAT eDy;
} XFORM;
теперь делаем следующим образом: пусть есть исходные координаты, описанные структурой POINT (из winapi)
используем подход с откатами при исключениях (при исключении не портим DC). Что получилось - смотрим скриншот.
Код:
#include <vector>
//#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <stdexcept>
//#include <windows.h>
using namespace std;
//----------------------------------------------
void _error(const char* message);
//----------------------------------------------
// не то, чтобы класс, просто кучка функций для того, чтобы было на чём рисовать
// и чтобы результаты трудов потом не затирались
class Window
{
public:
static void execute();
static char* ginit();
static void paint(HDC dc);
private:
static long __stdcall WndProc(HWND, unsigned, unsigned, long);
};
//----------------------------------------------
// класс для рисования объектов
// 1. сохраняет восстанавливает параметры DC
// 2. вычисляет преобразование координат
// 3. рисует полигоны
class draw_t
{
HDC rest_dc;
int rest_mode;
XFORM rest_form;
unsigned steps;
XFORM adv_form;
public:
draw_t(HDC dc) :
steps (10),
rest_dc (dc),
// включаем продвинутый режим драйвера рисования для устройства
// нужно для 2k, не всегда нужно для NT и XP:
rest_mode (SetGraphicsMode(rest_dc, GM_ADVANCED)),
// по умолчанию уменьшение и небольшое смещение
adv_form (draw_t::rotate_scale(0.0174533*10.0, 0.95, -3, -.5))
{
if (0==rest_mode ||
!GetWorldTransform(rest_dc, &rest_form)) // запомнили настройки отображения.
{
_error("draw_t::draw_t");
}
}
~draw_t()
{
// не проверяем на ошибки ибо пофиг уже
SetWorldTransform(rest_dc, &rest_form);
SetGraphicsMode(rest_dc, rest_mode);
}
// вычисляем поворот и масштабирование
static XFORM rotate_scale(const double& angle, const double& scale, const double& x, const double& y)
{
double cos1 = scale*cos(angle);
double sin1 = scale*sin(angle);
XFORM form1 = {cos1, sin1, -sin1, cos1, x, y};
return form1;
}
// вычисляем поворот и масштабирование
static XFORM rotate_scale2(const double& angle, const double& scaleX, const double& scaleY, const double& x, const double& y)
{
double cos1 = cos(angle);
double sin1 = sin(angle);
XFORM form1 = {scaleX*cos1, scaleX*sin1, -scaleY*sin1, scaleY*cos1, x, y};
return form1;
}
// настройка параметров
void setSteps(unsigned n)
{
steps = n;
}
void setTransform(const XFORM& form1)
{
if (!SetWorldTransform(rest_dc, &form1)) _error("draw_t::setTransform");
}
void setAdvance(const XFORM& form1)
{
adv_form = form1;
}
// рисование
void operator()(POINT* points, unsigned count)
{
// собственно говоря, самая полезная функция,
// ради которой все затевалось. Отрисовка на низком уровне.
// остальной код - только для поддержания порядка.
for(unsigned n=0; n<steps; ++n)
{
if (!Polygon(rest_dc, points, count))
_error("draw_t() at polygon");
if (!ModifyWorldTransform(rest_dc, &adv_form, MWT_LEFTMULTIPLY))
_error("draw_t() at modify transform");
}
}
};
//----------------------------------------------
void Window::paint(HDC dc)
{
// здесь происходит отрисовка на высоком уровне
draw_t draw(dc);
// параметры
draw.setSteps(150);
draw.setTransform(draw_t::rotate_scale(0.0174533*0.0, 5.0, 200, 400));
draw.setAdvance(draw_t::rotate_scale2(0.0174533*9.0, 0.90, 1.05, 5., -7.5));
// рисуешь в кореле фигуру и переписываешь сюда координаты её точек ;-)
static POINT tryangle_points[3] = {{-20,-10}, {20, -10}, {0, 20}};
draw(tryangle_points, 3);
}
//----------------------------------------------
char* Window::ginit()
{
// это требуется для создания окна. Можно было через диалоги сделать,
// но там код короче, но запутанней
static char* class_name = 0;
if (!class_name)
{
WNDCLASSEX wcla;
wcla.cbSize = sizeof(WNDCLASSEX);
wcla.style = 0;
wcla.lpfnWndProc = WndProc;
wcla.cbClsExtra = 0;
wcla.cbWndExtra = 4;
wcla.hInstance = GetModuleHandle(0);
wcla.hIcon = LoadIcon(0, IDI_APPLICATION);
wcla.hCursor = LoadCursor(0, IDC_ARROW);
wcla.hbrBackground = HBRUSH(1 + COLOR_BTNFACE);
wcla.lpszMenuName = 0;
wcla.lpszClassName = "Window";
wcla.hIconSm = LoadIcon(0, IDI_APPLICATION);
class_name = reinterpret_cast<char*>(RegisterClassEx(&wcla));
}
return class_name;
}
// структура для обработки события WM_PAINT (вынесена отдельно)
// и чистки мусора после неё, exception-safe
struct safe_dc
{
HWND hwnd;
PAINTSTRUCT ps;
safe_dc(HWND hwnd1) : hwnd(hwnd1)
{
if (!BeginPaint(hwnd1, &ps)) _error("safe_dc::safe_dc");
}
~safe_dc()
{
EndPaint(hwnd, &ps);
}
};
long __stdcall Window::WndProc(HWND hwnd, unsigned code, unsigned wparam, long lparam)
{
// оконная процедура
try
{
if (code==WM_PAINT) // рисуем
{
safe_dc safe_dc1(hwnd);
Window::paint(safe_dc1.ps.hdc);
return 0;
}
else if (code==WM_CLOSE) // закрываемся (выходим)
{
PostQuitMessage(0);
}
}
catch(exception& e)
{
MessageBox(0, e.what(), 0, MB_OK|MB_ICONERROR);
}
return DefWindowProc(hwnd, code, wparam, lparam);
}
void Window::execute()
{
// простейшая обработка ввода/вывода приложения.
MSG cmsg;
while (GetMessage(&cmsg, 0, 0, 0))
{
// TranslateMessage(&cmsg);
DispatchMessage(&cmsg);
}
}
//----------------------------------------------
void _error(const char* message)
{
// генерируем исключение с кодом ошибки ОС
long last_error = GetLastError();
ostringstream ss;
ss << message << " error=";
ss.width(8);
ss.fill('0');
ss.flags(ios::hex|ios::right);
ss << last_error;
throw runtime_error(ss.str());
}
int main()
{
// создали окно, поигрались, уничтожили
HWND hwnd = CreateWindowEx(0, Window::ginit(), "Draw Test", WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
GetDesktopWindow(), 0, GetModuleHandle(0), 0);
if (hwnd)
{
Window::execute();
DestroyWindow(hwnd);
}
return 0;
}