Ход решения задачи

Представим стрелку в виде четырехугольника следующей формы рис. 1.

Рис. 1. Вид стрелки и нумерация ее вершин.

Точку С – центр стрелки разместим в центре окна, и координаты вершин стрелки будем рассчитывать, используя координаты точки С как базисные. Примем, что координаты точки С представлены вектором . Тогда, координаты точки 0 зададим как вектор , точки 1 – , точки 2 - , точки 3 - . В однородных координатах, координаты вершин стрелки будут следующими:

Для имитации вращения стрелки вокруг своего центра, необходимо над координатами всех вершин выполнять преобразование поворота относительно заданной точки на заданный угол. Координаты точки - центра вращения, это координаты точки С. Преобразование можно представить как комбинацию трех элементарных преобразований, а именно:

1. Преобразование параллельного переноса начала системы координат в точку С.

2. Преобразование поворота точек плоскости на заданный угол относительно начала новой системы координат.

3. Преобразование параллельного переноса начала системы координат на старое место.

В матричной форме запишем наше преобразование в виде произведения матриц

,

где M – результирующая матрица преобразования,

- матрица переноса начала системы координат в точку С,

- матрица поворота точек плоскости на угол φ относительно начала координат,

- матрица преобразования переноса начала системы координат на старое место.

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

1. Определим координаты центра окна cx и cy.

2. Рассчитаем на их основе координаты вершин стрелки, и занесем их в массив векторов.

3. Отобразим стрелку на поверхности окна.

4. Рассчитаем матрицу преобразования для поворота на угол 5 или -5 в зависимости от направления вращения стрелки, используя в качестве координат центра вращения, координаты cx и cy.

5. Применим полученную матрицу к векторам координат вершин стрелки.

6. Через небольшой промежуток времени, сотрем изображение стрелки на поверхности окна, и нарисуем ее заново, используя новые координаты вершин.

7. Будем повторять пункты 4 – 6 пока не будет получена команда на прекращение вращения.

8. Если пользователь произвел щелчок левой кнопкой мыши на поверхности окна, заменим координаты cx и cy на соответствующие координаты курсора мыши.

Примечание: пункт 8 выполняется параллельно с циклом 4 – 6.

Листинг программы.

Библиотека для работы с векторами и матрицами

Заголовочный файл Vectors2.h

//---------------------------------------------------------------------------

// Определения классов для работы с векторами и матрицами

// для двумерной графики

//---------------------------------------------------------------------------

#ifndef vectors2H

#define vectors2H

//---------------------------------------------------------------------------

#endif

//Предварительное объявление классов

class Vector2;

class Matrix2;

//=========================================================

// Класс для работы с двумерными векторами

// в однородных координатах

//=========================================================

class Vector2

{

float X,Y,W; //Поля для хранения координат вектора в системе X, Y, W

public:

Vector2(float x=0, float y=0, float w=1); //Конструктор со значениями по умолчанию

Vector2(Vector2& vec); //Конструктор копирования

Vector2 operator=(Vector2 a); //Определение операции присваивания для векторов

Vector2 operator+(Vector2 a); //Определение операции сложения для векторов

Vector2 operator-(Vector2 a); //... вычитания...

Vector2 operator*(float c); //... умножения вектора на скаляр

Vector2 operator*(Matrix2 a); //... умножения вектора на матрицу 3х3

float& operator[](int i); //... доступа к компонентам вектора

void clear(void); //Функция делает вектор нулевым (0,0,1)

Vector2 set(float x=0, float y=0, float w=1); // Функция для установки значений сразу нескольким компонентам вектора

};

Vector2 vector2(float x=0, float y=0, float w=1); // Функция создает вектор с заданными значениями компонентов

class Matrix2

{

Vector2 M[3]; // Представляем матрицу 3х3 как совокупность 3-х векторов 3х1

public:

Matrix2(); // Конструктор без параметров (создает единичную матрицу)

Matrix2(Matrix2& m); // Конструктор копирования

Matrix2 operator=(Matrix2 a);// Определение операции присваивания для матрицы

Matrix2 operator+(Matrix2 a); // Определение операции сложения двух матриц

Matrix2 operator-(Matrix2 a); // Определение операции вычитания двух матриц

Matrix2 operator*(Matrix2 a); // Определение операции умножения двух матриц

Vector2& operator[](int i); // Определение операции доступа к элементам матрицы

void identity(void); // Функция делает единичную матрицу

};

Файл реализации Vectors2.cpp

