PROGRAMOWANIE – PRZYKŁADY
IDE C++ WinBGI C# Wpadki

Przykład C++

Wielokąt foremny i gwiazdka Kreślenie wielokąta Kreślenie gwiazdki Programy w C++ Argumenty domyślne Program w C++ (flaga USA) Poprzedni przykład Następny przykład Program w Visual C# Kontakt

Wielokąt foremny i gwiazdka

Zadanie rysowania wielokąta foremnego lub gwiazdki równora­miennej stanowi szcze­gólny przypadek zaga­dnienia ryso­wania linii łamanej występu­jącego w grafice kompute­rowej. Na przykład operacja ryso­wania krzywej, nawet bardzo złożonej, sprowadza się do naryso­wania łamanej o odpo­wiednio dużej liczbie wierz­chołków leżących na tej krzywej. Technika rysowania łamanej o wierz­chołkach P0,P1,...,Pn polega na przenie­sieniu pisaka do wierz­chołka początko­wego P0, a nastę­pnie wykonania pętli rysującej kolejne odcinki Pk-1Pk dla k=1,2,...,n. W przy­padku wielokąta i gwiazdki, które są łamanymi zamkniętymi, wierz­chołek początkowy jest również końcowym: P0=Pn.

Kreślenie wielokąta

Wierzchołki wielokąta foremnego są równomiernie rozło­żone na okręgu (rys.). Jeżeli R jest promieniem tego okręgu, p i q są współrzę­dnymi jego środka S oraz promień łączący punkt SP0 jest równo­legły do osi x, to współ­rzędne xkyk wierz­chołków Pk wielo­kąta można dla k=0,1,...,n wyzna­czyć następująco:

gdzie

Podstawowymi narzędziami biblioteki WinBGI do ryso­wania łamanej są funkcje movetolineto. Pierwsza ustawia pisak w okre­ślonym punkcie obszaru robo­czego okna grafi­cznego, druga rysuje odcinek od bieżącej pozycji pisaka do jego nowej pozycji. Ich argumenty typu całko­witego określają współ­rzędne punktu, do którego pisak ma być przesu­nięty. Zatem operację ryso­wania wielokąta fore­mnego można zaprogra­mować w postaci funkcji:

void wielokat(int n, int p, int q, double R)
{
    double alpha = 2 * M_PI / n;
    moveto(R + p, q);
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha;
        lineto(R * cos(fi) + p, q - R * sin(fi));
    }
}

Zmiana znaku (minus zamiast plus) w wyrażeniu występu­jącym w roli drugiego argumentu wywołania funkcji lineto wynika z odwro­tnego skiero­wania osi y układu współrzę­dnych okna graficznego. Użycie stałej M_PI w wyrażeniu inicjali­zującym zmienną alpha oraz funkcji trygonome­trycznych sincos wymaga włączenia pliku nagłówko­wego math.h lub cmath. Należy pamiętać, że w przy­padku Visual C++ stałe matema­tyczne są dostępne pod warunkiem uprze­dniego zdefinio­wania symbolu _USE_MATH_DEFINES:

#define _USE_MATH_DEFINES
#include <cmath>

(alternatywnie można go wpisać do listy definicji preprocesora). Kompilator Visual C++ jest bardziej rygorystyczny niż Borland C++ i MinGW C++, co przejawia się w jego ostrze­żeniach przed niejawną konwersją wartości argumentów typu double na int funkcji movetolineto. Oczywiście konwersja niejawna daje w tym przypadku wyniki zgodne z oczeki­waniami, ale ostrzeżeń można uniknąć, stosując konwersję jawną:

moveto(int(R) + p, q);
...
lineto(int(R * cos(fi)) + p, q - int(R * sin(fi)));

Kreślenie gwiazdki

Nietrudno jest przejść od rysowania wielokąta foremnego do gwiazdki równora­miennej, która ma 2n wierz­chołków leżących naprze­miennie na dwóch koncentry­cznych okręgach (rys.). Oprócz n wierz­chołków wielo­kąta jej dodatkowe n wierz­chołków Q1,Q2,...,Qn leży na drugim okręgu o pro­mieniu r i środku S. Nietrudno zauważyć, że współ­rzędne ukvk wierz­chołków Qk dla k=1,2,...,n spełniają równości:

w których

