Koło informatyczne 2013

Przekształcenia macierzowe na płaszczyźnie.

Obiekt na płaszczyźnie składa się z ciągu wierzchołków połączonych liniami - tworzy tzw. łamaną. Wierzchołki są obiektami typu TPoint. Cała łamana jest tablicą typu TPoint, której elementy są poszczególnymi wierzchołkami. Na początek zdefiniujmy odpowiednią figurę:

 

obrazek

Figura składa się z następujących wierzchołków (wierzchołek v7 jest równy wierzchołkowi v0, aby otrzymać łamaną zamkniętą):

 

v0 (50,0)
v1 (50,50)
v2 (0,50)
v3 (0,0)
v4 (-50,0)
v5 (-50,-50)
v6 (0,-50)
v7 (50,0)

Przy przekształceniach punkty będziemy reprezentować wektorem:

 

[x,y,1]

 

Jest to macierz jednowierszowa o trzech kolumnach.

 

Translacja

Translacja jest przesunięciem wszystkich wierzchołków obiektu o odległość Tx w osi OX i Ty w osi OY.

 

obrazek

 

W ten sposób wierzchołek P(x,y) przechodzi w wierzchołek P'(x',y'), gdzie:

 

x' = x + Tx
y' = y + Tx

 

Gdyby nam chodziło jedynie o translację, to właściwie sprawę przekształcenia mamy już załatwioną. Jednakże będziemy chcieli składać ze sobą kilka przekształceń, a tutaj bardzo wygodny staje się rachunek macierzowy. Najpierw tworzymy tzw. macierz translacji o postaci:

 

    1 0  0  
T3 x 3   0 1  0  
    Tx  Ty  1  

 

Następnie z każdego wierzchołka figury tworzymy wektor [x,y,1] i mnożymy go przez macierz przekształcenia:

 

    1 0  0  
[x,y,1] x    0 1  0  
    Tx  Ty  1  

 

Wynikiem tego mnożenia jest wektor [x',y',1], który oznacza wierzchołek przesunięty o Tx i Ty wzdłuż odpowiednich osi. Otrzymane wyniki zapamiętujemy w tablicy wierzchołków docelowych, która posłuży do narysowania figury w oknie. Zasada tworzenia animacji jest następująca:

  1. Tworzymy tablicę V definiującą figurę, która nie będzie zmieniana - posłuży jako obiekt wyjściowy
  2. Tworzymy macierz przekształcenia na podstawie ustalonych parametrów
  3. Każdy wierzchołek V zamieniamy w wektor i przemnażamy przez macierz przekształcenia. Wyniki zapisujemy w tablicy DV.
  4. Wykorzystujemy tablicę DV do narysowania obiektu.

Utwórz projekt w C++ Builder. Na oknie umieść Timer.

 

Form1

BorderIconsbiMaximize = false
Okno nie będzie można powiększać na cały ekran.

BorderStyle = bsSingle
Użytkownik nie będzie mógł zmieniać rozmiarów okna przez przeciąganie myszką jego krawędzi.

Caption = Przekształcenia 2D
Tytuł okna.

ClientHeight = 512
Wysokość obszaru graficznego okna. Taką wysokość będzie miała nasza powierzchnia rysunkowa Canvas.

ClientWidth = 512
Szerokość obszaru graficznego okna.

Color = clBlack
Kolor okna.

Name = frm2D
Pod tą nazwą będzie dostępna w programie zmienna klasy naszego okna.

Position = poScreenCenter
Okienko aplikacji będzie się pojawiało na środku ekranu.

Timer1

Interval = 20
Czas w ms pomiędzy kolejnymi zdarzeniami onTimer.

Name = tmrAnimate

 

Teraz tworzymy zmienne globalne. Definicje umieszczamy na początku programu przed wszystkimi funkcjami.

 

//Zmienne globalne

// Definicja obiektu

TPoint V[] =  {Point(50,0),
               Point(50,50),
               Point(0,50),
               Point(0,0),
               Point(-50,0),
               Point(-50,-50),
               Point(0,-50),
               Point(50,0)};

// Obiekt do rysowania na ekranie

TPoint DV[8];

// Macierz przekształcenia

double G[3][3];

// Parametry ruchu

int Tx = 0;
int Ty = 0;

int dx = 2;
int dy = 3;

 

Teraz kilka funkcji pomocniczych:

 