//---------------------------------------------------------------------------

// Реализация классов для работы с векторами и матрицами

// для двумерной графики

//---------------------------------------------------------------------------

#pragma hdrstop

#include "vectors2.h"

#include <sysutils.hpp>

//---------------------------------------------------------------------------

#pragma package(smart_init)

//==========================================================

// Реализация класса Vector2

//==========================================================

//Конструктор со значениями по умолчанию

Vector2::Vector2(float x, float y, float w)

{

X = x; Y = y; W = w;

}

//Конструктор копирования

Vector2::Vector2(Vector2& vec)

{

X=vec[0]; Y=vec[1]; W=vec[2];

}

//--------------------------------------------------------

// Реализация операции присвавания векторов

Vector2 Vector2::operator=(Vector2 a)

{

X = a[0]; Y = a[1];W = a[2];

return *this; //Возвращаем сам вектор

}

//--------------------------------------------------------

//Реализация операции сложения векторов

Vector2 Vector2::operator+(Vector2 a)

{

Vector2 t; //заводим новый вектор для результата,

//поскольку исходный изменяться не должен...

//Вычисляем значения его компонентов...

t[0] = X + a[0];

t[1] = Y + a[1];

t[2] = W + a[2];

return t; //... и возвращаем как результирующий вектор

}

//--------------------------------------------------------

//Реализация операции вычитания векторов

Vector2 Vector2::operator-(Vector2 a)

{

Vector2 t;//заводим новый вектор для результата,

//поскольку исходный изменяться не должен...

//Вычисляем значения его компонентов...

t[0] = X - a[0];

t[1] = Y - a[1];

t[2] = W - a[2];

return t; //... и возвращаем как результирующий вектор

}

//--------------------------------------------------------

//Реализация операции умножения вектора на скаляр

Vector2 Vector2::operator*(float c)

{

Vector2 t; //заводим новый вектор для результата,

//поскольку исходный изменяться не должен...

//Вычисляем значения его компонентов...

t[0] = X * c;

t[1] = Y * c;

t[2] = W * c;

return t; //... и возвращаем как результирующий вектор

}

//---------------------------------------------------------

//Реализация операции умножения вектора на матрицу 3х3

Vector2 Vector2::operator*(Matrix2 a)

{

Vector2 t; //заводим новый вектор для результата,

//поскольку исходный изменяться не должен...

//Вычисляем значения его компонентов...

t[0]=X * a[0][0] + Y * a[1][0] + W * a[2][0];

t[1]=X * a[0][1] + Y * a[1][1] + W * a[2][1];

t[2]=X * a[0][2] + Y * a[1][2] + W * a[2][2];

return t; //... и возвращаем как результирующий вектор

}

//---------------------------------------------------------

//Реализация доступа к компонентам вектора

float& Vector2::operator[](int i)

{

switch (i)

{

case 0: return X; //[0]

case 1: return Y; //[1]

case 2: return W; //[2]

//Если индекс за границей массива, то формируем исключение

default: throw ERangeError("Invalid index");

}

}

//---------------------------------------------------------

// Функция делает вектор нулевым (0,0,1)

void Vector2::clear(void)

{

X = 0; Y = 0; W = 1;

}

//---------------------------------------------------------

//Функция задает новые значения сразу нескольким

//компонентам вектора

Vector2 Vector2::set(float x, float y, float w)

{

X=x; Y=y; W=w;

return *this; //Возвращаем сам вектор

}

//==========================================================

// Реализация класса Matrix2

//==========================================================

// Конструктор по умолчанию...

Matrix2::Matrix2()

{

identity(); //... создает единичную матрицу

}

//----------------------------------------------------------

//Конструктор копирования...

Matrix2::Matrix2(Matrix2& m)

{

//... создает копию матрицы m

M[0] = m[0];

M[1] = m[1];

M[2] = m[2];

}

//---------------------------------------------------------

//Реализация операции присваивания матриц

Matrix2 Matrix2::operator=(Matrix2 a)

{

int i;

for (i=0; i<3; i++) //Для всех строк матрицы...

M[i] = a[i]; //... копируем значения строк матрицы a

return *this;

}

//------------------------------------------------------------------------------

//Реализация операции сложения двух матриц

Matrix2 Matrix2::operator+(Matrix2 a)

{

Matrix2 t; //заводим новую матрицу для результата,

//поскольку исходная изменяться не должна...

//Вычисляем значения ее элементов...

t[0] = a[0] + M[0];

t[1] = a[1] + M[1];

t[2] = a[2] + M[2];

return t; //... и возвращаем как результирующую

}

