Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Компьютерная графика c примерами на С++ Поворот плоскости


Приветствую вас, уважаемые подписчики.

Хочу презентовать всем читателем рассылки наш сайт: compgraphics.info. Там можно найти все предыдущие выпуски + исходные файлы всех примеров. Там есть несколько разделов и скоро они начнут наполняться информацией, касающейся теории 3D, фракталов и OpenGL. Заходите!

Сегодня речь пойдет о поворотах плоскости. С плоскостью все получается относительно несложно. Если мы делаем поворот относительно начала координат, то для задания вращения используется один угол (phi). 

Прим. Принято считать направление вращения против часовой стрелки положительным. При этом удобно считать, что угол phi находится в интервале [–π; π].

Чтобы получить преобразование координат при повороте, возьмем произвольный вектор r, задающий некоторую точку. Его координаты:

x = |r| * cos(α)

y = |r| * sin(α)

При повороте на угол phi:

x1 = |r| * cos(α + phi) = |r| * (cos(α)cos(phi) – sin(α)sin(phi)) = x * cos(phi) – y * sin(phi)

y1 = |r| * sin(α + phi) = |r| * (sin(α)cos(phi) + sin(phi)cos(α)) = x * sin(phi) + y * cos(phi)

Т.о. при повороте на угол phi координаты x и y подвергаются преобразованию, написанному выше.

Прим. Здесь мы фактически использовали полярную систему координат. О ней я расскажу подробно в одном из последующих выпусков.

Матричное представление поворота плоскости

Написанное выше преобразование координат удобно представить в виде матрицы:

[ x   y ]  *  [ cos(phi)     –sin(phi) ]     [ x1   y1 ]

 *               =

    [ sin(phi)      cos(phi) ]     

Прим. Умножение матриц производится по принципу строка на столбец. Поэтому количество столбцов (элементов в строке) в матрице слева должно совпадать с количеством строк (элементов в столбце) в матрице справа.

Какие преимущества дает нам матричное представление? Заметим, что есть умножить две матрицы - повороты на угол α и угол β, то получится матрица поворота на угол α + β. Это легко проверить, перемножив соответствующие матрицы и использовав формулы для косинуса и синуса суммы.

Создадим программу, демонстрирующую приведенную выше теорию. Будем хранить текущий поворот системы, как матрицу и каждый раз ''доворачивать’’ систему на нужный угол, используя умножение матриц.

Для начала нам понадобится framework для работы с матрицами:

matrix.cpp:

#include <math.h>
#include
<memory.h>
#include
"matrix.h"
#include
"geometry.h" 

void SetRotationMatrix(double alpha, Matrix &matrix){
      matrix[0] = cos(alpha);
      matrix[1] = -sin(alpha);
      matrix[2] = sin(alpha);
      matrix[3] = cos(alpha);
}

void MultiplyMatrices(Matrix &dest, Matrix &left, Matrix &right){
      double ldet = left[0] * left[3] - left[1] * left[2];
      double rdet = right[0] * right[3] - right[1] * right[2];
      Matrix _dest;
      _dest[0] = left[0] * right[0] + left[1] * right[2];
      _dest[1] = left[0] * right[1] + left[1] * right[3];
      _dest[2] = left[2] * right[0] + left[3] * right[2];
      _dest[3] = left[2] * right[1] + left[3] * right[3];
      memcpy(dest, _dest, sizeof(Matrix));
}

void ApplyMatrixtoPoint(Matrix rot, _Point &point){
      double _x, _y;
      _x = point.x;
      _y = point.y;
      point.x = _x * rot[0] + _y * rot[1];
      point.y = _x * rot[2] + _y * rot[3];
}

matrix.h:

#include "geometry.h"

typedef double Matrix[4];

void SetRotationMatrix(double alpha, Matrix &matrix);
void
MultiplyMatrices(Matrix &dest, Matrix &left, Matrix &right);
void
ApplyMatrixtoPoint(Matrix rot, _Point &point);

Прим. Единственное, на что я хочу обратить ваше внимание, это на функцию MultiplyMatrices. Во многих случаях в роли dest и left выступает одна и та же матрица. Поэтому если сразу записывать в dest, то получится некорректно.

Появляется модуль geometry.h и тип ’’точка’’ (_Point):

#ifndef _POINT

      struct _Point{
            double x, y;
      };

      #define _POINT

#endif

Достаточно существенно меняется модуль draw.cpp и я решил привести его целиком:

#include <windows.h>
#include
"geometry.h"
#include
"matrix.h"

int Width, Height;
Matrix current_rot;

const int MARGIN = 10;

void InitRotation()
{
      SetRotationMatrix(0.0, current_rot);
}
 
void
AddRotation(double alpha)
{
      Matrix additional_rot;
      SetRotationMatrix(alpha, additional_rot);
      MultiplyMatrices(current_rot, current_rot, additional_rot);

}
 
void
SetWindowSize(int _Width, int _Height){
      Width = _Width;
      Height = _Height;

}

_Point T(_Point point){
      _Point TPoint;
      TPoint.x = MARGIN + (1.0 / 2) * (point.x + 1) * (Width - 2 * MARGIN);
      TPoint.y = MARGIN + (-1.0 / 2) *  (point.y - 1) * (Height - 2 * MARGIN);
      return TPoint;

}
 
void
Draw(HDC hdc){
      _Point triangle[3];
      triangle[0].x = 0.0;
      triangle[0].y = 0.5;
      triangle[1].x = 0.5;
      triangle[1].y = 0.0;
      triangle[2].x = -0.5;
      triangle[2].y = -0.5;

      for (int i = 0; i < 3; i++){
            ApplyMatrixtoPoint(current_rot, triangle[i]);
            triangle[i] = T(triangle[i]);
      }

      for (int i = 0; i <= 3; i++){
            int j = i % 3;
            if (i == 0){
                  MoveToEx(hdc, triangle[j].x, triangle[j].y, NULL);
            }
            else
           
{
                  LineTo(hdc, triangle[j].x, triangle[j].y);
            }
      }
}
 

Вот содержание файла draw.h:

void Draw(HDC hdc);

void SetWindowSize(int _Width, int _Height);

void InitRotation();

void AddRotation(double alpha);

И наконец в модуле main.cpp мы добавляем обработчик WM_KEYPRESSED:

case WM_KEYDOWN:

            int KeyPressed;

            KeyPressed = int(wParam);

            if (KeyPressed == VK_RIGHT){

                  AddRotation(-PI / 10);

                  }

            if (KeyPressed == VK_LEFT){

                  AddRotation(PI / 10);

                  }

            InvalidateRect(hWnd, NULL, FALSE);

            break;

Все! Теперь при нажатии клавиш ← и → треугольник будет вращаться.

Прим. Несколько не разумно перерисовывать окно при нажатии любой клавиши, но мы не будем пока заострять на этом внимание.

Посмотреть выпуск на сайте и скачать исходный код демонстрационной программы

Не забывайте об обратной связи. Мне будет приятно не только услышать ваше мнение, но и прочитать о темах, которые вы хотите увидеть в настоящей рассылке. Моя электронная почта: cgraphics@mail.ru

Ведущий рассылки, Денис


В избранное