void Identity()
// Funkcja ustawia macierz jednostkową w macierzy przekształcenia.
// Służy ona do wyzerowania poprzednich przekształceń i stworzenia
// przekształcenia tożsamosciowego.
{
  G[0][0] = G[1][1] = G[2][2] = 1;
  G[0][1] = G[0][2] = G[1][0] = G[1][2] = G[2][0] = G[2][1] = 0;
}

void Multiply(double X[][3])
// Mnoży macierz G przez macierz X. Słuzy do składania przekształceń
{
  double C[3][3]; // macierz pomocnicza
  int i,j;

  for(i = 0; i < 3; i++)
    for(j = 0; j < 3; j++)
      C[i][j] = G[i][0] * X[0][j] + G[i][1] * X[1][j] + G[i][2] * X[2][j];

  for(i = 0; i < 3; i++)
    for(j = 0; j < 3; j++)
      G[i][j] = C[i][j];
}

void Translate(double Tx, double Ty)
// Tworzy macierz translacji T, a następnie mnoży G przez T.
// W ten sposób translacja składa się z przekształceniem w G.
{
  double T[3][3];

  T[0][0] = T[1][1] = T[2][2] = 1;
  T[0][1] = T[0][2] = T[1][0] = T[1][2] = 0;
  T[2][0] = Tx;
  T[2][1] = Ty;
  Multiply(T);
}

void SetDV()
// Tworzy DV na podstawie V i macierzy przekształcenia G
{
  int x,y,i;

  for(i = 0; i < 8; i++)
  {
    x = V[i].x;
    y = V[i].y;
    DV[i].x = x * G[0][0] + y * G[1][0] + G[2][0];
    DV[i].y = x * G[0][1] + y * G[1][1] + G[2][1];
  }
}

 

Utwórz funkcję obsługi zdarzenia onTimer dla tmrAnimate i wpisz kod:

 

void __fastcall Tfrm2D::tmrAnimateTimer(TObject *Sender)
{
  int t;
  Identity();       // zerujemy przekształcenie
  Translate(Tx,Ty); // translacja
  SetDV();          // ustawiamy obiekt do wyświetlenia
  t = Tx + dx;      // modyfikujemy parametry
  if(t < 0 || t > ClientWidth) dx = -dx;
  Tx += dx;
  t = Ty + dy;
  if(t < 0 || t > ClientHeight) dy = -dy;
  Ty += dy;
  Invalidate();     // okno wymaga przerysowania
}

 

Utwórz funkcję obsługi zdarzenia onCreate dla frm2D i wpisz kod:

 

void __fastcall Tfrm2D::FormCreate(TObject *Sender)
{
  DoubleBuffered = true;
  tmrAnimateTimer(Sender);
}

 

Utwórz funkcję obsługi zdarzenia onPaint dla frm2D i wpisz kod:

 

void __fastcall Tfrm2D::FormPaint(TObject *Sender)
{
  Canvas->Pen->Color = clYellow;
  Canvas->Brush->Color = clRed;
  Canvas->Polygon(DV,7);
}

 

Aplikację możesz skompilować i uruchomić. Będzie wykonywała prostą animację

 

Rotacja

Rotacja polega na obróceniu wszystkich wierzchołków o zadany kat wokół środka układu współrzędnych.

 

obrazek

 

Zadanie to realizujemy prosto mnożąc wektory [x,y,1] przez macierz rotacji:

 

    cosφ  sinφ  0  
R3 x 3   -sinφ  cosφ  0  
    0 0  1  

 

Jeśli chcemy złożyć ze sobą kilka przekształceń, to macierz całkowitego przekształcenia jest iloczynem macierzy przekształceń. Na przykład, mnożąc macierz rotacji przez macierz translacji uzyskamy przekształcenie, które obraca obiekt o zadany kat, a następnie przemieszcza go w wybrane miejsce płaszczyzny graficznej.

Na początku programu wpisz

 

#include <math.h>

 

Do zmiennych globalnych dodaj

 

double fi = 0;
double dfi = 0.05;

 

Dodaj funkcję tworzenia rotacji:

 

void Rotate(double fi)
// Tworzy macierz rotacji R, a następnie mnoży G przez R.
// W ten sposób rotacja składa się z przekształceniem w G.
{
  double R[3][3];
  R[0][0] = R[1][1] = cos(fi);
  R[0][1] = sin(fi);
  R[1][0] = -R[0][1];
  R[2][2] = 1;
  R[0][2] = R[1][2] = R[2][0] = R[2][1] = 0;
  Multiply(R);
}

 