//---------------------------------------------------------

//Реализация операции вычитания двух матриц

Matrix2 Matrix2::operator-(Matrix2 a)

{

Matrix2 t; //заводим новую матрицу для результата,

//поскольку исходная изменяться не должна...

//Вычисляем значения ее элементов...

t[0] = M[0] - a[0];

t[1] = M[1] - a[1];

t[2] = M[2] - a[2];

return t; //... и возвращаем как результирующую

}

//---------------------------------------------------------

//Реализация операции умножения двух матриц

Matrix2 Matrix2::operator*(Matrix2 a)

{

int i; //Номер текущей строки

Matrix2 t; //заводим новую матрицу для результата,

//поскольку исходная изменяться не должна...

//Вычисляем значения ее элементов...

for (i = 0; i < 3; i++)

t[i] = M[i] * a;

return t; //... и возвращаем как результирующую

}

//---------------------------------------------------------

//Реализация доступа к строкам матрицы

//Чтобы получить значение эл-та (i, j) используйте [i][j]

Vector2& Matrix2::operator[](int i)

{

switch (i) //Проверяем границы индекса

{

case 0: return M[0]; //[0]

case 1: return M[1]; //[1]

case 2: return M[2]; //[2]

//Если индекс за пределами, то формируем исключение

default: throw ERangeError("Invalid index");

}

}

//---------------------------------------------------------

//Функция создает единичную матрицу

void Matrix2::identity(void)

{

int i,j;

for (i = 0; i < 3; i++) //Проходим по всем строкам...

for (j = 0; j < 3; j++)//... и столбцам

if (i==j)

M[j][i] = 1.0; // Если диагональный элемент, то 1

else

M[i][j] = 0; // Иначе - 0

}

//---------------------------------------------------------

//Функция создает вектор с заданными значениями компонентов

Vector2 vector2(float x, float y, float w)

{

Vector2 vec(x,y,w); //Создаем новый вектор и задаем значения компонентам

return vec; //Возвращаем его как результат

}

Основной модуль.

Заголовочный файл mainform.h

//---------------------------------------------------------------------------

#ifndef MainFormH

#define MainFormH

//---------------------------------------------------------------------------

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include <ExtCtrls.hpp>

//---------------------------------------------------------------------------

class TfrMain: public TForm

{

__published: // IDE-managed Components

TTimer *Timer;

TPanel *Panel1;

TRadioGroup *rgDirect;

TButton *Button1;

void __fastcall FormCreate(TObject *Sender);

void __fastcall TimerTimer(TObject *Sender);

void __fastcall FormClose(TObject *Sender, TCloseAction &Action);

void __fastcall FormShow(TObject *Sender);

void __fastcall FormPaint(TObject *Sender);

void __fastcall Button1Click(TObject *Sender);

void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int X, int Y);

private: // User declarations

public: // User declarations

__fastcall TfrMain(TComponent* Owner);

};

//---------------------------------------------------------------------------

extern PACKAGE TfrMain *frMain;

//---------------------------------------------------------------------------

#endif

Файл реализации mainform.cpp

//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "MainForm.h"

#include "vectors2.h" //Подключаем библиотеку для работы с векторами и матрицами

#include <math.h> //Подключаем библиотеку математических ф-ций

//---------------------------------------------------------------------------

#pragma package(smart_init)

#pragma resource "*.dfm"

class Square //Класс для представления прямоугольника

{

Vector2 sq[4]; //Массив координат вершин

public:

Square(); //Конструктор по умолчанию

Vector2& operator[](int i); //Операция доступа к координатам вершин

//Функция рисует прямоугольник на поверхности рисования canvas

//три стороны одним цветом (с1), одну - другим (с2)

void draw(TCanvas *canvas, TColor c1, TColor c2);

};

Square::Square() //Конструктор по умолчанию

{ //Создает прямоугольник, у к-рого все вершины

//расположены в точке (0,0,1)

int i;

Vector2 vec(0,0,1); //Вспомогательный вектор

for (i=0; i<4; i++) //Проходим по всем вершинам...

sq[i] = vec; //... и задаем им координаты вектора vec

}

Vector2& Square::operator[](int i)

{

if (i<0 || i>3) //Проверяем границы индекса

//Если индекс за пределами, то формируем исключение

throw ERangeError("Invalid Side Index");

else

//Иначе - возвращаем соответсвующую вершину

return sq[i];

}

