Serwis Edukacyjny
Nauczycieli
w I-LO w Tarnowie

obrazek

Materiały dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek
Uaktualniono: 31.07.2022

©2024 mgr Jerzy Wałaszek
I LO w Tarnowie

Macierze

Podrozdziały

Definicje

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.


Na początek:  podrozdziału   strony 

Operacje na macierzach

Macierze są rozszerzeniem pojęcia liczby i, podobnie jak na liczbach, na macierzach można wykonywać różne operacje arytmetyczne.

Mnożenie przez skalar

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

Dodawanie macierzy

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

Mnożenie macierzy

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:

Algorytm mnożenia macierzy

Wejście:

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

Wyjście:

Macierz C = A × B

Zmienne pomocnicze:

i, j, k  –  indeksy elementów macierzy, i, j, k  ∈ N
s  – suma częściowa, s  ∈ R

Lista kroków:

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

Na początek:  podrozdziału   strony 

Przekształcenia na płaszczyźnie

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ę:

Translacja

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

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.

Rotacja

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:

Podsumowanie:

Punkt:

Translacja:

Skalowanie:

Rotacja:


Na początek:  podrozdziału   strony 

Składanie przekształceń

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ń: T1R  → 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;
}

Na początek:  podrozdziału   strony 

Przydatne macierze przekształceń

Oto kilka przydatnych przekształceń:

Symetria względem osi OX

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 względem osi OY

Symetria środkowa

Symetria środkowa jest złożeniem obu powyższych symetrii:

Pochylenie wzdłuż osi x

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:

Pochylenie wzdłuż osi OY

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:


Na początek:  podrozdziału   strony 

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: i-lo@eduinf.waw.pl

Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.