Narzucający się algorytm rysowania gwiazdki sprowadza się do przenie­sienia pisaka do punktu P0 i wyko­nania pętli, która dla k=1,2,...,n rysuje dwa odcinki Pk-1QkQkPk zamiast jednego odcinka Pk-1Pk w przy­padku wielo­kąta foremnego. Operację rysowania gwiazdki równora­miennej można więc zaprogra­mować w postaci funkcji:

void gwiazdka(int n, int p, int q, double R, double r)
{
    double alpha2 = M_PI / n, alpha = 2 * alpha2;
    moveto(R + p, q);
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha, psi = fi - alpha2;
        lineto(r * cos(psi) + p, q - r * sin(psi));
        lineto(R * cos(fi) + p, q - R * sin(fi));
    }
}

Programy w C++

Opisany wyżej algorytm rysowania gwiazdki równora­miennej został wykorzy­stamy w programie przedsta­wionym na poniższym listingu. Program wczytuje liczbę n ramion gwiazdki i promień r okręgu określa­jącego jej wielkość, otwiera okno grafi­czne oraz ustawia kolor pisaka (YELLOW – żółty) i styl wypeł­niania obszarów (SOLID_FILL – pełne wypeł­nianie kolorem żółtym). Następnie rysuje pośrodku obszaru robo­czego okna gwiazdkę o 2n wierz­chołkach leżących na przemian na okręgach o promie­niach r i r/2, po czym zapełnia ją od środka aż do napo­tkania brzegu w aktualnym kolorze pisaka (dzia­łanie funkcji floodfill budzi skoja­rzenie z płonącą mapą z czołówki filmu Bonanza).

#include <iostream>
#include <graphics.h>
#include <math.h>

using namespace std;

void gwiazdka(int n, int p, int q, double R, double r)
{
    double alpha2 = M_PI / n, alpha = 2 * alpha2;
    moveto(R + p, q);
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha, psi = fi - alpha2;
        lineto(r * cos(psi) + p, q - r * sin(psi));
        lineto(R * cos(fi) + p, q - R * sin(fi));
    }
    floodfill(p, q, getcolor());
}

int main()
{
    int n;
    double r;
    cout << "Liczba ramion: ";
    cin >> n;
    cout << "Promie\xe4 : ";
    cin >> r;
    initwindow(640, 480, "Gwiazdka");
    setcolor(YELLOW);
    setfillstyle(SOLID_FILL, YELLOW);
    gwiazdka( n, 320, 240, r, r / 2);
    getch();
    closegraph();
    return 0;
}

A oto wynik wykonania programu dla n=40 i r=200:

Inny sposób kreślenia wielokąta, w szczegól­ności gwiazdki, polega na użyciu funkcji drawpoly lub fillpoly. Pierwsza rysuje kontur wielokąta, druga go dodatkowo wypełnia. Obie wymagają dwóch argu­mentów: liczby wierz­chołków wielokąta i tablicy liczb całko­witych, z których każda para określa współ­rzędne x i y wierz­chołka wielokąta. Dla n-kąta należy podać n+1 punktów (2n+2 współ­rzędnych). Pierwszy i ostatni powinny mieć te same współ­rzędne, w prze­ciwnym razie łamana nie zostanie zamknięta.

Funkcja fillpoly tworzy efektowny rysunek gwiazdki, gdy wielokąt ma niepa­rzystą liczbę wierz­chołków i nie są one podane kolejno według wzrasta­jących indeksów, lecz najpierw te o indeksach parzystych, a potem o niepa­rzystych. Taka kolejność (co drugi wierz­chołek) wymusza podwójny obieg pisaka wokół środka rysowanego konturu. Wypełniane są tylko te obszary zamknięte, których środek był obiegany tylko raz podczas rysowania ich brzegu. Oto prosty program rysujący w ten sposób gwiazdkę siedmio­ramienną:

#include <graphics.h>
#include <math.h>

void gwiazdka(int n, int p, int q, double r)
{
    double alpha = 4 * M_PI / n;
    int *P = new int[2 * n + 2];
    for (int k = 0; k <= n; k++)
    {
        double fi = k * alpha;
        P[2 * k] = r * cos(fi) + p;
        P[2 * k + 1] = q - r * sin(fi);
    }
    fillpoly(n + 1, P);
    delete[] P;
}