//Функция рисует прямоугольник на поверхности рисования canvas

//три стороны одним цветом (с1), одну - другим (с2)

void Square::draw(TCanvas *canvas, TColor c1, TColor c2)

{

if (canvas) //Рисуем, только если canvas существует

{

canvas->Pen->Color=c1; //Сначала установим цвет с1

canvas->Pen->Style=psSolid; //Устанавливаем сплошную линию

int i;

for (i=0; i<4; i++)

{

canvas->MoveTo(sq[i][0], sq[i][1]);//Перемещаем перо в новую точку

if (i!=3)

canvas->LineTo(sq[i+1][0], sq[i+1][1]); //Рисуем три стороны прямоугольника цветом с1

else

{

canvas->Pen->Color=c2; //Устанавливаем цвет с2

canvas->LineTo(sq[0][0], sq[0][1]); //Рисуем четвертую сторону цветом с2

}

}

}

}

//Функция выполняет поворот четырехугольника sq

//на угол degree, вокруг точки cp

void RotateSquare(Square& sq, float degree, Vector2& cp)

{

Matrix2 mf, //матрица поворота относительно точки (0,0)

mt, //матрица переноса точки cp в точку (0,0)

mtc, //матрица переноса точки (0,0) в точку cp

m; //результирующая матрица преобразования

float sinf, cosf;

int i;

sinf = sin(M_PI * degree / 180); //sin угла поворота

cosf = cos(M_PI * degree / 180); //cos угла поворота

//Сначала делаем все матрицы единичными

mf.identity();

mt.identity();

mtc.identity();

//Формируем матрицу переноса точки (0,0) в точку cp

mtc[2][0] = -cp[0];

mtc[2][1] = -cp[1];

//Формируем матрица обратного переноса начала координат

//из точки cp в точку (0,0)

mt[2][0] = cp[0];

mt[2][1] = cp[1];

//Формируем матрицу поворота на угол degree cosf sinf 0

// -sinf cosf 0

// 0 0 1

mf[0][0] = mf[1][1]= cosf;

mf[0][1] = sinf;

mf[1][0] = -sinf;

m = mtc * mf * mt; //Вычисляем матрицу общего преобразования...

for (i=0; i<4; i++) //... и применяем ее ко всем вершинам четырехугольника

sq[i] = sq[i] * m;

}

//-----------------------------------------------------------------------

//Определение глобальных переменных

Square square; //Экземпляр четырехугольника

Graphics::TBitmap *bitmap=NULL; //Указатель на битовую карту изображения

int cx, cy; //Координаты центра окна

Vector2 cpoint; //Центр вращения

TfrMain *frMain;

//---------------------------------------------------------------------------

__fastcall TfrMain::TfrMain(TComponent* Owner)

: TForm(Owner)

{

}

//---------------------------------------------------------------------------

//Когда создаем окно в первый раз

void __fastcall TfrMain::FormCreate(TObject *Sender)

{

cx=ClientWidth / 2; //Задаем координаты

cy=ClientHeight /2; //центра окна...

cpoint[0]=cx, cpoint[1] = cy; //и центра вращения

//Формируем четырехугольник

square[0]=vector2(cx-20,cy-30);

square[1]=vector2(cx,cy+30);

square[2]=vector2(cx+20,cy-30);

square[3]=vector2(cx,cy-10);

try //Пробуем создать битовую карту,

// если возникнет исключение выполнится секция catch

{

bitmap = new Graphics::TBitmap();

bitmap->Width = ClientWidth;

bitmap->Height = ClientHeight;

bitmap->PixelFormat = pf32bit;

if (bitmap->PixelFormat!= pf32bit) //проверяем установился ли нужный формат пикселей...

throw ""; //нет, формируем исключение

}

catch (...)

{

//Если были исключения,

//то выдаем сообщение...

Application->MessageBoxA("Невозможно создать битмап", "Ошибка", MB_ICONHAND | MB_OK);

Application->Terminate();//... и завершаем работу программы

}

//Если все прошло удачно...

bitmap->Canvas->Brush->Style=bsSolid;

bitmap->Canvas->Brush->Color=clBtnFace;

//Чистим битмап

bitmap->Canvas->FillRect(Rect(0,0,bitmap->Width-1,bitmap->Height-1));

//Рисуем четырехугольник в битмапе

square.draw(bitmap->Canvas, clRed, clGreen);

}

//---------------------------------------------------------------------------

