Przekształcenia na płaszczyźnie

Powrót do spisu treści

Wymagane jest zapoznanie się z następującymi podrozdziałami:

P019 - Pierwszy program dla Windows
OL031 - instalacja biblioteki SDL w środowisku Dev-C++
OL032 - inicjalizacja biblioteki SDL
OL033 - powierzchnie graficzne w SDL
OL034 - przygotowanie plików własnej biblioteki graficznej
OL035 - rysowanie po powierzchni graficznej
OL036 - algorytm Bresenhama rysowania odcinka
OL037 - obcinanie grafiki do prostokąta
OL038 - podstawowe algorytmy wypełniania obszarów
OL039 - algorytm Smitha
OL040 - praca w środowisku sterowanym zdarzeniami
OL041 - czcionka bitmapowa
OL042 - czcionka wektorowa
OL043 - przyciski poleceń
OL050 - macierze - podstawowe operacje na macierzach

UWAGA!!!

Bieżące opracowanie NIE JEST KURSEM programowania biblioteki SDL. Są to materiały przeznaczone do zajęć na kole informatycznym w I LO w Tarnowie.

Przed pracą z tym rozdziałem utwórz w środowisku Dev-C++ nowy projekt SDL i dołącz do niego pliki SDL_gfx.cpp oraz SDL_gfx.h. Jeśli nie przeczytałeś zalecanych rozdziałów, koniecznie zrób to, a wszystko stanie się jasne. W przeciwnym razie odpuść sobie również ten rozdział. Opis funkcji bibliotecznych znajdziesz w podsumowaniu SDL009.

 

Artykuł nie jest już rozwijany


obrazekNa tej lekcji poznamy uniwersalne sposoby geometrycznego przekształcania (ang. geometric transformation) obiektów dwuwymiarowych (w przestrzeni trójwymiarowej będzie bardzo podobnie) na płaszczyźnie. Najpierw określmy sposób reprezentacji obiektów geometrycznych. Umówmy się, iż obiekt będzie zbudowany z wielokątów. Wielokąty składają się z wierzchołków połączonych liniami. Zastosujemy zatem sposób wykorzystywany przez czcionkę wektorową, który opisaliśmy w rozdziale OL042. Takie rozwiązanie umożliwi również stosowanie naszych przekształceń do liter wektorowych.


Obiekt będzie zbudowany z listy łamanych. Zasady tworzenia tej listy są następujące:

Każda łamana rozpoczyna się od liczby określającej liczbę wierzchołków. Jeśli liczba ta ma wartość 0, to oznacza to koniec listy łamanych. Za liczbą wierzchołków następują pary współrzędnych określających położenie wierzchołka na płaszczyźnie.

Przykład:

3 12 1 14 5 1 1 2 5 3 4 7 0

Ten obiekt składa się z dwóch łamanych:

 3  12 1 14 5 1 1       2  5 3 4 7     0

Pierwsza łamana posiada trzy wierzchołki: (12,1), (14,5) i (1,1).
Druga łamana posiada tylko dwa wierzchołki: (5,3) i (4,7).
Ostatnie zero oznacza koniec listy łamanych.

Listę łamanych można utworzyć w tablicy o odpowiedniej wielkości. Na przykład tak:

  Sint32 obj[] = {3, 12, 1, 14, 5, 1, 1, 2, 5, 3, 4, 7, 0};

Translacja - przesunięcie

Translacja (ang. translation) polega na przesunięciu współrzędnych wszystkich wierzchołków łamanej o wektor (Tx, Ty).

obrazek

Oś OY jest skierowana w dół na powierzchni karty graficznej, pamiętasz? Współrzędne nowych punktów obliczamy wg wzorów:

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

Jeśli w aplikacji stosujemy różne przekształcenia, to takie podejście nie jest wygodne. Lepszym rozwiązaniem będzie zastosowanie rachunku macierzowego, który ułatwi nam składanie przekształceń. Każdy punkt łamanej przekształcamy w macierz P1 x 3 = [x, y, 1] - macierz P możemy potraktować jako współrzędne punktu (x,y) leżącego w przestrzeni trójwymiarowej na płaszczyźnie z=1 - równoległej do osi OX i OY układu współrzędnych. Następnie konstruujemy macierz translacji:

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

Współrzędne (x',y') punktu przesuniętego o wektor (Tx,Ty) uzyskamy mnożąc macierz P przez macierz T:

    1 0  0  
P' = P x T;    [x',y',1] = [x,y,1] x    0 1  0  
    Tx  Ty  1  

Sprawdźmy. Zgodnie z definicją mnożenia macierzy mamy:

x' = x • 1 + y • 0 + 1 • Tx = x + Tx

y' = x • 0 + y • 1 + 1 • Ty = y + Ty

1 = x • 0 + y • 0 + 1 • 1

Rotacja - obrót

obrazek

Jeśli chcemy uzyskać obrót (ang. rotation) punktu P = (x,y) wokół środka układu współrzędnych o kąt φ (podany w radianach), to konstruujemy macierz punktu:

P1 x 3 = [x, y, 1].

Następnie konstruujemy macierz rotacji:

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

Nowe współrzędne otrzymamy mnożąc macierz punktu P przez macierz rotacji R:

    cosφ  sinφ  0  
P' = P x R;   [x',y',1] = [x,y,1] x    -sinφ  cosφ  0  
    0 0  1  

UWAGA!!! - PUŁAPKA.

Ponowne pomnożenie macierzy P' przez R da nam punkty obrócone o kolejny kąt φ. Mogłoby więc się wydawać, że będzie to dobry sposób do tworzenia animacji. Niestety, musimy uwzględnić błędy zaokrągleń. Wyliczone współrzędne nie będą dokładne, lecz przybliżone. Wielokrotne zastosowanie przekształcenia powoduje wzrost błędów i w efekcie nasza figura może się "numerycznie" rozsypać po powierzchni graficznej. Poprawnie robimy to tak:

  • W pamięci przechowujemy oryginalne współrzędne obiektu, których nigdy nie zmieniamy.

  • Przekształcone współrzędne zapamiętujemy w kopii obiektu, która posłuży do narysowania go na powierzchni graficznej.

  • Modyfikujemy odpowiednio macierze przekształcenia i za ich pomocą wyliczamy nowe współrzędne wykorzystując współrzędne oryginalnego obiektu.. Dzięki temu zawsze utrzymamy błędy zaokrągleń na niskim poziomie - nie będą się kumulowały.

Jeśli chcemy obrócić nasz obiekt o kąt φ wokół dowolnego punktu (xR,yR), to musimy zastosować trzy przekształcenia:

  1. Sprowadzić punkt (xR,yR) do początku układu współrzędnych - translacja obiektu o wektor (-xR,-yR)
  2. Obrócić wierzchołki obiektu o kąt φ - rotacja wokół środka układu współrzędnych.
  3. Powrócić do punktu (xR,yR) - translacja o wektor (xR,yR)

Tworzymy zatem trzy macierze, dwie translacji i jedną rotacji:

    1 0  0  
T   0 1  0  
    -xR  -yR  1  
    cosφ  sinφ  0  
R   -sinφ  cosφ  0  
    0 0  1  
    1 0  0  
T-1   0 1  0  
    xR yR  1  

Obliczamy macierz przekształcenia G = T x R x T-1 (operację można rozbić: G = T x R; G = G x T-1).

    1 0  0  
G   0 1  0  
    -xR  -yR  1  
    cosφ  sinφ  0  
 x    -sinφ  cosφ  0  
    0 0  1  
    1 0  0  
 x    0 1  0  
    xR  yR  1  
    cosφ sinφ  0  
    -sinφ cosφ  0  
    xR(1 - cosφ) + yRsinφ   -xRsinφ + yR(1 - cosφ)  1  

Wykorzystujemy macierz G do wyznaczenia nowych współrzędnych wszystkich wierzchołków obiektu:

P' = P x G

Tutaj uwidaczniają się zalety rachunku macierzowego - jednorodność operacji. To duże ułatwienie.

Zapis T-1 oznacza macierz odwrotną. Przez analogię do zwykłego rachunku algebraicznego:

a • a-1 = 1

Iloczyn macierzy T i T-1 daje tzw. macierz jednostkową, która na głównej przekątnej posiada same jedynki, a na pozostałych pozycjach same zera. Sprawdźmy:

T-1   1 0 0    
  0 1 0  
  -Tx -Ty 1  
T   1 0 0       1 0 0    = 1
  0 1 0       0 1 0  
  Tx Ty 1       0 0 1  
Macierz odwrotną będziemy zawsze interpretować jako przekształcenie odwrotne - np. jeśli macierz R oznacza obrót o kąt φ, to R-1 będzie oznaczać obrót o kąt -φ. Istnieją algorytmy obliczające macierz odwrotną do danej. Jednakże wcale nie musimy z nich korzystać - jak zobaczymy dalej, wystarczy odpowiednio zastosować funkcje tworzące macierze przekształceń.

Skalowanie

Skalowanie (ang. scaling) względem osi układu współrzędnych polega na przemnożeniu współrzędnych x i y każdego wierzchołka przez współczynniki skali w osi OX - sx i w osi OY - sy, które nie muszą być równe - wtedy obiekt zostanie rozciągnięty lub ściśnięty względem wybranej osi.

obrazek

Macierz skalowania zbudowana jest następująco:

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

Współrzędne wierzchołków otrzymamy mnożąc macierz punktu P przez macierz skalowania SP' = P x S.


Jeśli chcemy skalować względem punktu PS = (xS,yS), to postępujemy podobnie jak przy rotacji - przekształcenie składamy z translacji od punktu (xS,yS) do środka układu współrzędnych, skalowania oraz translacji odwrotnej - od środka układu współrzędnych do punktu (xS,yS). W tym celu tworzymy trzy macierze przekształceń:

    1 0 0  
T   0 1 0  
    -xS -yS 1  
    Sx 0 0  
S   0 Sy 0  
    0 0 1  
    1 0 0  
T-1   0 1 0  
    xS yS 1  

Następnie wyliczamy macierz G złożonego przekształcenia jako iloczyn wyznaczonych macierzy G = T1 x S x T2:

    1 0 0  
G   0 1 0  
    -xS -yS 1  
    Sx 0 0  
 x    0 Sy 0  
    0 0 1  
    1 0 0  
 x    0 1 0  
    xS yS 1  
    Sx 0  0  
    0 Sy  0  
    -xS2Sx   -yS2Sy  1  

Współrzędne punktów otrzymujemy mnożąc ich macierze P przez macierz przekształcenia G:  P' = P x G.


Skalować można również wzdłuż dwóch osi prostopadłych do siebie i tworzących kąt φ z osiami układu współrzędnych:

obrazek

Znalezienie macierzy przekształcenia G proponuję jako zadanie dla czytelnika. Dla ambitnych proponuję zastanowić się nad skalowaniem względem dwóch osi prostopadłych, nachylonych pod zadanym katem względem osi układu współrzędnych, jednakże przesuniętych do punktu PS = (xS.yS).

Jednokładność

Jednokładność (ang. homothety, homeothetic transformation) o środku PH = (xH,yH) i skali k różnej od zera jest przekształceniem płaszczyzny, które punkt (x,y) przekształca w punkt o współrzędnych:

x' = xH + k(x - xH)
y' = yH + k(y - yH)

Interpretacja geometryczna jest bardzo prosta:

obrazek

 

Prowadzimy prostą przechodzącą przez punkt PH = (xH,yH) oraz punkt P = (x,y). Punkt P' = (x',y') odkładamy na tej prostej, tak aby jego odległość do PH była k razy większa od odległości punktu P od PH - czyli:

|PHP'| = k|PHP|

Jeśli k jest ujemne, to punkt P' będzie znajdował się po drugiej stronie punktu PH niż punkt P. Dla k = -1 oba punkty P i P' będą w równych odległościach od PH, lecz znajdą się po przeciwnych stronach punktu PH. Otrzymamy symetrię środkową.

Macierz jednokładności jest następująca:

    k 0  0  
H 3 x 3   0 k  0  
    (1 - k)xH   (1 - k)yH  1  

Punkt P' otrzymamy standardowo: P' = P x H.

Powinowactwo prostokątne

obrazek

Przy powinowactwie prostokątnym (ang. rectangular affinity) mamy daną prostą o równaniu ax + by + c = 0 oraz stosunek powinowactwa k. Przez punkt P = (x,y) prowadzimy prostopadłą do prostej. Prostopadła przecina prostą ax + by + c = 0 w punkcie Q, który jest rzutem prostokątnym punktu P na oś powinowactwa. Punkt P' = (x',y') znajdujemy na prostopadłej w odległości |P'Q| równej odległości k|QP|.

Jeśli k = -1, otrzymujemy symetrię osiową (ang. axial symmetry) względem osi powinowactwa.

Jeśli oś powinowactwa pokrywa się z osią OX lub OY, to otrzymujemy skalowanie względem osi OX lub OY.