int main()
{
    initwindow(300, 250, "Gwiazdka");
    setfillstyle(SOLID_FILL, MAGENTA);
    gwiazdka(7, 150, 125, 100);
    getch();
    closegraph();
    return 0;
}

Wynik wykonania programu przedstawiony jest poniżej. Kontur gwiazdki ma domyślnie kolor biały, a jej narożne obszary fioletowy. Obszar centralny nie został wypełniony, gdyż podczas rysowania jego brzegu pisak obiegał środek gwiazdki dwukrotnie.

Argumenty domyślne

W języku C++ można definiować funkcje o domyślnych argu­mentach. Wartości takich argu­metów są określane bezpo­średnio w nagłówku funkcji – albo w jej proto­typie, albo w defi­nicji, gdy nie ma proto­typu. Argu­menty domyślne mogą być umieszczone tylko z prawej strony listy argu­mentów (jeśli któryś z argu­mentów ma wartość domyślną, to wszystkie występu­jące po nim też muszą mieć wartości domyślne). W wywo­łaniu funkcji można nie podawać wartości argu­mentów domyślnych – wówczas przyjęte zostają dla nich wartości domyślne.

Przykładem funkcji o domyślnych argumentach jest funkcja initwindow z biblio­teki WinBGI tworząca okno graficzne. Jej dwa argu­menty określa­jące rozmiar obszaru roboczego okna są obowiązkowe, pozostałe trzy są domyślne:

void initwindow(
     int width,                         // szerokość obszaru roboczego okna
     int height,                        // wysokość obszaru roboczego okna
     const char* title = "Windows BGI", // napis na pasku tytułowym okna
     int left = 0,                      // odległość okna od lewego brzegu ekranu
     int top = 0                        // odległość okna od górnego brzegu ekranu
     );

Funkcję można wywołać z dwoma lub większą liczbą argumentów aktualnych. Gdy podane są tylko dwa argumenty, zostanie utwo­rzone okno o rozmiarze obszaru robo­czego okre­ślonym przez wartości tych argu­mentów, zatytu­łowane Windows BGI i poło­żone na ekranie w miejscu ustalonym przez system opera­cyjny. Tytuł okna można zmienić, podając stosowny łańcuch w trzecim argu­mencie wywołania, zaś poło­żenie okna na ekranie, umieszczając współ­rzędne ekranowe jego lewego górnego rogu w czwartym i piątym argumencie.

Zaprezentowana powyżej funkcja rysowania gwiazdki równora­miennej nie jest uniwer­salna, gdyż nie przewi­duje obrotu (promień SP0 jest zawsze równo­legły do osi x). Warto zatem ją zmodyfi­kować, wprowa­dzając dodatkowy argument określa­jący kąt obrotu wierz­chołków gwiazdki względem jej środka (rys.).

W udoskonalonej wersji funkcji gwiazdka najlepiej jest zadekla­rować nowy argument jako domyślny o wartości 0, gdyż pozwoli to na używanie funkcji w dotych­czasowy sposób – bez podawania wartości nowego argumentu, gdy obrót nie występuje. Uwzglę­dnienie kąta obrotu wymaga korekty obliczeń współ­rzędnych wierz­chołków gwiazdki: wartości obu argumentów wywołania funkcji moveto i zmiennej fi wewnątrz pętli for:

void gwiazdka(int n, int p, int q, double R, double r, double omega = 0)
{
    double alpha2 = M_PI / n, alpha = 2 * alpha2;
    moveto(R * cos(omega) + p, q - R * sin(omega));
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha + omega, psi = fi - alpha2;
        lineto(r * cos(psi) + p, q - r * sin(psi));
        lineto(R * cos(fi) + p, q - R * sin(fi));
    }
}

Jak pokazuje poniższy fragment prostego programu, nową funkcję kreślenia gwiazdki można wywołać z pięcioma lub sześcioma argumentami.

initwindow(400, 200, "Gwiazdki");
gwiazdka(5, 100, 100, 80, 40);          // gwiazdka 5-ramienna nieobrócona
gwiazdka(5, 300, 100, 80, 40, M_PI/2);  // gwiazdka 5-ramienna obrócona o 90o
getch();
closegraph();

A oto wynik jego wykonania:

Program w C++ (flaga USA)

