МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”
Кафедра КСА
Пояснювальна записка
до розрахункової роботи
з курсу "Програмування комп'ютерної графіки"
1.1 Завдання
Побудувати графік функції заданої таблично. Масштаб розмітки осей координат графіка повинен відповідати реальним результатам розрахунків. Параметри для побудови графіка визначені у таблиці 2.
Необхідно передбачити плаваючий центр координат – основну частину екрану мають займати ті чверті, в яких знаходиться графік функції.
Завдання для побудови графіку
Таблиця 1
№ варіанту
Функція f(x)
Діапазон зміни аргументу
17
y = ch(x)
[0; 20]
Таблиця 2
№ варіанту
Тип лінії для побудови
Товщина лінії
Колір лінії
17
2
Orange
1.2 Завдання
Створити на екрані комп’ютера графічне вікно і сформувати в ньому рухоме зображення. Вікно розмістити у верхньому правому куті екрану. Навести межі вікна.
Параметри зображення задані в пікселях. Графік і вікно з рухомим зображенням повинні присутні на екрані одночасно.
Завдання для побудови рухомого зображення
№ варіанту
Структура зображення
Пояснення
9
2.1 Розрахунок функціональних залежностей для побудови графіка
Оскільки реальні координати графіка функції, що виводиться на екран можуть бути або значно більшими, або значно меншими за машинні координати екрану монітору, необхідно визначити значення масштабних коефіцієнтів стискання або розтягу для функції, що виводиться на екран.
Протабулюємо задану функцію на проміжку зміни аргументу з метою визначення максимального та мінімального значень функції і аргументу : xmax , xmin, ymax, ymin.
Знайдемо значення масштабуючих коефіцієнтів:
kx = (rect.right - 450) / (xmax - xmin);
ky = (rect.bottom - 50) / (ymax - ymin);
де: rect.right, rect.bottom – координати меж вікна.
Використовуючи обчислені коефіцієнти побудуємо графік функції і осі координат, нанесемо розмітку.
2.2 Розрахунок матриці перетворень для рухомого зображення
Для побудови зображень на екрані комп'ютера використовуються операції переносу, масштабування, повороту та їх композиції.
У своєму завданні я використав формули розрахунку повороту в полярних координатах. Розрахунок повороту в полярних координатах можна виразити наступним чином :
x = rd * sin(angle) + Mx
y = -rd * cos(angle) + My
де:
angle – кут повороту;
М(х,у) – точка відносно якої відбувається операція повороту.
rd – радіус вектор.
У циклі програми, який реалізує рух маятника я скористався цими формулами (координата кінця нитки обчислюються при змінному значенні кута angle, у межах від 30° до 150° і від 150° до 30°, під час руху маятника у напрямі і проти напряму осі ОХ, відповідно):
dx = xc - (radius) * cos(angle);
dy = yc + radius * sin(angle);
де:
(xc, yc) – координати точки відносно якої відбувається поворот підвісу маятника.
Координати вершин спиць кола обчислюються при змінному значенні кута angle_small.
3. Список індентифікаторів програми
Побудова графіка функції:
rect – структура, в якій записані розміри екрана.
rect.bottom, rect.right – нижня та права сторона вікна, відповідає за розміри клієнтського вікна.
rect.top, rect.left – верхня та ліва сторона вікна, рівна 0.
rect.right – змінна яка задає ширину області для побудови графіка функції.
str[192] – масив символів.
kx, ky – коефіцієнт перетворення координат.
x0, y0 – мінімальне значення графіка в машинних координатах по осях x та y відповідно.
x – змінна, з інтервалом зміни від x1 до x2 з кроком h.
y – значення функції.
lx, ly – довжина осі по x та y відповідно.
sx, sy – ціле значення від розбиття осі на 10 частин по x та y, відповідно.
tx, ty – змінна, значення якої відповідає довжині додатної осі по x та від’ємної по y, відповідно.
xc, yc – центр графіка в точці x = 0 по осях x та y, відповідно. Плаваючий центр координат.
xx, yy – змінна, яка відповідає за розбиття графіка на 10 частин по осях x та y, відповідно.
xmax , xmin, ymax, ymin – мінімальне та максимальне значення функції по осях x та y, відповідно.
txp, txm, typ, tym – змінні, які визначають положення тексту розмітки осей у додатному та від’ємному напрямках x та y, відповідно.
Побудова анімації:
radius – радіус маятника.
radius_small – радіус кола зі спицями.
slp_tm_crcl – час паузи (у мілісекундах) для побудови анімації кола зі спицями
slp_tm_pndlm - час паузи (у мілісекундах) для побудови анімації нитки маятника
h – крок.
аngle – кут, на який повертається нитка маятника.
аngle_small – кут, на який обертаються спиці маятника.
dx, dy – координати кінця нитки маятника.
dxc1, dyc1, dxc2, dyc2, dxc3, dyc3, dxc4, dyc4 – координати точок спиць при обертанні навколо центра кола, ці точки лежать на дузі кола.
counter – кількість повних коливань маятника.
sequel – умова для виходу із циклу while().
Команди і функції:
GetClientRect(&rect) – команда для запису розмірів екрана в структуру rect.
swprintf_s() – записує значення змінної в масив str.
TextOut() – виводить значення змінної по заданих координатах.
MoveTo(x, y) – переміщує поточну вершину, змінна x, y визначають координати нової поточної вершини.
LineTo(x, y) – будує лінію з поточної вершини у точку з координатами x, y.
Rectangle(int x1, int y1, int x2, int y2) - малює прямокутник, x1, у1 задають координати верхнього лівого куга, a x2, у2 правого нижнього кута прямокутника.
Ellipse(int x1, int y1, int x2, int y2) - малює еліпс, x1, у1 задають координати верхнього лівого куга, a x2, у2 правого нижнього кута прямокутника який визначає розміри еліпса. При використанні функцій «перо» , колір контуру еліпса задається «пером».
Створює і задає перо з параметрами:
CPen «ім’я»;
«ім’я».CreatePen(«стиль пера», «товщина пера», «колір RGB»);
dc.SelectObject(&«ім’я»).
4. Блок-схема програми:
5. Лістинг вихідної програми:
#include "stdafx.h"
#include "roz.h"
#include "ChildView.h"
#include "math.h"
#include <stdio.h>
#define _USE_MATH_DEFINES
#include "math.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CChildView
CChildView::CChildView()
{
}
CChildView::~CChildView()
{
}
BEGIN_MESSAGE_MAP(CChildView, CWnd)
ON_WM_PAINT()
END_MESSAGE_MAP()
// CChildView message handlers
BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
cs.style &= ~WS_BORDER;
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), NULL);
return TRUE;
}
void CChildView::OnPaint()
{
CPaintDC dc(this);
//===============================================
// Графік функції
//===============================================
wchar_t str[192];
double kx, ky, x0, y0, y, lx, ly, sx, sy, xc, yc, tx, ty, xx, yy, t;
double xmax = 0, xmin = 0, ymax = 0, ymin = 0, txp = 0, txm = 0, typ = 0, tym = 0;
double x;
double x1 = 0, x2 = 20; // Межі табулювання
double h = 0.05; // Крок табулювання
CPen pen(PS_DOT, 2, RGB(255, 165, 0));
RECT rect; // Змінна яка визначає координати робочої області вікна, записана в структуру RECT
GetClientRect(&rect); // Функція визначає координати робочої області вікна
// Табулювання заданої функції на проміжку зміни аргументу з метою
// визначення максимального та мінімального значень функції
for (x = x1; x <= x2; x += h)
{
y = cosh(x);
if (ymin > -y)
{
ymin = -y;
}
else if (ymax<-y)
{
ymax = -y;
}
if (xmin>x)
{
xmin = x;
}
else if (xmax < x)
{
xmax = x;
}
}
// Знаходження значення функції в точці x = 0
x = 0;
y = cosh(x);
// Знаходження коефіцієнтів і центру реальних координат
kx = (rect.right - 450) / (xmax - xmin);
ky = (rect.bottom - 50) / (ymax - ymin);
x0 = xmin*kx - 20;
y0 = ymin*ky - 20;
xc = x*kx - x0;
yc = -y*ky - y0;
// Побудова осей
// Побудова У
dc.MoveTo(xc, rect.top + 10);
dc.LineTo(xc, rect.bottom - 10);
// Побудова Х
dc.MoveTo(rect.left + 10, yc);
dc.LineTo(rect.right - 400, yc);
dc.MoveTo(xc - 5, rect.top + 20); // Стрілка осі У
dc.LineTo(xc, rect.top + 10);
dc.LineTo(xc + 5, rect.top + 20);
dc.MoveTo(rect.right - 410, yc - 5.0);// Стрілка осі Х
dc.LineTo(rect.right - 400, yc);
dc.LineTo(rect.right - 410, yc + 5);
// Розмітка осей
const CString Y("Y"), X("X"), O("0");
dc.TextOutW(xc - 15, rect.top + 5, Y);
dc.TextOutW(rect.right - 400, yc + 5, X);
dc.TextOutW(xc + 5, yc + 5, O);
lx = (rect.right - 200) - (rect.left + 200); // Довжини осей
ly = (rect.bottom + 10) - (rect.top - 50);
sx = floor(lx / 10);
sy = floor(ly / 10);
tx = rect.right - fabs(x0) - 400; // Довжини розмітки
ty = rect.bottom - fabs(y0);
xx = (xmax - xmin) / 10;
yy = (ymax - ymin) / 10;
// Розмітка осі Х
for (t = sx; t <= -x0 - 30; t = t + sx) // У від'ємному напрямі
{
txm = txm - xx;
dc.MoveTo(xc - t, yc - 5);
dc.LineTo(xc - t, yc + 5);
swprintf_s(str, 50, L"%.1f", txm);
dc.TextOut(xc - t - 10, yc + 5, str);
}
for (t = sx; t <= tx - 30; t = t + sx) // У додатньому напрямі
{
txp = txp + xx;
dc.MoveTo(xc + t, yc - 5);
dc.LineTo(xc + t, yc + 5);
swprintf_s(str, 50, L"%.0f", txp); // Значення під поділками
dc.TextOut(xc + t - 10, yc + 5, str); // Положення тексту під поділками
}
// Розмітка осі У
for (t = sy; t <= -y0 - 15; t = t + sy) // у додатньому напрямі
{
typ = typ + yy;
dc.MoveTo(xc - 5, yc - t);
dc.LineTo(xc + 5, yc - t);
swprintf_s(str, 50, L"%.1f", typ/1e7);
dc.TextOut(xc + 10, yc - t - 10, str);
}
for (t = sy; t <= ty - 15; t = t + sy) // у від'ємному напрямі
{
tym = tym - yy;
dc.MoveTo(xc - 5, yc + t);
dc.LineTo(xc + 5, yc + t);
swprintf_s(str, 50, L"%.1e", tym/1e7);
dc.TextOut(xc + 10, yc + t + 10, str);
}
CPen* oldPen = dc.SelectObject(&pen);
// Побудова графіка
for (x = x1; x <= x2; x += h)
{
y = cosh(x);
if (x == x1)
{
dc.MoveTo(x*kx - x0, -y*ky - y0);
}
dc.LineTo(x*kx - x0, -y*ky - y0);
}
dc.SelectObject(oldPen);
//====================================================
// Анімація
//====================================================
double angle; // Кут повороту
double dx, dy;
double dxc1, dyc1, dxc2, dyc2, dxc3, dyc3, dxc4, dyc4;
double radius = 90.0, radius_small = 14.0;
// Час паузи
int slp_tm_crcl = 50, // для кола зі спицями
slp_tm_pndlm = 1; // для підвісу маятника
bool sequel = false; // Умова для виходу із циклу
int counter = 0; // Кількість повторень анімації
h = 0.1; // Крок
CPen Black_pen(PS_SOLID, 1, RGB(0, 0, 0));
CPen White_pen(PS_SOLID, 1, RGB(255, 255, 255));
CPen Blue_pen(PS_SOLID, 1, RGB(0, 0, 255));
xc = rect.right - 200; // Центри координат для
yc = rect.top + 40; // побудови рухомого зображення
dc.Rectangle(rect.right - 350, rect.top + 20, rect.right - 50, rect.top + 200); dc.MoveTo(xc, yc);
while (!sequel)
{
// Рух маятника у додатньому напрямку
for (angle = 30 * M_PI / 180; angle <= 150 * M_PI / 180; angle += h / 5)
{
dx = xc - (radius) * cos(angle);
dy = yc + radius * sin(angle);
dc.SelectObject(&Black_pen);
dc.MoveTo(xc, yc);
dc.LineTo(dx, dy);
dc.MoveTo(xc, yc);
dc.SelectObject(&Blue_pen);
dc.MoveTo(xc, yc);
for (double angle_small = 0; angle_small <= 120 * M_PI / 180;
angle_small += 0.9)
{
dxc1 = radius_small * sin(angle_small - M_PI / 2) + dx;
dyc1 = -radius_small * cos(angle_small - M_PI / 2) + dy;
dxc2 = radius_small * sin(angle_small + M_PI) + dx;
dyc2 = -radius_small * cos(angle_small + M_PI) + dy;
dxc3 = radius_small * sin(angle_small + M_PI + M_PI + M_PI / 2) + dx;
dyc3 = -radius_small * cos(angle_small + M_PI + M_PI + M_PI / 2) + dy;
dxc4 = radius_small * sin(angle_small + M_PI * 2) + dx;
dyc4 = -radius_small * cos(angle_small + M_PI * 2) + dy;
dc.Ellipse(dx - radius_small, dy - radius_small, dx + radius_small,
dy + radius_small);
dc.MoveTo(dx, dy); dc.LineTo(dxc1, dyc1);
dc.MoveTo(dx, dy); dc.LineTo(dxc2, dyc2);
dc.MoveTo(dx, dy); dc.LineTo(dxc3, dyc3);
dc.MoveTo(dx, dy); dc.LineTo(dxc4, dyc4);
Sleep(slp_tm_crcl);
}
Sleep(slp_tm_pndlm);
dc.SelectObject(&White_pen);
dc.MoveTo(xc, yc);
dc.LineTo(dx, dy);
dc.MoveTo(xc, yc);
for (double angle_small = 0; angle_small <= 120 * M_PI / 180;
angle_small += 0.9)
{
dxc1 = radius_small * sin(angle_small - M_PI / 2) + dx;
dyc1 = -radius_small * cos(angle_small - M_PI / 2) + dy;
dxc2 = radius_small * sin(angle_small + M_PI) + dx;
dyc2 = -radius_small * cos(angle_small + M_PI) + dy;
dxc3 = radius_small * sin(angle_small + M_PI + M_PI + M_PI / 2) + dx;
dyc3 = -radius_small * cos(angle_small + M_PI + M_PI + M_PI / 2) + dy;
dxc4 = radius_small * sin(angle_small + M_PI * 2) + dx;
dyc4 = -radius_small * cos(angle_small + M_PI * 2) + dy;
dc.Ellipse(dx - radius_small, dy - radius_small, dx + radius_small,
dy + radius_small);
dc.MoveTo(dx, dy); dc.LineTo(dxc1, dyc1);
dc.MoveTo(dx, dy); dc.LineTo(dxc2, dyc2);
dc.MoveTo(dx, dy); dc.LineTo(dxc3, dyc3);
dc.MoveTo(dx, dy); dc.LineTo(dxc4, dyc4);
}
}
// Рух маятника у від'ємному напрямку
for (angle = 150 * M_PI / 180; angle > 30 * M_PI / 180; angle -= h / 5)
{
dx = xc - radius * cos(angle);
dy = yc + radius * sin(angle);
dc.SelectObject(&Black_pen);
dc.MoveTo(xc, yc);
dc.LineTo(dx, dy);
dc.MoveTo(xc, yc);
dc.SelectObject(&Blue_pen);
dc.MoveTo(xc, yc);
for (double angle_small = 0; angle_small <= 120 * M_PI / 180;
angle_small += 0.9)
{
dxc1 = radius_small * sin(angle_small - M_PI / 2) + dx;
dyc1 = -radius_small * cos(angle_small - M_PI / 2) + dy;
dxc2 = radius_small * sin(angle_small + M_PI) + dx;
dyc2 = -radius_small * cos(angle_small + M_PI) + dy;
dxc3 = radius_small * sin(angle_small + M_PI + M_PI + M_PI / 2) + dx;
dyc3 = -radius_small * cos(angle_small + M_PI + M_PI + M_PI / 2) + dy;
dxc4 = radius_small * sin(angle_small + M_PI * 2) + dx;
dyc4 = -radius_small * cos(angle_small + M_PI * 2) + dy;
dc.Ellipse(dx - radius_small, dy - radius_small, dx + radius_small,
dy + radius_small);
dc.MoveTo(dx, dy); dc.LineTo(dxc1, dyc1);
dc.MoveTo(dx, dy); dc.LineTo(dxc2, dyc2);
dc.MoveTo(dx, dy); dc.LineTo(dxc3, dyc3);
dc.MoveTo(dx, dy); dc.LineTo(dxc4, dyc4);
Sleep(slp_tm_crcl);
}
Sleep(slp_tm_pndlm);
dc.SelectObject(&White_pen);
dc.MoveTo(xc, yc);
dc.LineTo(dx, dy);
dc.MoveTo(xc, yc);
for (double angle_small = 0; angle_small <= 120 * M_PI / 180;
angle_small += 0.9)
{
dxc1 = radius_small * sin(angle_small - M_PI / 2) + dx;
dyc1 = -radius_small * cos(angle_small - M_PI / 2) + dy;
dxc2 = radius_small * sin(angle_small + M_PI) + dx;
dyc2 = -radius_small * cos(angle_small + M_PI) + dy;
dxc3 = radius_small * sin(angle_small + M_PI + M_PI + M_PI / 2) + dx;
dyc3 = -radius_small * cos(angle_small + M_PI + M_PI + M_PI / 2) + dy;
dxc4 = radius_small * sin(angle_small + M_PI * 2) + dx;
dyc4 = -radius_small * cos(angle_small + M_PI * 2) + dy;
dc.Ellipse(dx - radius_small, dy - radius_small, dx + radius_small,
dy + radius_small);
dc.MoveTo(dx, dy); dc.LineTo(dxc1, dyc1);
dc.MoveTo(dx, dy); dc.LineTo(dxc2, dyc2);
dc.MoveTo(dx, dy); dc.LineTo(dxc3, dyc3);
dc.MoveTo(dx, dy); dc.LineTo(dxc4, dyc4);
}
}
if (counter == 3) sequel = true;
counter++;
}
dc.SelectObject(&oldPen);
}
6. Результат роботи програми:
/
7. Висновок:
Виконавши завдання даної роботи я набув практичних навиків в складанні програми для побудови зображень на екрані комп’ютера в середовищі Microsoft Visual Studio C++ 2013.
Ключовим моментом у програмуванні анімації є точне визначення матриць руху чи обертання реперних точок фігури та максимально точне обчислення координат подальшого положення фігури з метою уникнення похибок, які можуть суттєво погіршити результат. Суть анімації – зміна положення предмета з “затиранням” попереднього положення.