Serwis Edukacyjny Nauczycieli w I-LO w Tarnowie Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej
Autor artykułu: mgr Jerzy Wałaszek |
©2024 mgr Jerzy Wałaszek
|
Podrozdziały |
W grafice komputerowej intensywnie wykorzystuje się struktury danych zwane macierzami. Zanim do nich przejdziemy, podamy kilka podstawowych definicji.
W języku C++ mamy strukturę danych zwaną tablicą (ang. array). Tablica jest obiektem złożonym z ciągu obiektów tego samego typu zwanych elementami. Tablicę definiujemy następująco:
typ_elementów nazwa_tablicy[liczba_elementów];
Na przykład, poniższa definicja tworzy tablicę liczb całkowitych, która nazywa się T i zawiera 10 elementów, każdy będący liczbą całkowitą:
int T[10];
Dostęp do elementów tablicy następuje poprzez nazwę tablicy oraz numer elementu, zwany indeksem. Indeks podajemy za nazwą tablicy w klamerkach. W języku C++ indeks pierwszego elementu ma zawsze wartość 0. Indeks ostatniego elementu jest zatem o 1 mniejszy od liczby elementów w tablicy. Dla tablicy zdefiniowanej powyżej mamy następujące elementy:
T[0] T[1] T[2] ... T[8] T[9]
Element T[10] nie występuje w tej tablicy.
Elementy tablicy są normalnymi zmiennymi i można z nimi robić wszystko to, co ze zmienną. Poniższy program wypełnia tablicę T kolejnymi liczbami parzystymi i wykonuje na jej elementach kilka działań arytmetycznych.
C++// Tablica - 1 // (C)2020 mgr Jerzy Wałaszek // I LO w Tarnowie #include <iostream> #include <iomanip> using namespace std; int main( ) { int T[10]; // Tablica 10 elementów typu int int i; // Indeks elementów // Wypełniamy tablicę liczbami parzystymi 2, 4, 6, ... for(i = 0; i < 10; i++) T[i] = 2 + 2 * i; // Wyświetlamy elementy tablicy for(i = 0; i < 10; i++) cout << setw(4) << T[i]; cout << endl; // Mnożymy każdy element przez 3 for(i = 0; i < 10; i++) T[i] *= 3; // Wyświetlamy elementy tablicy for(i = 0; i < 10; i++) cout << setw(4) << T[i]; cout << endl; // Sumujemy elemnty i-ty z (i+1)-szym i wynik umieszczamy w i-tym for(i = 0; i < 9; i++) T[i] += T[i+1]; // Wyświetlamy elementy tablicy for(i = 0; i < 10; i++) cout << setw(4) << T[i]; cout << endl; return 0; } |
Wynik |
2 4 6
8 10 12 14 16 18 20 6 12 18 24 30 36 42 48 54 60 18 30 42 54 66 78 90 102 114 60 |
Elementem tablicy może być tablica. Powstaje wtedy tablica dwuwymiarowa. Tablica 2-wymiarowa posiada n wierszy i m kolumn. Tablicę dwuwymiarową definiujemy następująco:
typ_elementów nazwa_tablicy[liczba_wierszy][liczba_kolumn];
Poniższa definicja tworzy tablicę 2-wymiarową o 4 wierszach i 5 kolumnach, która przechowuje liczby typu int:
int T[4][5];
Indeksy wierszy i kolumn w języku C++ rozpoczynają się od 0. Dostęp do elementów takiej tablicy odbywa się za pomocą dwóch indeksów w osobnych klamrach:
T[0][0] T[0][1] T[0][2] T[0][3] T[0][4] T[1][0] T[1][1] T[1][2] T[1][3] T[1][4] T[2][0] T[2][1] T[2][2] T[2][3] T[2][4] T[3][0] T[3][1] T[3][2] T[3][3] T[3][4]
Poniższy program tworzy tablicę dwuwymiarową i wykonuje na niej podobne operacje jak program poprzedni
C++// Tablica - 2 // (C)2020 mgr Jerzy Wałaszek // I LO w Tarnowie #include <iostream> #include <iomanip> using namespace std; int main( ) { int T[4][10]; // Tablica 2-wymiarowa o 4 wierszach i 10 kolumnach int i,j; // Indeksy elementów int c; // Wypełniamy tablicę liczbami parzystymi 2, 4, 6, ... c = 0; for(i = 0; i < 4; i++) for(j = 0; j < 10; j++) T[i][j] = c += 2; // Wyświetlamy elementy tablicy for(i = 0; i < 4; i++) { for(j = 0; j < 10; j++) cout << setw(4) << T[i][j]; cout << endl; } cout << endl; // Mnożymy każdy element przez 3 for(i = 0; i < 4; i++) for(j = 0; j < 10; j++) T[i][j] *= 3; // Wyświetlamy elementy tablicy for(i = 0; i < 4; i++) { for(j = 0; j < 10; j++) cout << setw(4) << T[i][j]; cout << endl; } cout << endl; // Sumujemy elemnty i-tego wiersz z elemntami wiersza (i+1)-szego for(i = 0; i < 3; i++) for(j = 0; j < 10; j++) T[i][j] += T[i+1][j]; // Wyświetlamy elementy tablicy for(i = 0; i < 4; i++) { for(j = 0; j < 10; j++) cout << setw(4) << T[i][j]; cout << endl; } cout << endl; return 0; } |
Wynik |
2 4 6
8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 6 12 18 24 30 36 42 48 54 60 66 72 78 84 90 96 102 108 114 120 126 132 138 144 150 156 162 168 174 180 186 192 198 204 210 216 222 228 234 240 72 84 96 108 120 132 144 156 168 180 192 204 216 228 240 252 264 276 288 300 312 324 336 348 360 372 384 396 408 420 186 192 198 204 210 216 222 228 234 240 |
Więcej na temat tablic znajdziesz w osobnym artykule.
Macierz (ang. matrix) jest tablicą dwuwymiarową. W grafice komputerowej szczególnie będą nas interesowały macierze kwadratowe. Macierz kwadratowa (ang. square matrix) jest tablicą 2-wymiarową o takiej samej liczbie wierszy i kolumn. Liczba wierszy/kolumn jest w tym przypadku zwana stopniem macierzy (ang. matrix order).
Np. macierz stopnia trzeciego posiada 3 wiersze i 3 kolumny. Poniższy zapis jest zapisem matematycznym. Indeksy elementów dopasowaliśmy do języka C++, w którym będziemy pisać programy.
W języku C++ taką macierz definiujemy następująco (zakładamy elementy typu int):
int a[3][3];
Macierz zawiera następujące elementy:
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] a[2][0] a[2][1] a[2][2]
Wszystkie elementy macierzy o równych indeksach wiersza i kolumny tworzą przekątną główną (ang. main diagonal). W naszym przykładzie są to:
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] a[2][0] a[2][1] a[2][2]
Macierz zerowa (ang. zero/null matrix) ma wszystkie elementy równe 0:
Macierz jednostkowa (ang. identity matrix) ma elementy głównej przekątnej równe 1, a wszystkie pozostałe równe 0:
W grafice 2-wymiarowej stosuje się macierze stopnia trzeciego. W grafice 3-wymiarowej stosowane są macierze stopnia czwartego. Więcej na temat macierzy znajdziesz w osobnym artykule. Tutaj zajmiemy się tylko tym, co będzie nam potrzebne w grafice.
Macierze są rozszerzeniem pojęcia liczby i, podobnie jak na liczbach, na macierzach można wykonywać różne operacje arytmetyczne.
Skalar jest pojedynczą liczbą. Mnożenie przez skalar polega na przemnożeniu każdego elementu macierzy przez tę liczbę:
Poniższy program tworzy macierz trzeciego stopnia, wypełnia ją liczbami losowymi i przemnaża przez liczbę losową. Zwróć uwagę na sposób przekazania macierzy w parametrze funkcji. Macierze są zawsze przekazywane przez referencję.
C++// Macierze - Mnożenie przez skalar // (C)2020 mgr Jerzy Wałaszek // I LO w Tarnowie #include <iostream> #include <cstdlib> #include <ctime> #include <iomanip> using namespace std; // Funkcja wyświetla zawartość macierzy 3x3 //----------------------------------------- void print_matrix(int a[][3]) { int i,j; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) cout << setw(4) << a[i][j]; cout << endl; } cout << endl; } // Funkcja mnoży macierz 3x3 przez liczbę //--------------------------------------- void mul_const(int c, int a[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) a[i][j] *= c; } int main( ) { int A[3][3]; // Macierz int c; // Skalar int i,j; // Indeksy // Inicjujemy generator pseudolosowy srand(time(NULL)); // Losujemy skalar od 2 do 8 c = 2 + rand() % 7; cout << "c = " << c << endl << "A:" << endl; // Losujemy elementy macierzy od 0 do 99 for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) A[i][j] = rand() % 100; print_matrix(A); cout << "A <- " << c << " x A" << endl << "A:" << endl; mul_const(c,A); print_matrix(A); return 0; } |
Wynik |
c = 4 A: 5 45 74 64 56 9 26 84 48 A <- 4 x A A: 20 180 296 256 224 36 104 336 192 |
Dodawane macierze muszą posiadać tę samą liczbę wierszy i kolumn. Macierz wynikowa powstaje przez zsumowanie odpowiadających sobie wyrazów obu macierzy i ma rozmiar taki sam jak sumowane macierze:
Poniższy program tworzy dwie macierze A i B stopnia trzeciego. Macierze A i B wypełnia liczbami pseudolosowymi, po czy w macierzy A tworzy sumę macierzy A i B.
C++// Macierze - Dodawanie macierzy // (C)2020 mgr Jerzy Wałaszek // I LO w Tarnowie #include <iostream> #include <cstdlib> #include <ctime> #include <iomanip> using namespace std; // Funkcja wyświetla zawartość macierzy 3x3 //----------------------------------------- void print_matrix(int a[][3]) { int i,j; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) cout << setw(4) << a[i][j]; cout << endl; } cout << endl; } // Funkcja wypełnia macierz losowymi elementami //--------------------------------------------- void rand_matrix(int a[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) a[i][j] = rand() % 100; } // Funkcja sumuje macierze //------------------------ void sum_matrix(int a[][3], int b[][3], int c[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) c[i][j] = a[i][j] + b[i][j]; } int main( ) { int A[3][3],B[3][3]; // Macierze // Inicjujemy generator pseudolosowy srand(time(NULL)); // Losujemy macierze rand_matrix(A); rand_matrix(B); // Wyświetlamy macierze cout << "A:" << endl; print_matrix(A); cout << "B:" << endl; print_matrix(B); // Sumujemy macierze sum_matrix(A,B,A); // Wyświetlamy wyniki cout << "A <- A + B" << endl << "A:" << endl; print_matrix(A); return 0; } |
Wynik |
A: 96 0 55 75 47 32 90 89 18 B: 9 45 25 57 91 50 46 89 72 A <- A + B A: 105 45 80 132 138 82 136 178 90 |
W grafice komputerowej mnożenie macierzy wykorzystywane jest intensywnie. Nie jest to operacja tak prosta, jak dwie operacje opisane powyżej, jednak łatwa do zrozumienia. W przypadku mnożenia macierzy nie muszą one posiadać tych samych rozmiarów, jednak nie mogą to być rozmiary dowolne. Zasada jest bardzo prosta. Jeśli mamy dwie macierze A i B, to liczba kolumn macierzy A musi być równa liczbie wierszy macierzy B:
Dlaczego tak musi być, zrozumiesz za chwilę.
Jeśli obie macierze są kwadratowe i mają ten sam stopień, to warunek powyższy jest automatycznie spełniony.
Wynikiem mnożenia dwóch macierzy A i B jest macierz C, która posiada tyle wierszy, ile miała macierz A oraz tyle kolumn, ile miała macierz B:
Po tych wstępnych ustaleniach opiszemy sposób wykonywania mnożenia macierzy.
Każdy element c i, j macierzy C jest sumą iloczynów kolejnych elementów z wiersza i-tego macierzy A przez kolejne elementy z kolumny j-tej macierzy B:
Dlatego macierz A musi posiadać tyle kolumn, ile wierszy posiada macierz B. Wykonajmy przykładowe mnożenie prostych macierzy:
Zastosujemy prostą metodę mnemotechniczną. Macierz A umieszczamy po lewej stronie macierzy C, a macierz B umieszczamy ponad macierzą C:
Aby policzyć wartość c0,0, mnożymy przez siebie kolejne wyrazy wiersza 0 macierzy A przez wyrazy kolumny 0 macierzy B i iloczyny te sumujemy:
Podstawmy wartości liczbowe:
W podobny sposób liczymy wartość c0,1: mnożymy przez siebie kolejne wyrazy wiersza 0 macierzy A przez kolejne wyrazy kolumny 1 macierzy B i sumujemy iloczyny:
Obliczamy:
Operację kontynuujemy, aż obliczymy wszystkie wyrazy macierzy C:
Ostatecznie możemy zapisać:
Algorytm mnożenia dwóch dowolnych macierzy jest następujący:
m, n, p | – | rozmiary macierzy, m, n, p ∈ N |
A | – | macierz o rozmiarze m × n, A ∈ R |
B | – | macierz o rozmiarze n × p, B ∈ R |
C | – | macierz o rozmiarze m × p, C ∈ R |
Macierz C = A × B
i, j, k | – | indeksy elementów macierzy, i, j, k ∈ N |
s | – | suma częściowa, s ∈ R |
K01: | Dla i = 1, 2, ..., m
: wykonuj kroki K02...K05 |
|
K02: | Dla j
= 1, 2, ...,
p : wykonuj kroki K03...K05 |
|
K03: | s ← 0 | zerujemy sumę częściową |
K04: |
Dla k = 1, 2, ..., n : wykonuj: s ← s + A [ i, k ] × B [ k, j ] |
obliczamy sumę iloczynów |
K05: | C [ i, j ] ← s | sumę umieszczamy w elemencie macierzy wynikowej |
K06: | Zakończ |
Poniższy program tworzy trzy macierze kwadratowe A, B, C stopnia trzeciego. Macierze A i B są wypełniane losowymi liczbami, po czym program oblicza iloczyn macierzy A i B, wynik umieszcza w C.
C++// Macierze - Mnożenie macierzy // (C)2020 mgr Jerzy Wałaszek // I LO w Tarnowie #include <iostream> #include <cstdlib> #include <ctime> #include <iomanip> using namespace std; // Funkcja wyświetla zawartość macierzy 3x3 //----------------------------------------- void print_matrix(int a[][3]) { int i,j; for(i = 0; i < 3; i++) { for(j = 0; j < 3; j++) cout << setw(4) << a[i][j]; cout << endl; } cout << endl; } // Funkcja wypełnia macierz losowymi elementami //--------------------------------------------- void rand_matrix(int a[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) a[i][j] = rand() % 10; } // Funkcja mnoży macierze //------------------------ void mul_matrix(int a[][3], int b[][3], int c[][3]) { int i,j,k,s; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) { s = 0; for(k = 0; k < 3; k++ ) s += a[i][k] * b[k][j]; c[i][j] = s; } } int main( ) { int A[3][3],B[3][3],C[3][3]; // Macierze // Inicjujemy generator pseudolosowy srand(time(NULL)); // Losujemy macierze rand_matrix(A); rand_matrix(B); // Wyświetlamy macierze cout << "A:" << endl; print_matrix(A); cout << "B:" << endl; print_matrix(B); // Mnożymy macierze mul_matrix(A,B,C); // Wyświetlamy wynik cout << "C <- A x B" << endl << "C:" << endl; print_matrix(C); return 0; } |
Wynik |
A: 3 8 7 0 7 9 1 6 8 B: 7 2 4 3 5 3 7 5 0 C <- A x B C: 94 81 36 84 80 21 81 72 22 |
Macierze pozwalają w prosty sposób wykonywać różne przekształcenia punktów płaszczyzny (lub przestrzeni w grafice trójwymiarowej), dlatego stały się one tak ważne w grafice komputerowej. Zacznijmy od podstawowych definicji.
Płaszczyzna graficzna posiada dwie osie współrzędnych OX i OY, które służą do określania położenia punktów na płaszczyźnie. Początek układu współrzędnych jest umieszczony w lewym górnym narożniku okna graficznego i oś OY jest skierowana w dół:
Punkty będziemy definiować przez tzw. wektor kolumnowy, czyli przez macierz posiadającą 3 wiersze i 1 kolumnę:
Pierwszy i drugi element kolumny określa współrzędne x i y na płaszczyźnie graficznej. Ostatni element kolumny ma wartość 1 (to taka umowa, która upraszcza operacje graficzne, wyjaśnimy to dalej).
Przykładowy punkt na rysunku powyżej posiada następującą reprezentację:
Pierwszym podstawowym przekształceniem jest translacja. Polega ona na przemieszczeniu punktu wzdłuż osi OX o odległość Tx i wzdłuż osi OY o odległość Ty:
Matematycznie translacja wygląda następująco:
Aby wykorzystać macierze wprowadźmy pojęcie macierzy translacji. Jest to macierz kwadratowa stopnia 3. Macierz translacji mnożymy przez wektor kolumnowy punktu i otrzymujemy w wyniku wektor kolumnowy punktu przekształconego:
Macierz translacji posiada następującą postać:
Taka postać tej macierzy podyktowana jest wzorami mnożenia macierzowego. Dla przykładu wykonajmy takie mnożenie:
Jedynka w ostatnim elemencie kolumny wektora P umożliwia wykonanie translacji poprzez mnożenie macierzy. To, jak zobaczymy za chwilę, ujednolica wykonywanie wszystkich przekształceń.
Skalowanie jest drugim podstawowym przekształceniem punktów płaszczyzny. Polega ono na przemnożeniu współrzędnej x przez współczynnik skali Sx, a współrzędnej y przez współczynnik skali Sy, co zapisujemy matematycznie jako:
Macierz skalowania jest następująca:
Zasada wykonania przekształcenia jest identyczna jak przy translacji: macierz skalowania mnożymy przez wektor kolumnowy punktu:
Dla przykładu wykonajmy takie mnożenie:
Skalowanie wykonywane jest względem początku układu współrzędnych.
Trzecim podstawowym przekształceniem jest rotacja, czyli obrót. Za punkt obrotu wybieramy początek układu współrzędnych:
Kątem obrotu jest φ. r jest odległością punktu P od środka układu współrzędnych. W takim układzie zachodzą następujące związki:
Wykorzystujemy wzory trygonometryczne sumy kątów:
wykonujemy podstawienia:
Pozbywamy się promienia r z równań:
i otrzymujemy wzory na współrzędne punktu po przekształceniu.
Macierz rotacji ma następującą postać:
Przekształcenie wykonujemy standardowo:
Sprawdźmy:
Piękno macierzy leży w tym, iż przekształcenia można w prosty sposób łączyć ze sobą. Weźmy dla przykładu dwie translacje T1 i T2. Chcemy pewien punkt P poddać najpierw translacji T1, a później wynik poddać translacji T2:
Z pierwszej translacji otrzymujemy wzory:
Punkt P' jest punktem wejściowym dla drugiej translacji:
Jeśli złożymy ze sobą translację T1 i T2, to otrzymamy pojedynczą translację T12, która przemieszcza punkt P od razu do punktu P'':
Po dokonaniu podstawień otrzymujemy wzory:
Stąd:
W rachunku macierzowym złożenie przekształceń odpowiada mnożeniu macierzy przekształceń. W wyniku tego mnożenia otrzymujemy macierz przekształcenia złożonego. Sprawdźmy:
Otrzymaliśmy macierz translacji, która odpowiada złożeniu translacji T1 i T2. Sprawdź koniecznie te rachunki.
Składanie przekształceń nie ogranicza się tylko do translacji. Przez mnożenie macierzy możemy uzyskać macierz dowolnie złożonego przekształcenia. Takie podejście ma wiele zalet, np. jeśli przekształcamy w ten sam sposób dużo punktów, to potrzebujemy tylko jednej macierzy przekształcenia, którą wyznaczamy tylko jeden raz.
Jednakże musimy pamiętać o bardzo ważnej rzeczy: kolejności wykonywania przekształceń, co sprowadza się do właściwej kolejności mnożenia macierzy. W przypadku składania translacji nie miało to znaczenia. W przypadku ogólnym macierze mnożymy w kolejności odwrotnej do wykonywanych przekształceń. Zobaczymy to na przykładzie programu.
Poniższy program rysuje serię 1000 punktów w różnych miejscach ekranu, po czym obraca je płynnie wokół środka ekranu. Podstawowa rotacja obraca punkt wokół początku układu współrzędnych. Jeśli chcemy obrócić punkt wokół wybranego punktu C(cx,cy), to musimy dokonać kilku przekształceń:
Pierwsze przekształcenie będzie translacją T1, która przesunie punkt P o wektor (-cx,-cy). Po tej translacji środek obrotu C(cx,cy) znajdzie się w środku układu współrzędnych. Punkt P przechodzi w punkt P':
Drugie przekształcenie jest obrotem R o kąt φ wokół środka układu współrzędnych. Punkt P' przechodzi w punkt P'':
Trzecie przekształcenie jest translacją odwrotną do T1. Powoduje ona powrót środka obrotu do oryginalnego miejsca. Jednocześnie przenosi punkt P'' do punktu P''':
Punkt P''' jest obrazem punktu P po obrocie o kąt φ wokół środka C:
Ciąg operacji będzie następujący:
Translacja T1:
Obrót R
Translacja T2:
Iloczyn macierzy T2, R i T1 tworzy macierz przekształcenia całkowitego:
Macierz M jest iloczynem macierzy kolejnych przekształceń. Ważna jest kolejność wykonywania mnożeń. Macierz M jest macierzą przekształcenia złożonego z trzech kolejnych przekształceń: T1 → R → T2.
Macierz jednostkowa przy mnożeniu macierzy zachowuje się jak jedynka przy mnożeniu liczb:
Tworzenie macierzy M rozpoczynamy od utworzenia w niej macierzy jednostkowej:
Takie przekształcenie nazywa się przekształceniem tożsamościowym, ponieważ nie zmienia ono położenia punktu:
Dodawanie kolejnych przekształceń będzie polegało na mnożeniu macierzy przekształcenia przez macierz M i umieszczeniu wyniku w macierzy M. W ten sposób można w M skumulować dowolne przekształcenie:
Zwróć uwagę na odwróconą kolejność przekształceń w macierzy M.
Program pracuje wg opisanych powyżej zasad. Przeczytaj uważnie komentarze.
C++// Macierze // Składanie przekształceń //------------------------- #include <SDL.h> #include <iostream> #include <cmath> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba punktów const int N = 1000; // 2 x pi const double PI2 = M_PI + M_PI; // Przyrost kąta obrotu const double DPHI = M_PI / 500; // Funkcja mnoży macierze //------------------------ void mul_matrix(double a[][3],double b[][3],double c[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; } // Funkcja tworzy macierz jednostkową //----------------------------------- void I_matrix(double I[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) if(i == j) I[i][j] = 1.0; else I[i][j] = 0.0; } // Funkcja kopiuje macierze //------------------------- void copy_matrix(double a[][3],double b[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) b[i][j] = a[i][j]; } // Funkcja dodaje macierz translacji //---------------------------------- void T_matrix(double M[][3],double Tx, double Ty) { double A[3][3],T[3][3]; I_matrix(T); T[0][2] = Tx; T[1][2] = Ty; mul_matrix(T,M,A); copy_matrix(A,M); } // Funkcja dodaje macierz rotacji //------------------------------- void R_matrix(double M[][3],double phi) { double A[3][3],R[3][3]; double sx = sin(phi); I_matrix(R); R[0][0] = R[1][1] = cos(phi); R[0][1] = -sx; R[1][0] = sx; mul_matrix(R,M,A); copy_matrix(A,M); } // Funkcja wylicza współrzędną x z iloczynu macierzy // M - macierz przekształcenia // x,y - współrzędne przekształcanego punktu //-------------------------------------------------- int get_x(double M[][3],double x, double y) { return M[0][0] * x + M[0][1] * y + M[0][2]; } // Funkcja wylicza współrzędną y z iloczynu macierzy // M - macierz przekształcenia // x,y - współrzędne przekształcanego punktu //-------------------------------------------------- int get_y(double M[][3],double x, double y) { return M[1][0] * x + M[1][1] * y + M[1][2]; } int main(int argc, char* args[]) { // Inicjujemy SDL if(SDL_Init(SDL_INIT_VIDEO)) { cout << "SDL_Init Error: " << SDL_GetError() << endl; return 1; } // Tworzymy okno SDL_Window * w = SDL_CreateWindow("Zdarzenia: OKNO - TRANSLACJA + ROTACJA", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, SDL_WINDOW_RESIZABLE); if(!w) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(w); SDL_Quit(); return 3; } // Współrzędne ekranowe punktów int x[N],y[N]; // Kolory punktów int cr[N],cg[N],cb[N]; // Kąt obrotu double phi = 0.0; // Współrzędne środka obrotu int cx,cy; // Macierz przekształcenia double M[3][3]; // Inicjujemy generator pseudolosowy srand(time(NULL)); // Generujemy współrzędne punktów i kolory punktów int i; // Współrzędne środka obrotu cx = W_W / 2; cy = W_H / 2; for(i = 0; i < N; i++) { x[i] = cx / 2 + rand() % cx; y[i] = cy / 2 + rand() % cy; cr[i] = rand() & 0xff; cg[i] = rand() & 0xff; cb[i] = rand() & 0xff; } bool running = true; SDL_Event e; while(running) { // Obsługujemy zdarzenia if(SDL_PollEvent(&e)) // Jeśli jest zdarzenie w kolejce, pobieramy go { switch(e.type) // Sprawdzamy rodzaj zdarzenia { case SDL_QUIT: running = false; // To przerwie pętlę break; default: break; } } SDL_Delay(10); // Krótkie opóźnienie // Ustalamy wartość kąta fi phi += DPHI; if(phi > PI2) phi = 0; // Przekształcenie tożsamościowe I_matrix(M); // Dodajemy translację T1 T_matrix(M,-cx,-cy); // Dodajemy rotację R R_matrix(M,phi); // Dodajemy translację T2 T_matrix(M,cx,cy); // Czyścimy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy punkty po przekształceniu for(i = 0; i < N; i++) { int xe,ye; xe = get_x(M,x[i],y[i]); ye = get_y(M,x[i],y[i]); SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawPoint(r,xe,ye); } // Uaktualniamy treść okna SDL_RenderPresent(r); } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Kolejny program wykorzystuje translacje, skalowanie i rotację do utworzenia prostej animacji. W programie używamy figury symbolizującej "asteroidę" Figura zdefiniowana jest za pomocą listy wierzchołków o następujących współrzędnych:
n | x | y | |
P0 | 5 | 9 | |
P1 | 9 | 4 | |
P2 | 3 | 0 | |
P3 | 9 | -1 | |
P4 | 9 | -5 | |
P5 | 5 | -9 | |
P6 | -2 | -9 | |
P7 | -8 | -6 | |
P8 | -9 | 1 | |
P9 | -5 | 9 |
Figura będzie skalowana i obracana wg środka jej układu współrzędnych. Następnie za pomocą translacji zostanie umieszczona w oknie graficznym. Czytaj uważnie komentarze w programie.
C++// Macierze // Składanie przekształceń //------------------------- #include <SDL.h> #include <iostream> #include <cmath> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba figur const int N = 20; // 2 x pi const double PI2 = M_PI + M_PI; // Definicje wierzchołków figury SDL_Point f[] = {{5,9}, {9,4}, {3,0}, {9,-1},{9,-5}, {5,-9},{-2,-9},{-8,-6},{-9,1},{-5,9}}; // Funkcja mnoży macierze //------------------------ void mul_matrix(double a[][3],double b[][3],double c[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j]; } // Funkcja tworzy macierz jednostkową //----------------------------------- void I_matrix(double I[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) if(i == j) I[i][j] = 1.0; else I[i][j] = 0.0; } // Funkcja kopiuje macierze //------------------------- void copy_matrix(double a[][3],double b[][3]) { int i,j; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++) b[i][j] = a[i][j]; } // Funkcja dodaje macierz translacji //---------------------------------- void T_matrix(double M[][3],double Tx, double Ty) { double A[3][3],T[3][3]; I_matrix(T); T[0][2] = Tx; T[1][2] = Ty; mul_matrix(T,M,A); copy_matrix(A,M); } // Funkcja dodaje macierz rotacji //------------------------------- void R_matrix(double M[][3],double phi) { double A[3][3],R[3][3]; double sx = sin(phi); I_matrix(R); R[0][0] = R[1][1] = cos(phi); R[0][1] = -sx; R[1][0] = sx; mul_matrix(R,M,A); copy_matrix(A,M); } // Funkcja dodaje macierz skalowania //---------------------------------- void S_matrix(double M[][3],double sx,double sy) { double A[3][3],S[3][3]; I_matrix(S); S[0][0] = sx; S[1][1] = sy; mul_matrix(S,M,A); copy_matrix(A,M); } // Funkcja wylicza współrzędną x z macierzy przekształcenia // M - macierz przekształcenia // x,y - współrzędne przekształcanego punktu //--------------------------------------------------------- int get_x(double M[][3],double x, double y) { return M[0][0] * x + M[0][1] * y + M[0][2]; } // Funkcja wylicza współrzędną y z iloczynu macierzy // M - macierz przekształcenia // x,y - współrzędne przekształcanego punktu //-------------------------------------------------- int get_y(double M[][3],double x, double y) { return M[1][0] * x + M[1][1] * y + M[1][2]; } int main(int argc, char* args[]) { // Inicjujemy SDL if(SDL_Init(SDL_INIT_VIDEO)) { cout << "SDL_Init Error: " << SDL_GetError() << endl; return 1; } // Tworzymy okno SDL_Window * w = SDL_CreateWindow("Zdarzenia: OKNO - TRANSLACJA + ROTACJA + SKALOWANIE", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, SDL_WINDOW_RESIZABLE); if(!w) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(w,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(w); SDL_Quit(); return 3; } // Zmienne animacji // Figura animowana SDL_Point af[11]; // współrzędne figur int x[N],y[N]; // Przyrosty ruchu figur int dx[N],dy[N]; // Kąty obrotu i przyrosty kąta figur double phi[N],dphi[N]; // Współczynniki skalowania figur int sxy[N]; // Kolory figur int cr[N],cg[N],cb[N]; // Inicjujemy generator pseudolosowy srand(time(NULL)); // Tworzymy dane figur int i,j; for(i= 0; i < N; i++) { // Położenie środków figur w oknie x[i] = 20 + rand() % (W_W - 40); y[i] = 20 + rand() % (W_H - 40); // przyrosty prędkości -10...10 bez zer do dx[i] = -10 + rand() % 21; while(!dx[i]); do dy[i] = -10 + rand() % 21; while(!dy[i]); // Kąty obrotu i przyrosty kąta phi[i] = PI2 / RAND_MAX * rand(); dphi[i] = (-10 + rand() % 21) * PI2 / 1000; // Współczynniki skali 1...10; sxy[i] = 1 + rand() % 10; // Kolory figur cr[i] = rand() & 0xff; cg[i] = rand() & 0xff; cb[i] = rand() & 0xff; } // Macierz przekształcenia double M[3][3]; bool running = true; SDL_Event e; while(running) { // Obsługujemy zdarzenia if(SDL_PollEvent(&e)) // Jeśli jest zdarzenie w kolejce, pobieramy go { switch(e.type) // Sprawdzamy rodzaj zdarzenia { case SDL_QUIT: running = false; // To przerwie pętlę break; default: break; } } SDL_Delay(10); // Krótkie opóźnienie // Animacja // Czyścimy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); for(i = 0; i < N; i++) { // Przygotowujemy macierz przekształcenia I_matrix(M); // M = I // Dodajemy skalowanie S_matrix(M,sxy[i],sxy[i]); // M = S x I // Dodajemy obrót R_matrix(M,phi[i]); // M = R x S x I // Dodajemy przesunięcie T_matrix(M,x[i],y[i]); // M = T x R x S x I // Tworzymy figurę for(j = 0; j < 10; j++) { af[j].x = get_x(M,f[j].x,f[j].y); af[j].y = get_y(M,f[j].x,f[j].y); } af[10] = af[0]; // Ustawiamy kolor figury SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); // Rysujemy figurę SDL_RenderDrawLines(r,af,11); // Uaktualniamy zmienne phi[i] += dphi[i]; if(phi[i] > PI2) phi[i] -= PI2; if(phi[i] < PI2) phi[i] += PI2; x[i] += dx[i]; if((x[i] < 0) || (x[i] > W_W)) { dx[i] = -dx[i]; x[i] += dx[i]; } y[i] += dy[i]; if((y[i] < 0) || (y[i] > W_H)) { dy[i] = -dy[i]; y[i] += dy[i]; } } // Uaktualniamy treść okna SDL_RenderPresent(r); } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Oto kilka przydatnych przekształceń:
Współrzędna x nie zmienia się, współrzędna y otrzymuje wartość przeciwną:
Przekształcenie to otrzymamy w prosty sposób ze skalowania, przyjmując Sx = 1 i Sy = -1. Macierz przekształcenia wygląda zatem następująco:
Symetria środkowa jest złożeniem obu powyższych symetrii:
W tym przekształceniu obraz punktu przesunięty zostaje wzdłuż osi OX na odległość zależną od jego współrzędnej y:
Postarajmy się wyznaczyć macierz tego przekształcenia. Wychodzimy od mnożenia macierzy przekształcenia przez wektor kolumnowy punktu:
Teraz tworzymy układ 3 równań:
Układu tego nie musimy specjalnie rozwiązywać. Prosta analiza przez porównanie strony lewej z prawą daje wynik:
Nasza macierz przekształcenia wygląda zatem następująco:
Punkt zostaje przesunięty wzdłuż osi OY na odległość zależną od jego współrzędnej x:
Macierz przekształcenia wyznaczamy w identyczny sposób jak poprzednio:
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2024 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone
pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.