//Обработчик события таймера

void __fastcall TfrMain::TimerTimer(TObject *Sender)

{

int i;

float degree; //Значение угла поворота

//Стираем в битмапе старое изображение...

bitmap->Canvas->Brush->Color=clBtnFace;

//... рисуя закрашеный прямоугольник во весь битмап

bitmap->Canvas->FillRect(Rect(0,0,bitmap->Width-1, bitmap->Height-1));

//Определяем в какую сторону вращать четырехугольник

if (rgDirect->ItemIndex)

degree = -5; //Против часовой

else

degree = 5; //По часовой

RotateSquare(square, degree,cpoint); //Поворачиваем четырехугольник...

//... и рисуем его в битмапе

square.draw(bitmap->Canvas, clRed, clGreen);

//Рисуем точку вращения...

bitmap->Canvas->Pen->Color = clRed;

bitmap->Canvas->Brush->Color = clRed;

//... кружочком красного цвета

bitmap->Canvas->Ellipse(cpoint[0]-5,cpoint[1]-5,cpoint[0]+5,cpoint[1]+5);

//Отображаем битмап на окне

Canvas->Draw(0, 0, bitmap);

}

//---------------------------------------------------------------------------

//Когда окно закрывается...

void __fastcall TfrMain::FormClose(TObject *Sender, TCloseAction &Action)

{

Timer->Enabled = False; //Останавливаем таймер

if (bitmap) //Если битмап существует,

delete bitmap; //то уничтожаем его экземпляр

bitmap = NULL;

}

//---------------------------------------------------------------------------

//Когда окно рисуется в первый раз...

void __fastcall TfrMain::FormShow(TObject *Sender)

{

//... нужно отобразить битмап на окне

Canvas->Draw(0,0,bitmap);

}

//---------------------------------------------------------------------------

//Если окно нужно перерисовать,...

void __fastcall TfrMain::FormPaint(TObject *Sender)

{

Canvas->Draw(0,0,bitmap); //... то отобразим битмап на окне

}

//---------------------------------------------------------------------------

//Если нажали кнопку на окне,...

void __fastcall TfrMain::Button1Click(TObject *Sender)

{

Timer->Enabled =!(Timer->Enabled); //Переключили состояние таймера в противоположное

if (Timer->Enabled) //Если таймер включился,...

Button1->Caption = "Стоп"; //То кнопка теперь будет "Стоп"...

else

Button1->Caption = "Пуск"; //... Иначе - "Пуск"

}

//---------------------------------------------------------------------------

//Если нажали кнопку мыши в окне

void __fastcall TfrMain::FormMouseDown(TObject *Sender,

TMouseButton Button, TShiftState Shift, int X, int Y)

{

if (Button == mbLeft) //Если нажали левую кнопку мыши,

//то задаем новые координаты центра вращения

{

cpoint[0] = X;

cpoint[1] = Y;

}

}

//---------------------------------------------------------------------------

4. Скриншоты работы программы

Рис 2. Начальный вид окна программы.

Рис. 3. Вращение стрелки.

Рис. 4. Новый центр вращения.

Рис. 5. Сменили направление вращения.

Литература

Основная:

1. Дергач В.В. Компьютерная графика: Учебное пособие. ИПЦ КГТУ, 2003 г.

2. Петров М.Н. Компьютерная графика: Учебное пособие. СПб.: Питер, 2002 г.

3. Никулин Е.А. Компьютерная геометрия и алгоритмы машинной графики: Учебное пособие. СПб.: БХВ-Петербург, 2003 г.

4. Шикин Е.В. Компьютерная графика. Полигональные модели. М.: Диалог-МИФИ, 2001 г.

5. Тихомиров Ю. OpenGL. Программирование трехмерной графики – 2-е изд. - СПб.: «БХВ – Санкт-Петербург», 2002 г.

Дополнительная:

1. Иванов В.П. Трехмерная компьютерная графика. М.: Радио и связь, 1999 г.

2. Л.С. Рубан, Н.В. Соснин. Аффинные преобразования. Методические указания. КГТУ, 1999 г.

3. Федорова Н.А. Математические основы компьютерной графики. Методические указания. Красноярск, 1999 г.

4. Павлидис Т. Алгоритмы машинной графики и обработки изображений. Пер. с англ. М.: Радио и связь, 1986 г.

5. Роджерс Д., Адамс Дж. Математические основы машинной графики:
пер. с англ. – М.: Машиностроение, 1980 г.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: