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 |
©2023 mgr Jerzy Wałaszek
|
Podrozdziały |
Podrozdziały |
Figurę taką definiujemy za pomocą 4 punktów wierzchołkowych. Do narysowania czworokąta możemy użyć tablicy struktur SDL_Point oraz funkcji SDL_RenderDrawLines(). Aby łamana została zamknięta, ostatni element tablicy musi być taki sam jak pierwszy, z tego powodu tablica będzie zawierała 5 elementów, a nie 4.
Poniższy program tworzy zadaną liczbę losowych czworokątów, po czym animuje je w okienku.
C++// Czworokąty 1 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba czworokątów const int N = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point q[N][5]; int dx[N],dy[N],cr[N],cg[N],cb[N],i,j; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) { q[i][j].x = W_W / 2 - W_W / 4 + (rand() % W_W) / 2; q[i][j].y = W_H / 2 - W_H / 4 + (rand() % W_H) / 2; } q[i][4] = q[i][0]; // Zamykamy łamaną do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,q[i],5); } // Modyfikujemy współrzędne for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((q[i][j].x + dx[i] < 0) || (q[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) q[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((q[i][j].y + dy[i] < 0) || (q[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) q[i][j].y += dy[i]; q[i][4] = q[i][0]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Równoległobok (ang. parallelogram) jest czworokątem, którego boki są parami równoległe do siebie:
W powyższym równoległoboku zachodzą związki:
Oznacza to, iż równoległość boków wymusza, iż boki naprzeciwległe mają równe długości. Dlatego do zdefiniowania równoległoboku niezbędne są trzy punkty wierzchołkowe. Czwarty punkt nie jest swobodny, lecz zależy od tych trzech i można go wyznaczyć obliczeniowo.
Umówmy się, że 3 punkty definiujące równoległobok będą podane zgodnie z ruchem wskazówek zegara (jak na powyższym rysunku): P1(x1,y1), P2(x2,y2) i P3(x3,y3), Obliczymy współrzędne punktu P4(x4,y4). Wykorzystamy proste związki geometryczne:
Na dwóch przeciwległych bokach budujemy trójkąty prostokątne, tak aby boki te były przeciwprostokątnymi, a pozostałe boki trójkątów mają być równoległe do osi OX i OY. Ponieważ boki naprzeciwległe równoległoboku są równe co do długości, to otrzymujemy dla pierwszego trójkąta:
Dla drugiego trójkąta mamy podobnie:
Trójkąty są przystające, zatem:
A stąd otrzymujemy wzory na współrzędne czwartego punktu:
Zmodyfikujemy nieco poprzedni program, tak aby rysował równoległoboki:
C++// Czworokąty 2 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba równoległoboków const int N = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point q[N][5]; int dx[N],dy[N],cr[N],cg[N],cb[N],i,j; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów for(i = 0; i < N; i++) { // Losujemy trzy punkty for(j = 0; j < 3; j++) { q[i][j].x = W_W / 2 - W_W / 4 + (rand() % W_W) / 2; q[i][j].y = W_H / 2 - W_H / 4 + (rand() % W_H) / 2; } // Czwarty punkt wyliczamy q[i][3].x = q[i][0].x - q[i][1].x + q[i][2].x; q[i][3].y = q[i][0].y - q[i][1].y + q[i][2].y; q[i][4] = q[i][0]; // Zamykamy łamaną do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,q[i],5); } // Modyfikujemy współrzędne for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((q[i][j].x + dx[i] < 0) || (q[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) q[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((q[i][j].y + dy[i] < 0) || (q[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) q[i][j].y += dy[i]; q[i][4] = q[i][0]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Romb (ang. rhombus) jest równoległobokiem o wszystkich bokach równych co do długości:
Romb, podobnie jak opisany powyżej równoległobok, definiujemy za pomocą 3 kolejnych wierzchołków, a współrzędne czwartego wierzchołka wyliczamy wg wzorów:
W rombie dwa pierwsze punkty P1 i P2 można wybrać dowolnie, jednak punkt P3 nie jest już swobodny, ponieważ musi znajdować się w takiej samej odległości od punktu P2 jak punkt P1, inaczej równoległobok nie miałby boków równych. Wynika z tego, że punkt P3 musi leżeć na obwodzie okręgu o promieniu równym długości boku rombu i o środku w punkcie P2:
Spostrzeżenie to umożliwi nam generowanie rombów. Procedura jest następująca:
Generujemy losowe współrzędne punktów P1 i P2.
Obliczamy odległość od punktu P1 do P2 z twierdzenia Pitagorasa:
Ten drugi wzór nie jest pomyłką: jeśli odwrócimy kolejność składników w różnicy, to zmienimy jej znak na przeciwny. Nie ma to jednak żadnego znaczenia, ponieważ różnica i tak jest podnoszona do kwadratu:
Otrzymamy promień a okręgu, na obwodzie którego należy wyznaczyć losowy punkt. Środek okręgu jest w punkcie P2. Współrzędne punktów na okręgu określamy parametrycznie:
Parametr t wygenerujemy jako pseudolosową liczbę rzeczywistą wg wzoru dla języka C++:
t = 2 * M_PI * (rand() / (double) RAND_MAX);
Wyrażenie w nawiasie daje rzeczywistą wartość pseudolosową w zakresie od 0 do 1. Pomnożone przez 2π da wartość w pożądanym zakresie od 0 do 2π.
Gdy wyznaczymy współrzędne punktu P3, to wyliczamy współrzędne ostatniego wierzchołka rombu z podanego wcześniej wzoru dla równoległoboku:
Poniższy program jest modyfikacją pierwszego i animuje romby:
C++// Czworokąty 3 //------------- #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 rombów const int N = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point q[N][5]; int dx[N],dy[N],cr[N],cg[N],cb[N],i,j; double a,t; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów for(i = 0; i < N; i++) { // Losujemy dwa punkty for(j = 0; j < 2; j++) { q[i][j].x = W_W / 2 - W_W / 6 + (rand() % W_W) / 3; q[i][j].y = W_H / 2 - W_H / 6 + (rand() % W_H) / 3; } // Trzeci i czwarty punkt obliczamy a = sqrt((q[i][0].x-q[i][1].x)*(q[i][0].x-q[i][1].x) + (q[i][0].y-q[i][1].y)*(q[i][0].y-q[i][1].y)); t = 2 * M_PI * (rand() / (double) RAND_MAX); q[i][2].x = q[i][1].x + a * cos(t); q[i][2].y = q[i][1].y + a * sin(t); q[i][3].x = q[i][0].x - q[i][1].x + q[i][2].x; q[i][3].y = q[i][0].y - q[i][1].y + q[i][2].y; q[i][4] = q[i][0]; // Zamykamy łamaną do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,q[i],5); } // Modyfikujemy współrzędne for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((q[i][j].x + dx[i] < 0) || (q[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) q[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((q[i][j].y + dy[i] < 0) || (q[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) q[i][j].y += dy[i]; q[i][4] = q[i][0]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Prostokąt (ang. rectangle) jest równoległobokiem, w którym sąsiednie boki tworzą ze sobą kąt prosty:
Podobnie jak dla rombu dwa pierwsze punkty mogą być wybrane dowolnie. Punkt P3 musi leżeć na prostej prostopadłej do odcinka wyznaczonego przez P1 i P2 i przechodzącej przez punkt P2:
Problem sprowadza się zatem do wygenerowania punktu P3 na tej prostej prostopadłej i tym się teraz zajmiemy.
Punkt P3 można wygenerować na kilka sposobów. Poniżej podaję opis jednego z nich, w którym wykorzystuje się okrąg.
Najpierw losujemy punkt P2, który będzie środkiem okręgu o promieniu równym a:
Dowolny punkt na obwodzie okręgu posiada współrzędne:
Kąt α jest katem pomiędzy prostą równoległą do osi OX układu współrzędnych i przechodzącą przez środek okręgu, a drugą prostą przechodzącą przez środek i wybrany punkt P1 na obwodzie okręgu. Aby otrzymać jego współrzędne, wystarczy wylosować kat α w zakresie od 0 do 2π radianów i użyć podanych wzorów. W ten sposób otrzymamy dwa wierzchołki prostokąta. Trzeci wierzchołek leży na prostej prostopadłej do odcinka P1P2 i przechodzącej przez P2. Losujemy zatem promień b, środek nowego okręgu umieszczamy w punkcie P2 i wybieramy na obwodzie punkt P3 o współrzędnych wg wzoru:
Otrzymamy trzeci wierzchołek prostokąta. Czwarty wierzchołek znajdziemy już bez problemu ze wzorów:
C++// Czworokąty 4 //------------- #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 prostokatów const int N = 50; // Kąt prosty w radianach const double PI2 = M_PI / 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point q[N][5]; int dx[N],dy[N],cr[N],cg[N],cb[N],i,j,x,a,b; double alpha; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy P2 q[i][1].x = W_W / 2 - W_W / 6 + (rand() % W_W) / 3; q[i][1].y = W_H / 2 - W_H / 6 + (rand() % W_H) / 3; // Losujemy promień a i kąt alpha a = rand() % x; alpha = 2 * M_PI * rand() / RAND_MAX; // Wyliczamy P1 q[i][0].x = q[i][1].x + a * cos(alpha); q[i][0].y = q[i][1].y + a * sin(alpha); // Losujemy promień b b = rand() % x; // Wyliczamy P3 q[i][2].x = q[i][1].x + b * cos(alpha+PI2); q[i][2].y = q[i][1].y + b * sin(alpha+PI2); // Wyliczamy P4 q[i][3].x = q[i][0].x - q[i][1].x + q[i][2].x; q[i][3].y = q[i][0].y - q[i][1].y + q[i][2].y; // Zamykamy łamaną q[i][4] = q[i][0]; do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,q[i],5); } // Modyfikujemy współrzędne for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((q[i][j].x + dx[i] < 0) || (q[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) q[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((q[i][j].y + dy[i] < 0) || (q[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) q[i][j].y += dy[i]; q[i][4] = q[i][0]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Biblioteka SDL2 zawiera specjalną funkcję, która rysuje prostokąt o bokach równoległych do boków ekranu:
SDL_RenderDrawRect(r,rect)
r – wskaźnik struktury SDL_Renderer z kontekstem
graficznym
rect – wskaźnik struktury SDL_Rect, która
definiuje prostokąt
SDL_Rect | – | struktura definiuje prostokątny obszar
na powierzchni graficznej:struct SDL_Rect { Sint16 x, y; Uint16 w, h; };
x,y – współrzędne ekranowe lewego
górnego narożnika. Zwróć uwagę, że mogą być ujemne. |
Poniższy program demonstruje sposób wykorzystania tej funkcji w prostej animacji:
C++// Prostokąty 1 //------------- #include <SDL.h> #include <iostream> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy zmienne int d = -1; int w_max = W_W / 10 - 3; int h_max = W_H / 10 - 3; int i,j; SDL_Rect rect; rect.w = w_max; rect.h = h_max; // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy prostokąty SDL_SetRenderDrawColor(r,255,255,255,255); for(i = 0; i < 10; i++) { rect.y = i * W_H / 10 + (W_H / 10 - rect.h) / 2; for(j = 0; j < 10; j++) { rect.x = j * W_W / 10 + (W_W / 10 - rect.w) / 2; SDL_RenderDrawRect(r,&rect); } } // Zmieniamy rozmiary prostokątów if((rect.w + d < 2) || (rect.h + d < 2) || (rect.w + d > w_max) || (rect.h + d > h_max)) d = -d; rect.w += d; rect.h += d; // Uaktualniamy okno SDL_RenderPresent(r); SDL_Delay(20); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Kolejny program wykorzystuje tablicę struktur SDL_Rect do utworzenia ciekawego efektu graficznego zanikających stopniowo prostokątów.
C++// Prostokąty 2 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba prostokątów const int N = 100; 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 * win = SDL_CreateWindow("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); if(!win) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(win,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(win); SDL_Quit(); return 3; } // Inicjujemy zmienne srand(time(NULL)); SDL_Rect R[N]; int dx,dy; do dx = 5 - rand() % 11; while(!dx); do dy = 5 - rand() % 11; while(!dy); int i,w,h,x,y,c; w = W_W / 8 + rand() % (W_W / 8); h = W_H / 8 + rand() % (W_H / 8); x = W_W / 2 - w / 2; y = W_H / 2 - h / 2; for(i = 0; i < N; i++) { R[i].x = x; R[i].y = y; R[i].w = w; R[i].h = h; } // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Animacja SDL_Event e; while(1) { // Rysujemy prostokąty od ostatniego do pierwszego // zwiększając stopniowo jasność; for(i = N - 1; i >= 0; i--) { c = (N - i - 1) * 255 / (N - 1); SDL_SetRenderDrawColor(r,c,c,c,255); SDL_RenderDrawRect(r,&R[i]); } // Modyfikujemy przyrosty if((R[0].x + dx < 0) || (R[0].x + R[0].w + dx >= W_W)) dx = -dx; if((R[0].y + dy < 0) || (R[0].y + R[0].h + dy >= W_H)) dy = -dy; // Modyfikujemy współrzędne pierwszego prostokąta R[0].x += dx; R[0].y += dy; // Ukatualniamy tablicę for(i = N - 1; i > 0; i--) R[i] = R[i - 1]; // Uaktualniamy okno SDL_RenderPresent(r); SDL_Delay(15); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(win); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
W bibliotece SDL2 istnieje funkcja, która pozwala hurtowo rysować prostokąty, których definicje przechowuje tablica o elementach typu SDL_Rect.
SDL_RenderDrawRects(r,R,n)
r – wskaźnik struktury SDL_Renderer z kontekstem graficznym
R – tablica elementów typu SDL_Rect, które zawierają
definicje prostokątów.
n – liczba prostokątów do
narysowania z tablicy.
Kolejny program tworzy prostą animację wykorzystującą tę funkcję:
C++// Prostokąty 3 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; 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 * win = SDL_CreateWindow("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); if(!win) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(win,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(win); SDL_Quit(); return 3; } // Inicjujemy zmienne srand(time(NULL)); SDL_Rect R[400]; int cr,cg,cb,dr,dg,db,i,j,n; for(i = 0; i < 20; i++) for(j = 0; j < 20; j++) { n = 20 * j + i; R[n].w = W_W / 20 - 4; R[n].h = W_H / 20 - 4; R[n].x = 2 + i * W_W / 20; R[n].y = 2 + j * W_H / 20; } cr = rand() % 256; cg = rand() % 256; cb = rand() % 256; do dr = 3 - rand() % 7; while(!dr); do dg = 3 - rand() % 7; while(!dg); do db = 3 - rand() % 7; while(!db); // Animacja SDL_Event e; while(1) { // Kolor prostokątów SDL_SetRenderDrawColor(r,cr,cg,cb,255); // Rysujemy prostokąty SDL_RenderDrawRects(r,R,400); // Uaktualniamy kolory if((cr + dr < 0) || (cr + dr > 255)) dr = - dr; if((cg + dg < 0) || (cg + dg > 255)) dg = - dg; if((cb + db < 0) || (cb + db > 255)) db = - db; cr += dr; cg += dg; cb += db; // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(win); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Oprócz rysowania krawędzi prostokątów biblioteka SDL2 zawiera funkcję rysującą wypełnione prostokąty:
SDL_RenderFillRect(r,rect)
r – wskaźnik struktury SDL_Renderer z kontekstem
graficznym
rect – wskaźnik struktury SDL_Rect, która definiuje
prostokąt
C++// Prostokąty 4 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; 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 * win = SDL_CreateWindow("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); if(!win) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(win,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(win); SDL_Quit(); return 3; } // Inicjujemy zmienne srand(time(NULL)); SDL_Rect rect; rect.w = rect.h = 0; int cr,cg,cb; // Animacja SDL_Event e; while(1) { // Czyścimy obszar okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Sprawdzamy rozmiary prostokąta if(!rect.w || !rect.h) { rect.w = W_W; rect.h = W_H; rect.x = rect.y = 0; cr = rand() % 256; cg = rand() % 256; cb = rand() % 256; } // Rysujemy wypałniony prostokąt SDL_SetRenderDrawColor(r,cr,cg,cb,255); SDL_RenderFillRect(r, &rect); // Zmniejszamy rozmiary prostokąta rect.w--; rect.h = rect.w * W_H / W_W; // Modyfikujemy odpowiednio współrzędne rect.x = (W_W - rect.w) / 2; rect.y = (W_H - rect.h) / 2; // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(win); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Podobnie do funkcji rysującej serię prostokątów w SDL2 istnieje funkcja rysująca serię prostokątów wypełnionych:
SDL_RenderFillRects(r,R,n)
r – wskaźnik struktury SDL_Renderer z kontekstem
graficznym
R – tablica o elementach typu SDL_Rect, które definiują
prostokąty
n – liczba prostokątów w tablicy R
Poniższy program tworzy zadaną liczbę prostokątów, a następnie przemieszcza je po obszarze okna. Kolor wypełnień jest płynnie zmieniany.
C++// Prostokąty 5 //------------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba prostokątów const int N = 10; 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 * win = SDL_CreateWindow("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); if(!win) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(win,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(win); SDL_Quit(); return 3; } // Inicjujemy zmienne srand(time(NULL)); // Generator pseudolosowy SDL_Rect R[N]; // Tablica prostokątów int dx[N],dy[N]; // Tabblice kierunków int i; for(i = 0; i < N; i++) { do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); R[i].x = rand() % (W_W - 10); R[i].y = rand() % (W_H - 10); R[i].w = 1 + rand() % (W_W - R[i].x) / 3; R[i].h = 1 + rand() % (W_H - R[i].y) / 3; } int cr,cg,cb,dr,dg,db; // Kolory wypełnień cr = rand() % 256; cg = rand() % 256; cb = rand() % 256; do dr = -3 + rand() % 7; while(!dr); do dg = -3 + rand() % 7; while(!dg); do db = -3 + rand() % 7; while(!db); // Animacja SDL_Event e; while(1) { // Ustawiamy kolor wypełnień SDL_SetRenderDrawColor(r,cr,cg,cb,255); // Modyfikujemy kolor wypełnień dla następnego obiegu pętli if((cr + dr > 255) || (cr + dr < 0)) dr = - dr; if((cg + dg > 255) || (cg + dg < 0)) dg = - dg; if((cb + db > 255) || (cb + db < 0)) db = - db; cr += dr; cg += dg; cb += db; // Wyświetlamy prostokąty SDL_RenderFillRects(r,R,N); // Modyfikujemy położenia prostokątów for(i = 0; i < N; i++) { if((R[i].x + dx[i] > W_W - R[i].w + 10) || (R[i].x + dx[i] < -10)) dx[i] = - dx[i]; if((R[i].y + dy[i] > W_H - R[i].h + 10) || (R[i].y + dy[i] < -10)) dy[i] = - dy[i]; R[i].x += dx[i]; R[i].y += dy[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(win); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Trójkąt możemy narysować przy pomocy poznanych wcześniej funkcji. SDL2 nie posiada dedykowanej funkcji do rysowania tylko trójkątów. Korzystamy z finkcji SDL_RenderDrawLine() lub SDL_RenderDrawLines().
Poniższy program generuje zadaną liczbę trójkątów o różnych kolorach, po czym animuje je w oknie SDL2.
C++// Trójkąty 1 //----------- #include <SDL.h> #include <iostream> #include <cstdlib> #include <ctime> using namespace std; // Rozmiar okienka const int W_W = 640; const int W_H = 480; // Liczba trójkątów const int N = 50; 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 * win = SDL_CreateWindow("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); if(!win) { cout << "SDL_CreateWindow Error: " << SDL_GetError() << endl; SDL_Quit(); return 2; } // Tworzymy kontekst graficzny SDL_Renderer * r = SDL_CreateRenderer(win,0,SDL_RENDERER_PRESENTVSYNC); if(!r) { cout << "SDL_CreateRenderer Error: " << SDL_GetError() << endl; SDL_DestroyWindow(win); SDL_Quit(); return 3; } // Inicjujemy zmienne srand(time(NULL)); // Generator pseudolosowy SDL_Point T[N][4]; // Tablica trójkątów int dx[N],dy[N]; // Tabblice przesunięć int cr[N],cg[N],cb[N]; // Tablice kolorów int dr[N],dg[N],db[N]; // Tablice zmian kolorów int i,j; // Generujemy trójkąty for(i = 0; i < N; i++) { // Wierzchołki for(j = 0; j < 3; j++) { T[i][j].x = rand() % W_W; T[i][j].y = rand() % W_H; } T[i][3] = T[i][0]; // Zamknięcie trójkąta // Przesunięcia w osiach OX i OY do dx[i] = -5 + rand() % 11; while(!dx[i]); do dy[i] = -5 + rand() % 11; while(!dy[i]); // Kolory trójkątów cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; // Zmiany kolorów do dr[i] = -3 + rand() % 7; while(!dr[i]); do dg[i] = -3 + rand() % 7; while(!dg[i]); do db[i] = -3 + rand() % 7; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy poszczególne trójkąty for(i = 0; i < N; i++) { // Kolor linii SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); // Trójkąt SDL_RenderDrawLines(r,T[i],4); // Modyfikujemy współrzędne for(j = 0; j < 3; j++) { if((T[i][j].x + dx[i] < - W_W / 2) || (T[i][j].x + dx[i] > 3 * W_W / 2)) dx[i] = -dx[i]; if((T[i][j].y + dy[i] < - W_H / 2) || (T[i][j].y + dy[i] > 3 * W_H / 2)) dy[i] = -dy[i]; T[i][j].x += dx[i]; T[i][j].y += dy[i]; } T[i][3] = T[i][0]; // Modyfikujemy kolory if((cr[i] + dr[i] > 255) || (cr[i] + dr[i] < 0)) dr[i] = -dr[i]; if((cg[i] + dg[i] > 255) || (cg[i] + dg[i] < 0)) dg[i] = -dg[i]; if((cb[i] + db[i] > 255) || (cb[i] + db[i] < 0)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Opóźnienie SDL_Delay(20); // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(win); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Problem narysowania trójkąta sprowadza się do wygenerowania współrzędnych jego 3 wierzchołków, które następnie łączymy liniami. Czasem możesz potrzebować narysować trójkąt prostokątny, równoboczny lub równoramienny. Poniżej przedstawiamy proste sposoby osiągnięcia tego celu.
Do wygenerowania trójkąta prostokątnego możemy wykorzystać metodę opisaną wyżej dla prostokątów. Jej adaptacja jest natychmiastowa:
Najpierw losujemy punkt P2, który będzie środkiem okręgu o promieniu równym podstawie a:
Dowolny punkt na obwodzie okręgu posiada współrzędne:
Kąt α jest katem pomiędzy prostą równoległą do osi OX układu współrzędnych i przechodzącą przez środek okręgu, a drugą prostą przechodzącą przez środek i wybrany punkt P1 na obwodzie okręgu. Aby otrzymać jego współrzędne, wystarczy wylosować kat α w zakresie od 0 do 2π radianów i użyć podanych wzorów. W ten sposób otrzymamy dwa wierzchołki trójkąta. Trzeci wierzchołek leży na prostej prostopadłej do odcinka P1P2 i przechodzącej przez P2. Losujemy zatem promień b, środek nowego okręgu umieszczamy w punkcie P2 i wybieramy na obwodzie punkt P3 o współrzędnych wg wzoru:
Otrzymane punkty łączymy odcinkami i mamy trójkąt prostokątny.
Poniższy program wykorzystuje tę metodę do tworzenia i animacji trójkątów prostokątnych:
C++// Trójkąty 2 //----------- #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 trójkątów const int N = 50; // Kąt prosty w radianach const double PI2 = M_PI / 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][4]; int dx[N],dy[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,a,b; double alpha; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy P2 T[i][1].x = W_W / 2 - W_W / 6 + (rand() % W_W) / 3; T[i][1].y = W_H / 2 - W_H / 6 + (rand() % W_H) / 3; // Losujemy promień a i kąt alpha a = 10 + rand() % x; alpha = 2 * M_PI * rand() / RAND_MAX; // Wyliczamy P1 T[i][0].x = T[i][1].x + a * cos(alpha); T[i][0].y = T[i][1].y + a * sin(alpha); // Losujemy promień b b = 10 + rand() % x; // Wyliczamy P3 T[i][2].x = T[i][1].x + b * cos(alpha+PI2); T[i][2].y = T[i][1].y + b * sin(alpha+PI2); // Zamykamy trójkąt T[i][3] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne trójkąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],4); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) T[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) T[i][j].y += dy[i]; T[i][3] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Do wygenerowania trójkąta równobocznego również wykorzystamy metodę trygonometryczną. Zasada jest następująca:
Losujemy punkt S, który będzie środkiem okręgu opisującego trójkąt. Losujemy promień okręgu r:
Losujemy kąt α, który wyznaczy nam na obwodzie okręgu wierzchołek startowy trójkąta:
Kolejne dwa wierzchołki otrzymamy odejmując i dodając do kąta α kąt 2/3π:
Punkty wystarczy połączyć odcinkami i mamy trójkąt równoboczny:
Poniższy program wykorzystuje tę metodę do generacji trójkątów równobocznych:
C++// Trójkąty 3 //----------- #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 trójkątów const int N = 50; // Kąt 2/3 pi radianów const double PI23 = 2 * M_PI / 3; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][4]; int dx[N],dy[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,sr,xs,ys; double alpha; // Środek okręgu xs = W_W / 2; ys = W_H / 2; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy kąt alpha i promień okręgu sr = 10 + rand() % x; alpha = 2 * M_PI * rand() / RAND_MAX; // Wyliczamy wierzchołek A T[i][0].x = xs + sr * cos(alpha); T[i][0].y = ys + sr * sin(alpha); // Wyliczamy wierzchołki B i C T[i][1].x = xs + sr * cos(alpha - PI23); T[i][1].y = ys + sr * sin(alpha - PI23); T[i][2].x = xs + sr * cos(alpha + PI23); T[i][2].y = ys + sr * sin(alpha + PI23); // Zamykamy trójkąt T[i][3] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne trójkąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],4); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) T[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) T[i][j].y += dy[i]; T[i][3] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Trójkąt równoramienny posiada dwa boki o równej długości. Narysujemy go za pomocą prawie tej samej metody, co dla trójkąta równobocznego.
Losujemy punkt S, który będzie środkiem okręgu opisującego trójkąt. Losujemy promień okręgu r:
Losujemy kąt α, który wyznaczy nam na obwodzie okręgu wierzchołek C trójkąta:
Losujemy kąt β z zakresu (0...π). Kolejne wierzchołki otrzymamy dodając i odejmując ten kąt od kąta α:
Przykładowy program:
C++// Trójkąty 4 //----------- #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 trójkątów const int N = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][4]; int dx[N],dy[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,sr,xs,ys; double alpha,beta; // Środek okręgu xs = W_W / 2; ys = W_H / 2; // Losujemy współrzędne wierzchołków, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy kąt alpha i promień okręgu sr = 10 + rand() % x; alpha = 2 * M_PI * rand() / RAND_MAX; // Wyliczamy wierzchołek C T[i][2].x = xs + sr * cos(alpha); T[i][2].y = ys + sr * sin(alpha); // Losujemy kąt beta beta = M_PI * rand() / RAND_MAX; // Wyliczamy wierzchołki A i B T[i][0].x = xs + sr * cos(alpha - beta); T[i][0].y = ys + sr * sin(alpha - beta); T[i][1].x = xs + sr * cos(alpha + beta); T[i][1].y = ys + sr * sin(alpha + beta); // Zamykamy trójkąt T[i][3] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne trójkąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],4); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < 4; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < 4; j++) T[i][j].x += dx[i]; for(j = 0; j < 4; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < 4; j++) T[i][j].y += dy[i]; T[i][3] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Do narysowania dowolnego wielokąta foremnego wykorzystamy metodę opisaną dla trójkąta równobocznego:
Losujemy punkt S, który będzie środkiem okręgu opisującego wielokąt. Losujemy promień okręgu r:
Losujemy kąt α, który wyznaczy nam na obwodzie okręgu pierwszy wierzchołek wielokąta:
Pozostałe wierzchołki otrzymamy zwiększając kąt α kolejno o kąt 2π/n, gdzie n oznacza liczbę wierzchołków wielokąta (przykład przedstawia sześciokąt):
A uogólniając wzory dla n-kąta foremnego mamy:
Otrzymane punkty łączymy odcinkami i otrzymujemy wielokąt foremny:
Poniższy program tworzy n-kąty foremne (n = 3...9) i animuje je w oknie SDL2:
C++// Wielokąty foremne //------------------ #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 wielokątów const int N = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][10]; int dx[N],dy[N],n[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,sr,xs,ys; double alpha; // Środek okręgu xs = W_W / 2; ys = W_H / 2; // Losujemy liczby wierzchołków, współrzędne wierzchołków, // przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy liczbę wierzchołków od 3 do 9: n[i] = 3 + rand() % 7; // Losujemy kąt alpha i promień okręgu sr = 10 + rand() % x; alpha = 2 * M_PI * rand() / RAND_MAX; // Wyliczamy kolejne wierzchołki wielokata for(j = 0; j < n[i]; j++) { T[i][j].x = xs + sr * cos(alpha + j * 2 * M_PI / n[i]); T[i][j].y = ys + sr * sin(alpha + j * 2 * M_PI / n[i]); } // Zamykamy wielokąt T[i][n[i]] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],n[i]+1); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < n[i]; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < n[i]; j++) T[i][j].x += dx[i]; for(j = 0; j < n[i]; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < n[i]; j++) T[i][j].y += dy[i]; T[i][n[i]] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Do wyznaczania współrzędnych stosujemy wzory:
gdzie n jest odpowiednio duże. Poniżej przykład programu, który rysuje okręgi wg tej metody:
C++// Okręgi i elipsy 1 //------------------ #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 okręgów const int N = 30; // Liczba wierzchołków const int M = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][M+1]; int dx[N],dy[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,sr,xs,ys; // Środek okręgu xs = W_W / 2; ys = W_H / 2; // Losujemy liczby wierzchołków, współrzędne wierzchołków, // przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy promień okręgu sr = 10 + rand() % x; // Wyliczamy kolejne wierzchołki for(j = 0; j < M; j++) { T[i][j].x = xs + sr * cos(j * 2 * M_PI / M); T[i][j].y = ys + sr * sin(j * 2 * M_PI / M); } // Zamykamy wielokąt T[i][M] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],M + 1); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < M; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < M; j++) T[i][j].x += dx[i]; for(j = 0; j < M; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < M; j++) T[i][j].y += dy[i]; T[i][M] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Jeśli chcemy narysować elipsę, to musimy zmodyfikować wzór na poszczególne punkty:
Zwróć uwagę, że teraz mamy dwa promienie: poziomy rx oraz pionowy ry:
C++// Okręgi i elipsy 2 //------------------ #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 okręgów const int N = 30; // Liczba wierzchołków const int M = 50; 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point T[N][M+1]; int dx[N],dy[N],cr[N],cg[N],cb[N], dr[N],dg[N],db[N],i,j,x,rx,ry,xs,ys; // Środek elipsy xs = W_W / 2; ys = W_H / 2; // Losujemy liczby wierzchołków, współrzędne wierzchołków, // przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy promienie elipsy rx = 10 + rand() % x; ry = 10 + rand() % x; // Wyliczamy kolejne wierzchołki for(j = 0; j < M; j++) { T[i][j].x = xs + rx * cos(j * 2 * M_PI / M); T[i][j].y = ys + ry * sin(j * 2 * M_PI / M); } // Zamykamy wielokąt T[i][M] = T[i][0]; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne wielokąty for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderDrawLines(r,T[i],M + 1); } // Modyfikujemy współrzędne i kolory for(i = 0; i < N; i++) { for(j = 0; j < M; j++) if((T[i][j].x + dx[i] < 0) || (T[i][j].x + dx[i] >= W_W)) { dx[i] = -dx[i]; break; } for(j = 0; j < M; j++) T[i][j].x += dx[i]; for(j = 0; j < M; j++) if((T[i][j].y + dy[i] < 0) || (T[i][j].y + dy[i] >= W_H)) { dy[i] = -dy[i]; break; } for(j = 0; j < M; j++) T[i][j].y += dy[i]; T[i][M] = T[i][0]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Zastanów się, jak uzyskać elipsę obróconą o zadany kąt α. Wskazówkę znajdziesz w rozdziale poświęconym punktom.
Otrzymane w powyższych dwóch programach okręgi i elipsy nie są zbyt ładne, szczególnie gdy są duże. Spowodowane to jest błędami zaokrągleń – obliczenia wykonywane są jako zmiennoprzecinkowe, lecz ich wyniki zostają zapamiętane w postaci całkowitej. Istnieje specjalny algorytm, zwany algorytmem Bresenhama, który rysuje bardzo ładne okręgi i elipsy bez korzystania z funkcji trygonometrycznych. Algorytm ten pracuje tylko z liczbami całkowitymi.
Zacznijmy od okręgu.
Okrąg jest figurą symetryczną, dzięki temu nie musimy wyliczać wszystkich jego punktów. Przyjrzyj się poniższemu rysunkowi:
Załóżmy, że mamy wyznaczony punkt P1 o współrzędnych x1, y1. Środek okręgu znajduje się w środku układu współrzędnych (za chwilę to zmienimy). Dzięki symetrii pozostałe punkty wyznaczymy jako:
P2: x2
= y1, y2 = x1
P3: x3 = y1, y3
= -x1
P4: x4 = x1, y4
= -y1
P5: x5 = -x1, y5
= -y1
P6: x6 = -y1, y6
= -x1
P7: x7 = -y1, y7
= x1
P8: x8 = -x1, y8
= y1
Jak widzisz, współrzędne tych punktów otrzymamy bezpośrednio ze współrzędnych punktu P1. Wystarczy zatem wyznaczyć współrzędne punktów w zaznaczonej na zielono 1/8 obwodu okręgu, a resztę punktów dostaniemy dzięki symetrii.
Jeśli okrąg ma środek w w punkcie S(xs,ys), to współrzędne środka należy dodać do współrzędnych punktów, zatem powyższe wzory przyjmują postać:
P1: x1 + xs, y1
+ ys
P2: x2 = y1
+ xs, y2 = x1 + ys
P3: x3 = y1 + xs,
y3 = -x1 + ys
P4: x4 = x1 + xs,
y4 = -y1 + ys
P5: x5 = -x1 + xs,
y5 = -y1 + ys
P6: x6 = -y1 + xs,
y6 = -x1 + ys
P7: x7 = -y1 + xs,
y7 = x1 + ys
P8: x8 = -x1 + xs,
y8 = y1 + ys
Wyjdziemy z podstawowego równania okręgu:
gdzie x, y – współrzędne punktu na
obrysie okręgu, a r – promień okręgu.
Rysowanie okręgu rozpoczynamy w punkcie (0,r). Kolejne punkty będziemy wyznaczali poruszając się w kierunku zgodnym z ruchem wskazówek zegara aż do osiągnięcia pozycji, dla której x = y.
W rozważanej 1/8 obwodu okręgu z piksela P przechodzimy (zapalamy) do jednego z dwóch pikseli P1 lub P2. Oznaczmy wyrażenie błędu przez:
Zachodzą następujące zależności:
e < 0 | , punkt (x,y) leży wewnątrz okręgu |
e = 0 | , punkt (x,y) leży dokładnie na obwodzie okręgu |
e > 0 | , punkt (x,y) leży na zewnątrz okręgu |
Dla obu punktów P1 i P2 obliczamy wyrażenie błędu:
e1 = e + 2x + 1 |
P2 = (x + 1, y - 1)
e2 =
e + (x + 1)2 + (y - 1)2
- r2
e2 = e + (x + 1)2 +
(y - 1)2
- x2 - y2
e2 = e + x2
+ 2x + 1 + y2
- 2y + 1 - x2 - y2
e2 =
e + 2x - 2y + 2
e2 = e1 - 2y + 1 |
Gdy wyznaczymy błędy dla obu punktów, obliczamy odchyłkę e12:
e12 = 2e + 4x - 2y + 3 |
Badając znak odchyłki wybieramy:
e12 ≥ 0 - punkt P2, czyli x → x + 1, y → y - 1
Powód takiego postępowania wyjaśnia poniższa tabelka:
e1 | e2 | e12 = e1 + e2 |
Następny piksel |
Uwagi |
< 0 | < 0 | < 0 | P1 = (x + 1, y) | P1 i P2 są wewnątrz okręgu, lecz P1 jest bliżej brzegu |
< 0 | ≥ 0 | ? | ? | Ta sytuacja nie może wystąpić, gdyż oznaczałoby to, iż bliższy środkowi okręgu punkt P2 jest na zewnątrz lub na obrysie, a dalszy P1 jest wewnątrz okręgu. |
≥ 0 | < 0 | < 0 | P1 = (x + 1, y) | Punkt P2 jest wewnątrz okręgu, a P1 na zewnątrz lub na obrysie. Jednakże P1 jest bliżej obrysu lub w takiej samej odległości od niego jak punkt P2 |
≥ 0 | ≥ 0 | ≥ 0 | P2 = (x + 1, y - 1) | Oba punkty są albo na zewnątrz okręgu, albo na zewnątrz jest P1, a P2 leży na obrysie. W każdym przypadku P2 jest bliżej obrysu. |
Wartość początkową e przyjmujemy równą 0.
Po wyborze punktu P1 za nowe e przyjmujemy e1, a po wyborze P2 wyrażenie błędu e przyjmuje wartość e2. Wyznaczanie punktów kończy się, gdy x > y.
Poniższy program wykorzystuje algorytm Bresenhama do rysowania i animacji okręgów.
C++// Okręgi i elipsy 3 //------------------ #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 okręgów const int N = 30; // Funkcja rysuje okrąg algorytmem Bresenhama //------------------------------------------- void SDL_RenderCircle(SDL_Renderer * r, int xs, int ys, int rs) { int x,y,e,e1,e2; SDL_Point P[8]; // Inicjujemy zmienne e = x = 0; y = rs; while(x <= y) { // Rysujemy 8 pikseli P[0].x = x + xs; P[0].y = y + ys; P[1].x = y + xs; P[1].y = -x + ys; P[2].x = -x + xs; P[2].y = -y + ys; P[3].x = -y + xs; P[3].y = x + ys; P[4].x = y + xs; P[4].y = x + ys; P[5].x = x + xs; P[5].y = -y + ys; P[6].x = -y + xs; P[6].y = -x + ys; P[7].x = -x + xs; P[7].y = y + ys; SDL_RenderDrawPoints(r,P,8); // Obliczamy wyrażenia błędów e1 = e + 2 * x + 1; e2 = e1 - 2 * y + 1; x++; // krok w osi x if(e1 + e2 < 0) e = e1; else { y--; // krok w osi y e = e2; } } } 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point S[N]; // Środki okręgów int R[N]; // Promienie okręgów int dx[N],dy[N],cr[N],cg[N],cb[N],dr[N],dg[N],db[N],i,x; // Losujemy promienie, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy promienie okręgów R[i] = 10 + rand() % x; // Środki okręgów S[i].x = W_W / 2; S[i].y = W_H / 2; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne okręgi for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderCircle(r,S[i].x,S[i].y,R[i]); } // Modyfikujemy współrzędne środków i kolory for(i = 0; i < N; i++) { if((S[i].x + dx[i] - R[i] < 0) || (S[i].x + dx[i] + R[i] > W_W)) dx[i] = -dx[i]; if((S[i].y + dy[i] - R[i] < 0) || (S[i].y + dy[i] + R[i] > W_H)) dy[i] = -dy[i]; S[i].x += dx[i]; S[i].y += dy[i]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
Z elipsą jest nieco trudniej, ponieważ posiada ona tylko dwie osie symetrii. W konsekwencji musimy wyznaczyć punkty leżące na ćwiartce obwodu (linia w kolorze zielonym na rysunku powyżej).
Algorytm zakłada, że środek elipsy leży w środku układu współrzędnych. Jeśli tak nie jest, to do współrzędnych wyznaczonych punktów należy dodać współrzędne środka. Wyznaczony będzie punkt P1 z pierwszej ćwiartki elipsy. Pozostałe punkty iuzyskuje się poprzez symetrię:
Zapiszmy równanie elipsy:
x,y – współrzędne punktu na obwodzie elipsy
rx – promień elipsy wzdłuż
osi OX
ry – promień elipsy wzdłuż
osi OY
Aby unikać dzielenia, przekształcamy równanie elipsy do poniższej postaci:
Dla danego punktu P = (x,y) wyrażenie błędu:
określa położenie tego punktu w stosunku do obrysu elipsy. Jeśli e jest ujemne, to punkt P leży wewnątrz elipsy. Jeśli e jest dodatnie, punkt P leży poza elipsą. Jeśli wyrażenie błędu e przyjmie wartość 0, to punkt P leży dokładnie na obrysie elipsy - jego współrzędne spełniają równanie elipsy. Piszemy o tym dlatego, iż tekstura składa się z siatki pikseli o całkowitych współrzędnych. Nie wszystkie wyznaczone punkty (szczerze mówiąc duża większość z nich) nie będą spełniały równania elipsy, ponieważ jej linia przebiega tak, iż nie ma pikseli dokładnie ją odwzorowujących (patrz - rysunek poniżej). Wyznaczone piksele będą przybliżać rzeczywistą linię elipsy - zatem ze zbioru pikseli tekstury zostaną przez nasz algorytm wybrane te piksele, których środki leżą najbliżej rzeczywistej linii elipsy.
Rysowanie elipsy rozpoczynamy od punktu P = (0,ry) i będziemy poruszali się zgodnie z ruchem wskazówek zegara w obrębie pierwszej ćwiartki elipsy. Jeśli przyjrzysz się dokładnie rysunkowi powyżej, to powinieneś zauważyć, iż ćwiartka elipsy dzieli się na dwie części:
Oczywistym zatem jest, iż na obrysie ćwiartki elipsy musi być punkt, gdzie zmieniamy sposób modyfikacji współrzędnych pikseli. Punkt ten znajdziemy analizując styczną do obrysu elipsy, a właściwie jej współczynnik kierunkowy, który jest równy pochodnej funkcji opisującej elipsę.
W dolnej części ćwiartki przyrost x jest na moduł mniejszy od przyrostu y:
Wynika stąd, iż zmiana następuje w punkcie P2 (patrz rysunek powyżej), którego współrzędne (x,y) spełniają równanie:
Rozważmy górną połówkę ćwiartki elipsy. Podobnie jak w
przypadku okręgu z punktu P(x,y) możemy przejść tylko do
e1
= e + ry2(x + 1)2 + rx2y2
- rx2ry2
e1 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - rx2ry2,
zastępujemy : - rx2ry2
wyrażeniem - ry2x2
- rx2y2
e1 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - ry2x2
- rx2y2 i upraszczamy
otrzymując ostatecznie:
e1 = e + 2ry2x + ry2 |
P2: (x + 1, y + 1)
e2 = e + ry2(x + 1)2
+ rx2(y - 1)2 - rx2ry2
e2 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - 2rx2y
+ rx2 - rx2ry2
e2 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - 2rx2y
+ rx2 - ry2x2
- rx2y2
e2 = e + 2ry2x + ry2
- 2rx2y + rx2
e2 = e1 - 2rx2y + rx2 |
Obliczamy odchyłkę:
Badając znak odchyłki wybieramy:
e12 < 0 - punkt P1, czyli x → x + 1, y → y, e1 → e
e12 ≥ 0 - punkt P2, czyli x → x + 1, y → y - 1, e2 → e
Wyjaśnienie tego faktu znajdziesz w opisie algorytmu Bresenhama dla okręgu. Wyznaczanie punktów w górnej części ćwiartki elipsy kontynuujemy do momentu, gdy przestanie być spełniona nierówność:
Teraz przechodzimy do rysowania dolnej części ćwiartki obwodu
elipsy. Z punktu P(x,y) możemy przejść albo do punktu
e1 = e + ry2x2
+ rx2(y - 1)2 - rx2ry2
e1 = e + ry2x2
+ rx2y2 - 2rx2y
+ rx2 - rx2ry2,
zastępujemy : - rx2ry2
wyrażeniem - ry2x2
- rx2y2
e1 = e + ry2x2
+ rx2y2 - 2rx2y
+ rx2 - ry2x2
- rx2y2 i upraszczamy
otrzymując ostatecznie:
e1 = e - 2rx2y + rx2 |
P2: (x + 1, y + 1)
e2 = e + ry2(x + 1)2
+ rx2(y - 1)2 - rx2ry2
e2 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - 2rx2y
+ rx2 - rx2ry2
e2 = e + ry2x2
+ 2ry2x + ry2 +
rx2y2 - 2rx2y
+ rx2 - ry2x2
- rx2y2
e2 = e + 2ry2x + ry2
- 2rx2y + rx2
e2 = e1 + 2ry2x + ry2 |
Obliczamy odchyłkę:
Badając znak odchyłki wybieramy:
e12 ≥ 0 - punkt P1, czyli x → x, y → y - 1, e1 → e
C++// Okręgi i elipsy 4 //------------------ #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 okręgów const int N = 30; // Funkcja rysuje elipsę algorytmem Bresenhama //------------------------------------------- void SDL_RenderEllipse(SDL_Renderer * r, int xs, int ys, int rx, int ry) { int x,y,e,e1,e2,rx2,ry2,fx,fy; SDL_Point P[4]; // Inicjujemy punkt startowy x = e = 0; y = ry; rx2 = rx * rx; ry2 = ry * ry; fx = 0; fy = rx2 * ry; while(fx <= fy) { // Rysujemy górne punkty ćwiartki P[0].x = x + xs; P[0].y = y + ys; P[1].x = x + xs; P[1].y = -y + ys; P[2].x = -x + xs; P[2].y = y + ys; P[3].x = -x + xs; P[3].y = -y + ys; SDL_RenderDrawPoints(r,P,4); // Wyliczamy wyrażenia błędów e1 = e + (fx << 1) + ry2; e2 = e1 - (fy << 1) + rx2; fx += ry2; // W górnej części ćwiartki x jest zawsze zwiększane x++; if(e1 + e2 < 0) e = e1; // Punkt P1 else { y--; fy -= rx2; e = e2; // Punkt P2 } } // Teraz dolna część ćwiartki while(y >= 0) { // Rysujemy dolne punkty ćwiartki P[0].x = x + xs; P[0].y = y + ys; P[1].x = x + xs; P[1].y = -y + ys; P[2].x = -x + xs; P[2].y = y + ys; P[3].x = -x + xs; P[3].y = -y + ys; SDL_RenderDrawPoints(r,P,4); // Wyliczamy wyrażenia błędów e1 = e - (fy << 1) + rx2; e2 = e1 + (fx << 1) + ry2; fy -= rx2; // W dolnej części ćwiartki y jest zawsze zmniejszane y--; if(e1 + e2 >= 0) e = e1; // Punkt P1 else { x++; fx += ry2; e = e2; // Punkt P2 } } } 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("Figury", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, W_W, W_H, 0); 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; } // Inicjujemy generator pseudolosowy srand(time(NULL)); // Inicjujemy zmienne SDL_Point S[N]; // Środki elips int Rx[N],Ry[N]; // Promienie elips int dx[N],dy[N],cr[N],cg[N],cb[N],dr[N],dg[N],db[N],i,x; // Losujemy promienie, przyrosty ruchów i składowe kolorów x = sqrt (W_W * W_W + W_H * W_H) / 6; for(i = 0; i < N; i++) { // Losujemy promienie elips Rx[i] = 10 + rand() % x; Ry[i] = 10 + rand() % x; // Środki elips S[i].x = W_W / 2; S[i].y = W_H / 2; // Przesunięcia do dx[i] = -3 + rand() % 7; while(!dx[i]); do dy[i] = -3 + rand() % 7; while(!dy[i]); // Kolory cr[i] = rand() % 256; cg[i] = rand() % 256; cb[i] = rand() % 256; do dr[i] = -2 + rand() % 5; while(!dr[i]); do dg[i] = -2 + rand() % 5; while(!dg[i]); do db[i] = -2 + rand() % 5; while(!db[i]); } // Animacja SDL_Event e; while(1) { // Kasujemy treść okna SDL_SetRenderDrawColor(r,0,0,0,255); SDL_RenderClear(r); // Rysujemy kolejne elipsy for(i = 0; i < N; i++) { SDL_SetRenderDrawColor(r,cr[i],cg[i],cb[i],255); SDL_RenderEllipse(r,S[i].x,S[i].y,Rx[i],Ry[i]); } // Modyfikujemy współrzędne środków i kolory for(i = 0; i < N; i++) { if((S[i].x + dx[i] - Rx[i] < 0) || (S[i].x + dx[i] + Rx[i] > W_W)) dx[i] = -dx[i]; if((S[i].y + dy[i] - Ry[i] < 0) || (S[i].y + dy[i] + Ry[i] > W_H)) dy[i] = -dy[i]; S[i].x += dx[i]; S[i].y += dy[i]; if((cr[i] + dr[i] < 0) || (cr[i] + dr[i] > 255)) dr[i] = -dr[i]; if((cg[i] + dg[i] < 0) || (cg[i] + dg[i] > 255)) dg[i] = -dg[i]; if((cb[i] + db[i] < 0) || (cb[i] + db[i] > 255)) db[i] = -db[i]; cr[i] += dr[i]; cg[i] += dg[i]; cb[i] += db[i]; } // Uaktualniamy okno SDL_RenderPresent(r); // Sprawdzamy, czy użytkownik nie zamyka programu if(SDL_PollEvent(&e) && e.type == SDL_QUIT) break; } // Usuwamy kontekst graficzny SDL_DestroyRenderer(r); // Usuwamy okno SDL_DestroyWindow(w); // Kończymy pracę z SDL2 SDL_Quit(); return 0; } |
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2023 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.