Oznaczmy:   f = k - 1
a2 + b2

Macierz powinowactwa prostokątnego względem prostej ax + by + c = 0 i o współczynniku k jest następująca:

    1 + fa2 fab  0  
AR 3 x 3   fab 1 + fb2  0  
    fac fbc  1  

Macierze współrzędnych wierzchołków przemnażamy przez macierz AR: P' = P x AR.

Ścinanie

obrazek

Ś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. Obraz punktu otrzymujemy przemnażając macierz punktu P przez macierz ścinania SSP' = P x SS.

Funkcje biblioteczne tworzenia macierzy przekształceń

Dla każdego przekształcenia utworzymy osobną funkcję biblioteki SDL_gfx, która utworzy odpowiednią macierz przekształcenia na podstawie otrzymanych parametrów. Utwórz projekt SDL, dołącz do niego pliki SDL_gfx.h, SDL_gfx.cpp, których opis znajdziesz w podsumowaniu SDL009. Następnie dołącz pliki SDL_gui.h i SDL_gui.cpp - opis w podsumowaniu GUI004. Będziemy również potrzebowali czcionki vecprop9x12.fnt, którą przekopiuj do katalogu projektu SDL.

Na końcu pliku nagłówkowego SDL_gfx.h dopisz poniższy fragment:


double * gfx2DTrans(double Tx, double Ty);

double * gfx2DRot(double phi);

double * gfx2DScale(double Sx, double Sy);

double * gfx2DHomoth(double xh, double yh, double k);

double * gfx2DAffine(double a, double b, double c, double k);

double * gfx2DShear(double kx, double ky);

void gfx2DDrawPoly(SDL_Surface * s, Sint32 * p, Uint32 color);

void gfx2DTransform(Sint32 * p, double * tmx);

Sint32 * gfx2DCopyPoly(Sint32 * p);

gfx2DTrans(Tx,Ty) - tworzy macierz translacji
Tx  -  przesunięcie wzdłuż osi OX
Ty  - przesunięcie wzdłuż osi OY
gfx2DRot(phi) - tworzy macierz rotacji
phi  -  kąt obrotu podany w radianach
gfx2DScale(Sx,Sy) - tworzy macierz skalowania
Sx  - skala na osi OX
Sy  -  skala na osi OY
gfx2DHomoth(xh,yh,k) - tworzy macierz jednokładności
xh,yh  -  współrzędne punktu jednokładności
k  - współczynnik skali
gfx2DAffine(a,b,c,k) - tworzy macierz powinowactwa prostokątnego
a,b,c  -  współczynniki osi powinowactwa o równaniu ax + by + c = 0
k  - współczynnik powinowactwa

gfx2DShear(kx,ky) - tworzy macierz przekształcenia ścinającego

kx  -  współczynniki ścinania wzdłuż osi OX
ky  - współczynniki ścinania wzdłuż osi OY

gfx2DDrawPoly(s,p,color) - rysuje obiekt zbudowany z listy łamanych.

s  -   wskaźnik struktury SDL_Surface
p  - wskaźnik listy łamanych
color  - kolor rysowanych linii

gfx2DTransform(p,tmx) - przekształca współrzędne wierzchołków obiektu zgodnie z macierzą transformacji. Operację tę wykonujemy zwykle na kopi obiektu.

p  -   wskaźnik listy łamanych
tmx  - wskaźnik macierzy przekształcenia

gfx2DCopyPoly(p) - tworzy kopię listy łamanych i zwraca jej adres - po wykorzystaniu kopię usuwamy.

p  -   wskaźnik listy łamanych

Na początku pliku SDL_gfx.cpp dopisz dyrektywę preprocesora zaznaczoną na czerwono (bez niej nie będzie funkcji trygonometrycznych):

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne 2007
//
// Biblioteka procedur graficznych dla trybów 32 bitowych
//-------------------------------------------------------

#include <SDL/SDL_gfx.h>
#include <stack>
#include <fstream>
#include <list>
#include <cmath>

using namespace std;

Następnie na końcu pliku SDL_ghx.cpp dopisz kod nowych funkcji:


// Tworzy macierz translacji
// Tx, Ty - współrzędne wektora przesunięcia
//------------------------------------------

double * gfx2DTrans(double Tx, double Ty)
{
  double * T = new double[9];

  T[1] = T[2] = T[3] = T[5] = 0;
  T[0] = T[4] = T[8] = 1;
  T[6] = Tx;
  T[7] = Ty;
  return T;     
}