Zmień funkcję obsługi zdarzenia onTimer:

 

void __fastcall Tfrm2D::tmrAnimateTimer(TObject *Sender)
{
  int t;
  Identity();       // zerujemy przekształcenie
  Rotate(fi);       // rotacja
  Translate(Tx,Ty); // translacja
  SetDV();          // ustawiamy obiekt do wyświetlenia
  t = Tx + dx;      // modyfikujemy parametry
  if(t < 0 || t > ClientWidth) dx = -dx;
  Tx += dx;
  t = Ty + dy;
  if(t < 0 || t > ClientHeight) dy = -dy;
  Ty += dy;
  fi += dfi;
  if(fi > 6.28) fi = 0;
  Invalidate();     // okno wymaga przerysowania
}

 

Skompiluj i uruchom aplikację. Oprócz ruchu po okienku nasz obiekt wykonuje dodatkowo obroty. Sprawdź, co się stanie, gdy zamienisz miejscami wywołanie Rotate() z Translate(). Czy potrafisz to wytłumaczyć? Jak widzisz, kolejność składania przekształceń ma znaczenie.

 

Skalowanie

Skalowanie polega na przemnażaniu współrzędnych x i y odpowiednio przez współczynniki skali Sx w osi OX oraz Sy w Osi SY.

 

obrazek

 

Jeśli Sx = Sy, to obiekt rośnie równomiernie. Inaczej zostaje ścieśniony lub rozciągnięty wzdłuż określonej osi. Do skalowania służy macierz:

 

    Sx 0  0  
S3 x 3   0 Sy  0  
    0 0  1  

 

Skalowanie można łączyć z pozostałymi przekształceniami przez proste mnożenie macierzy.

Do zmiennych globalnych dodaj

 

double Sx = 1;
double dSx = 0.02;
double Sy = 1;
double dSy = 0.03;

 

Dodaj funkcję tworzenia skalowania:

 

void Scale(double sx, double sy)
// Tworzy macierz skalowania S, a następnie mnoży G przez S.
// W ten sposób skalowanie składa się z przekształceniem w G
{
  double S[3][3];
  S[0][0] = sx;
  S[1][1] = sy;
  S[2][2] = 1;
  S[0][1] = S[0][2] = S[1][0] = S[1][2] = S[2][0] = S[2][1] = 0;
  Multiply(S);
}

 

Zmień funkcję obsługi zdarzenia onTimer:

 

void __fastcall Tfrm2D::tmrAnimateTimer(TObject *Sender)
{
  int t;
  Identity(); // zerujemy przekształcenie
  Scale(Sx,Sy); // skalowanie
  Rotate(fi); // rotacja
  Translate(Tx,Ty); // translacja
  SetDV(); // ustawiamy obiekt do wyświetlenia
  t = Tx + dx; // modyfikujemy parametry
  if(t < 0 || t > ClientWidth) dx = -dx;
  Tx += dx;
  t = Ty + dy;
  if(t < 0 || t > ClientHeight) dy = -dy;
  y += dy;
  fi += dfi;
  if(fi > 6.28) fi = 0;
  if(Sx < 0.1 || Sx > 5) dSx = -dSx;
  if(Sy < 0.1 || Sy > 5) dSy = -dSy;
  Sx += dSx;
  Sy += dSy;
  Invalidate(); // okno wymaga przerysowania
}

 

Skompiluj i uruchom aplikację. Pozmieniaj kolejność przekształceń i sprawdź wynik.

 

Ścinanie

Ścinanie (ang. shear) jest przekształceniem, które przesuwa punkt P wzdłuż osi OX lub OY o odległość zależną od drugiej współrzędnej i współczynnika ścinania wzdłuż tej osi:

x' = x + kx • y
y' = y + ky • x

obrazek

Macierz przekształcenia jest następująca:

    1 ky  0  
SS 3 x 3   kx 1  0  
    0 0  1  

Jeśli jeden z współczynników kx  lub ky  jest równy 0, to ścinanie następuje tylko wzdłuż jednej z osi, dla której współczynnik ścinania ma wartość różną od zera.

Zaprojektuj odpowiednią funkcję dla ścinania i dołącz ją do programu. Współczynniki kx i ky mogą się zmieniać wg podobnych zasad jak pozostałe.


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe