![]() |
Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2011 mgr
Jerzy Wałaszek
|
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ę:

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.

W ten sposób wierzchołek P(x,y) przechodzi w wierzchołek P'(x',y'), gdzie:
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:
Utwórz projekt w C++ Builder. Na oknie umieść Timer.
Form1
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
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ę

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.

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 (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
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 |
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