// Tworzy macierz rotacji
// phi - kąt obrotu w radianach
//-----------------------------

double * gfx2DRot(double phi)
{
  double * R = new double[9];
  
  R[2] = R[5] = R[6] = R[7] = 0;
  R[0] = R[4] = cos(phi);
  R[1] = sin(phi);
  R[3] = -R[1];
  R[8] = 1;
  return R;
}

// Tworzy macierz skalowania
// Sx - współczynnik skali na osi OX
// Sy - współczynnik skali na osi OY
//----------------------------------

double * gfx2DScale(double Sx, double Sy)
{
  double * S = new double[9];
  
  S[1] = S[2] = S[3] = S[5] = S[6] = S[7] = 0;
  S[0] = Sx;
  S[4] = Sy;
  S[8] = 1;
  return S;
}

// Tworzy macierz jednokładności
// xh,yh - współrzędne punktu jednokładności
// k - współczynnik jednokładności
//------------------------------------------

double * gfx2DHomoth(double xh, double yh, double k)
{
  double * H = new double[9];
  
  H[1] = H[2] = H[3] = H[5] = 0;
  H[0] = H[4] = k;
  H[6] = (1 - k) * xh;
  H[7] = (1 - k) * yh;
  H[8] = 1;
  return H;       
}

// Tworzy macierz powinowactwa prostokątnego
// a,b,c - współczynniki osi powinowactwa ax+by+c=0
// k - współczynnik powinowactwa
//-------------------------------------------------

double * gfx2DAffine(double a, double b, double c, double k)
{
   double * SR = new double[9];
   double f = (k - 1) / (a * a + b * b);
   
   SR[0] = 1 + f * a * a;
   SR[1] = SR[3] = f * a * b;
   SR[2] = SR[5] = 0;
   SR[4] = 1 + f * b * b;
   SR[6] = f * a * c;
   SR[7] = f * b * c;
   SR[8] = 1;
   return SR;
}

// Tworzy macierz ścinania
// kx - współczynnik ścinania wzdłuż osi OX
// ky - współczynnik ścinania wzdłuż osi OY
//-----------------------------------------

double * gfx2DShear(double kx, double ky)
{
  double * S = new double[9];

  S[0] = S[4] = S[8] = 1;
  S[1] = ky;
  S[2] = S[5] = S[6] = S[7] = 0;
  S[3] = kx;
  return S;
}

// Rysuje figurę zbudowaną z listy łamanych
// s - wskaźnik struktury SDL_Surface
// p - wskaźnik listy łamanych
// color - kolor linii
//----------------------------------------- 

void gfx2DDrawPoly(SDL_Surface * s, Sint32 * p, Uint32 color)
{
  Sint32 plen, x, y;
  
  while(plen = * p++)
  {
    x = * p++; y = * p++;
    gfxMoveTo(x, y);
    while(--plen)
    {
      x = * p++; y = * p++;
      gfxClipLineTo(s, x, y, color);          
    }
  }
}

// Dokonuje transformacji figury na podstawie
// macierzy przekształcenia
// p   - wskaźnik listy łamanych
// tmx - macierz transformacji
//-------------------------------------------

void gfx2DTransform(Sint32 * p, double * tmx)
{
  double pxy[3], * pw;
  Sint32 plen;
  
  while(plen = * p++)
  {
    while(plen--)
    {
      pxy[0] = * p++; pxy[1] = * p++; pxy[2] = 1;
      pw = gfxMMul(1,3,3,pxy,tmx);
      * (p - 2) = (Sint32)pw[0];
      * (p - 1) = (Sint32)pw[1];
      delete [] pw;
    }
  } 
}

// Tworzy kopię listy łamanych
// p - wskaźnik pierwotnej listy
//------------------------------

Sint32 * gfx2DCopyPoly(Sint32 * p)
{
  Sint32 plen, * pp = p;
  
  while(plen = * pp++)
  {
    while(plen--) pp += 2;
  }
  plen = pp - p;
  
  Sint32 * f = pp = new Sint32[plen];

  while(plen--) * pp++ = * p++;

  return f;
}

Poniższy program testuje nowe funkcje biblioteczne demonstrując jednocześnie sposób ich używania. Rodzaj transformacji wybieramy przyciskiem akcji.