Flaga Stanów Zjednoczonych Ameryki składa się z trzy­nastu równej wysokości poziomych pasów na przemian czerwo­nych i białych (czerwony na początku i na końcu) symboli­zujących 13 pierwo­tnych kolonii. W lewym górnym rogu flagi znajduje się niebieski kanton z pięćdzie­sięcioma pięciora­miennymi białymi gwiazdami symboli­zującymi 50 stanów. Są one rozmie­szczone w dzie­więciu poziomych rzędach po 6 (na górze i dole) i 5 gwiazd przemiennie.

Flaga i jej elementy mają w prawie USA ściśle określone proporcje. Zachowując je, można rozmiar flagi i jej poszcze­gólnych elementów wyrazić w pikselach za pomocą stałych zależnych od szero­kości pasa, np.:

const int P = 32;                   // Szerokość pasa
const int A = 13 * P;               // Szerokość flagi
const int B = 1.9 * A;              // Długość flagi
const int C = 7 * P;                // Szerokość kantonu
const int D = 0.76 * A;             // Długość kantonu
const int G = 0.0308 * A;           // Promień gwiazdy

Przy tych ustawieniach obszar roboczy okna graficznego ma rozmiar BxA. Do jego zapeł­nienia trzyna­stoma pasami o rozmiarze BxP, naprze­miennie czerwonym i białym, a także utworzenia w lewym górnym rogu niebieskiego kantonu o rozmiarze DxC, wygodnie jest skorzy­stać z funkcji bar, która rysuje wypeł­niony prostokąt (słupek dwuwy­miarowy). Na koniec należy narysować 50 białych gwiazdek. Zaprogra­mowanie tej operacji ułatwia siatka dzieląca kanton na 12x10 jedna­kowych prosto­kątów (rys.).

Środki gwiazdek znajdują się w węzłach siatki. I tak, dla grupy 30 gwiazdek rozlokowanych w 6 rzędach po 5 w każdym (rys. powyżej z lewej) współ­rzędne ich środków wyrażają się wzorami:

zaś dla grupy 20 gwiazdek ustawionych w 5 rzędach po 4 w każdym (rys. powyżej z prawej) wzorami:

Rozważania te prowadzą do następującego kodu źródło­wego programu w C++ rysują­cego flagę Stanów Zjedno­czonych Ameryki:

#include <graphics.h>
#include <math.h>

const int P = 32;                   // Szerokość pasa
const int A = 13 * P;               // Szerokość flagi
const int B = 1.9 * A;              // Długość flagi
const int C = 7 * P;                // Szerokość kantonu
const int D = 0.76 * A;             // Długość kantonu
const int G = 0.0308 * A;           // Promień gwiazdy

void gwiazdka(int n, int p, int q, double R, double r, double omega = 0)
{
    double alpha2 = M_PI / n, alpha = 2 * alpha2;
    moveto(R * cos(omega) + p, q - R * sin(omega));
    for (int k = 1; k <= n; k++)
    {
        double fi = k * alpha + omega, psi = fi - alpha2;
        lineto(r * cos(psi) + p, q - r * sin(psi));
        lineto(R * cos(fi) + p, q - R * sin(fi));
    }
    floodfill(p, q, getcolor());
}

int main()
{
    initwindow(B, A, "Flaga USA");
    for (int i = 0; i < 13; i++)
    {
        int y = i * P;
        setfillstyle(SOLID_FILL, (i % 2) ? WHITE : RED);
        bar(0, y, B, y + P);
    }
    setfillstyle(SOLID_FILL, BLUE);
    bar(0, 0, D, C);
    setfillstyle(SOLID_FILL, WHITE);
    for (int j = 1; j <= 5; j++)
    {
        int y = (2*j - 1) * C / 10;
        for (int i = 1; i <= 6; i++)
            gwiazdka(5, (2*i - 1) * D / 12, y, G, 0.4 * G, M_PI / 2);
    }
    for (int j = 1; j <= 4; j++)
    {
        int y = j * C / 5;
        for (int i = 1; i <= 5; i++)
            gwiazdka(5, i * D / 6, y, G, 0.4 * G, M_PI / 2);
    }
    getch();
    closegraph();
    return 0;
}

Poniższy rysunek prezentuje wynik wykonania programu.


Opracowanie przykładu: grudzień 2018