// I Liceum Ogólnokształcące
// w Tarnowie
// Koło informatyczne
//
// P040 - Transformacje geometryczne
//----------------------------------

#include <SDL/SDL_gfx.h>
#include <SDL/SDL_gui.h>
#include <time.h>

const int SCRX = 640;      // stałe określające szerokość i wysokość
const int SCRY = 480;      // ekranu w pikselach

SDL_Surface * screen;
gfxFont     * font = gfxOpenFont("vecprop9x12.fnt");

//------------------------------------------
// Funkcje obsługujące poszczególne animacje
//------------------------------------------

void (* action)();         // wskaźnik funkcji

Sint32 figure[] = {11,0,20,20,0,40,0,40,20,60,20,60,40,50,50,50,60,20,60,0,40,0,20,0};

char * t[] = {"Transformacje geometryczne"," Translacja "," Rotacja "," Skalowanie ",
              " Jednokładność "," Powinowactwo "," Ścinanie "," Zakończ "};

// Ustala prostokąt obcinania dla animacji
// i kasuje ekran - włącza blokadę
//----------------------------------------

void SetClip()
{
  SDL_Rect r;
  
  r.x = 0; r.y = font->h + 8; r.h = screen->h - font->h - 8; r.w = screen->w;
  
  SDL_SetClipRect(screen,&r);
  
  if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

  SDL_FillRect(screen, &(screen->clip_rect), 0);  
}

// Odtwarza prostokąt obcinania
//-----------------------------

void RestoreClip(int i)
{
  gfxDrawText(screen, font, t[i], (screen->w >> 1) - (gfxTextLength(font, t[i]) >> 1),
              screen->clip_rect.y + 8, 0x00ff00, -1);
  SDL_Rect r;
  
  if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

  SDL_UpdateRect(screen, screen->clip_rect.x, screen->clip_rect.y,
                 screen->clip_rect.w, screen->clip_rect.h);    

  r.x = 0; r.y = 0; r.h = screen->h; r.w = screen->w;
  
  SDL_SetClipRect(screen, &r);
}

// Translacja
//-----------

const int MAXF = 25;

double Tx[MAXF],Ty[MAXF],dx[MAXF],dy[MAXF];
int    si[MAXF];

void InitTransI(int i)
{
  int xp,yp,xk,yk;
  si[i] = 300 + rand() % 1000;
  switch(rand() % 4)
  {
    case 0:
      xp = rand() % (screen->w + 120) - 60;
      xk = rand() % (screen->w + 120) - 60;
      yp = -60;
      yk = screen->h + 60;
      break;
    case 1:
      xp = rand() % (screen->w + 120) - 60;
      xk = rand() % (screen->w + 120) - 60;
      yk = -60;
      yp = screen->h + 30;
      break;
    case 2:
      yp = rand() % (screen->h + 120) - 60;
      yk = rand() % (screen->h + 120) - 60;
      xp = -60;
      xk = screen->w + 60;
      break;
    case 3:
      yp = rand() % (screen->h + 120) - 60;
      yk = rand() % (screen->h + 120) - 60;
      xk = -60;
      xp = screen->w + 60;
      break;
  }
  Tx[i] = xp; Ty[i] = yp;
  dx[i] = (xk - Tx[i]) / si[i];
  dy[i] = (yk - Ty[i]) / si[i];
}

void InitTrans()
{
  for(int i = 0; i < MAXF; i++) InitTransI(i);     
}

void Trans()
{ 
  SetClip();

  for(int i = 0; i < MAXF; i++)
  {
    if(!si[i]) InitTransI(i);
    si[i]--;
    Tx[i] += dx[i];
    Ty[i] += dy[i];
    double * T = gfx2DTrans(Tx[i], Ty[i]);
    Sint32 * F = gfx2DCopyPoly(figure);
    gfx2DTransform(F, T);
    gfx2DDrawPoly(screen, F, 0xffff00);
    delete [] T;
    delete [] F;
  }
  RestoreClip(1);
}

// Rotacja
//-----------

double phi[MAXF], dphi[MAXF];

void InitRot()
{
  for(int i = 0; i < MAXF; i++)
  {
    Tx[i] = rand() % (screen->w);
    Ty[i] = rand() & (screen->h);
    phi[i] = 0;
    dphi[i] = 1 / (double)(200 + rand() % 200);
  }
}

void Rot()
{
  SetClip(); 

  for(int i = 0; i < MAXF; i++)
  {
    phi[i] += dphi[i];
    if(phi[i] > 6.2831853) phi[i] -= 6.2831853;
    double * T1 = gfx2DTrans(Tx[i] - (screen->w >> 1), Ty[i] - (screen->h >> 1));
    double * R  = gfx2DRot(phi[i]);
    double * T2 = gfx2DTrans((screen->w >> 1),(screen->h >> 1));
    double * E  = gfxMMul(3, 3, 3, T1, R);
    double * G  = gfxMMul(3, 3, 3, E, T2);
    Sint32 * F  = gfx2DCopyPoly(figure);
    gfx2DTransform(F, G);
    gfx2DDrawPoly(screen, F, 0xffff00);
    delete [] T1;
    delete [] R;
    delete [] T2;
    delete [] E;
    delete [] F;
    delete [] G;
  }
  
  gfxLine(screen, screen->w >> 1, (screen->h >> 1) - 5,
                  screen->w >> 1, (screen->h >> 1) + 5, 0xff0000);
  gfxLine(screen, (screen->w >> 1) - 5, screen->h >> 1,
                  (screen->w >> 1) + 5, screen->h >> 1, 0xff0000);
                  
  RestoreClip(2);
}

// Skalowanie
//-----------

double Sx[MAXF], Sy[MAXF], dSx[MAXF], dSy[MAXF];

void InitScale()
{
  for(int i = 0; i < MAXF; i++)
  {
    Tx[i] = rand() % (screen->w);
    Ty[i] = rand() & (screen->h);
    Sx[i] = 0.5; dSx[i] = 1 / (double)(200 + rand() % 200);
    Sy[i] = 0.5; dSy[i] = 1 / (double)(200 + rand() % 200);
  }
}

void Scale()
{
  SetClip();

  for(int i = 0; i < MAXF; i++)
  {
    if((Sx[i] < -2) || (Sx[i] > 2)) dSx[i] = - dSx[i];
    if((Sy[i] < -2) || (Sy[i] > 2)) dSy[i] = - dSy[i];
    Sx[i] += dSx[i];
    Sy[i] += dSy[i];    
    double * T1 = gfx2DTrans(-30, -30);
    double * S  = gfx2DScale(Sx[i], Sy[i]);
    double * T2 = gfx2DTrans(Tx[i] + 30, Ty[i] + 30);
    double * E  = gfxMMul(3, 3, 3, T1, S);
    double * G  = gfxMMul(3, 3, 3, E, T2);
    Sint32 * F  = gfx2DCopyPoly(figure);
    gfx2DTransform(F, G);
    gfx2DDrawPoly(screen, F, 0xffff00);
    delete [] T1;
    delete [] S;
    delete [] T2;
    delete [] E;
    delete [] F;
    delete [] G;
  }
                  
  RestoreClip(3);
}

// Jednokładność
//-----------

double kH[MAXF], dkH[MAXF];

void InitHomoth()
{
  for(int i = 0; i < MAXF; i++)
  {
    Tx[i] = rand() % (screen->w);
    Ty[i] = rand() & (screen->h);
    kH[i] = 1; dkH[i] = 1 / (double)(200 + rand() % 200);
  }     
}

void Homoth()
{
  SetClip();

  for(int i = 0; i < MAXF; i++)
  {
    if((kH[i] < -2) || (kH[i] > 2)) dkH[i] = - dkH[i];
    kH[i] += dkH[i];
    double * T  = gfx2DTrans(Tx[i], Ty[i]);    
    double * H  = gfx2DHomoth(screen->w >> 1, screen->h >> 1, kH[i]);
    double * G  = gfxMMul(3, 3, 3, T, H);
    Sint32 * F  = gfx2DCopyPoly(figure);
    gfx2DTransform(F, G);
    gfx2DDrawPoly(screen, F, 0xffff00);
    gfxLine(screen, screen->w >> 1, (screen->h >> 1) - 5,
                    screen->w >> 1, (screen->h >> 1) + 5, 0xff0000);
    gfxLine(screen, (screen->w >> 1) - 5, screen->h >> 1,
                    (screen->w >> 1) + 5, screen->h >> 1, 0xff0000);
    delete [] T;
    delete [] H;
    delete [] G;
    delete [] F;
  }
                  
  RestoreClip(4);   
}

// Powinowactwo
//-------------

void Affine()
{
  SetClip();

  for(int i = 0; i < MAXF; i++)
  {
    if((kH[i] < -2) || (kH[i] > 2)) dkH[i] = - dkH[i];
    kH[i] += dkH[i];
    double * T = gfx2DTrans(Tx[i], Ty[i]);    
    double * A = gfx2DAffine(-screen->h + 1, screen->w - 1, 0, kH[i]);
    double * G = gfxMMul(3, 3, 3, T, A);
    Sint32 * F = gfx2DCopyPoly(figure);
    gfx2DTransform(F, G);
    gfx2DDrawPoly(screen, F, 0xffff00);
    gfxClipLine(screen, 0,0,screen->w - 1, screen->h - 1, 0xff0000);
    delete [] T;
    delete [] A;
    delete [] G;
    delete [] F;
  }
                  
  RestoreClip(5);
}

// Ścinanie
//---------

double kx[MAXF], dkx[MAXF], ky[MAXF], dky[MAXF];

void InitShear()
{
  for(int i = 0; i < MAXF; i++)
  {
    Tx[i] = rand() % (screen->w);
    Ty[i] = rand() % (screen->h);
    kx[i] = 0; dkx[i] = 1 / (double)(200 + rand() % 200);
    ky[i] = 0; dky[i] = 1 / (double)(200 + rand() % 200);
  } 
}

void Shear()
{
  SetClip();

  for(int i = 0; i < MAXF; i++)
  {
    if((kx[i] < -2) || (kx[i] > 2)) dkx[i] = - dkx[i];
    if((ky[i] < -2) || (ky[i] > 2)) dky[i] = - dky[i];
    kx[i] += dkx[i];
    ky[i] += dky[i];
    double * S = gfx2DShear(kx[i], ky[i]);
    double * T = gfx2DTrans(Tx[i], Ty[i]);
    double * G = gfxMMul(3, 3, 3, S, T);
    Sint32 * F = gfx2DCopyPoly(figure);
    gfx2DTransform(F, G);
    gfx2DDrawPoly(screen, F, 0xffff00);
    delete [] T;
    delete [] S;
    delete [] G;
    delete [] F;
  }

  RestoreClip(6); 
}

// Funkcja obsługująca przyciski
//------------------------------

void bfn(gfxGUIObject * sender)
{
  switch(sender->tag)
  {
    case 1: InitTrans();  action = Trans;  break;
    case 2: InitRot();    action = Rot;    break;
    case 3: InitScale();  action = Scale;  break;
    case 4: InitHomoth(); action = Homoth; break;
    case 5: InitHomoth(); action = Affine; break;
    case 6: InitShear();  action = Shear;  break;
    case 7: exit(0);
  }
}

//***********************
// *** Program główny ***
//***********************

int main(int argc, char * argv[])
{
  
// Tablica siedmiu wskaźników do przycisków

  gfxButton * b[7];
  
  if(SDL_Init(SDL_INIT_VIDEO)) exit(-1);

  atexit(SDL_Quit);
  
  if(!(screen = SDL_SetVideoMode(SCRX, SCRY, 32, SDL_HWSURFACE))) exit(-1);

  SDL_WM_SetCaption(t[0], ""); 
    
// Tworzymy przyciski akcji

  SDL_Rect r;

  r.x = r.y = r.w = 0;
  r.h = font->h + 8;
    
  for(int i = 1; i <= 7; i++)
  {
    b[i] = new gfxButton(i, true, screen, font, &r, t[i], bfn);
    r.x += gfxTextLength(font, t[i]) + 4;
  }
   
// Inicjujemy generator liczb pseudolosowych

  srand((unsigned)time(NULL));
  
// Inicjujemy animacje

  InitTrans();
  action = Trans;
  
// Obsługujemy zdarzenia

  SDL_Event event;
  
  bool running = true;  
  while(running)
  {
    SDL_Delay(1);
    (* action)(); // wywołujemy funkcję obsługi animacji
    while(SDL_PollEvent(&event))
    {
      bool eventfree;
      for(int i = 1; i <= 7; i++)
        if(!(eventfree = b[i]->DoEvents(&event))) break;
      if(eventfree && (event.type == SDL_QUIT))
      {
        running = false;
        break;
      }
    }
  }
  
// zamykamy czcionkę

  gfxCloseFont(font);

// Usuwamy przyciski

  for(int i = 1; i <= 7; i++) delete b[i];
  return 0;
}

obrazek


Podsumowanie


   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