Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Janusz Ganczarski
Biblioteki C++

Przekształcenia geometryczne

[lekcja] Rozdział 4. Obrót, skalowanie, przesunięcie, mnożenie macierzy, ładowanie macierzy, składanie przekształceń, bryły w bibliotece GLUT (kula, sześcian, stożek, torus, dwunastościan, czajnik, ośmiościan, czworościan, dwudziestościan), operacje na stosie macierzy, stos macierzy modelowania; przeliczenia współrzędnych w przestrzeni widoku na współrzędne w przestrzeni okna i odwrotnie; obsługa myszki oraz dwa przykładowe programy.
W tym odcinku kursu poznamy mechanizmy biblioteki OpenGL umożliwiające dowolne przekształcenia geometryczne obiektów. Wykorzystamy także stos macierzy w celu modelowania sceny 3D zawierającej wiele elementów.

Obrót

Obrót w OpenGL wykonują funkcje glRotated i glRotatef:
C/C++
void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )
void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
których parametry oznaczają:
  • angle - kat obrotu w stopniach,
  • x, y, z - współrzędne wektora określającego os obrotu.

ArgumentZnaczenie
angleKąt obrotu w stopniach
x,y,zWspółrzędne wektora określającego oś obrotu.

Obrót realizowany jest w kierunku przeciwnym do ruchu wskazówek zegara w kierunku prostej wyznaczonej przez wektor [x, y, z] zaczepionym w początku układu współrzędnych. Jeżeli wektor ma długość rożną od 1, wektor jest normalizowany.
Funkcje glRotated i glRotatef mnożną bieżącą macierz przez przez macierz obrotu, która ma następującą postać (parametr angle zamieniono na a):

Skalowanie

Skalowanie wykonują następujące funkcje:
C/C++
void glScalef( GLfloat x, GLfloat y, GLfloat z )
void glScalex( GLfixed x, GLfixed y, GLfixed z )
gdzie x, y, z sa współczynnikami skalowania względem kolejnych osi układu współrzędnych. Funkcje te mnożą bieżącą macierz przez macierz skalowania, która ma postać:

Przesunięcie

Trzecia z podstawowych operacji geometrycznych jest przesuniecie o wektor (transalcja), która realizuja funkcje:

C/C++
void glTranslatef( GLfloat x, GLfloat y, GLfloat z )
void glTranslatex( GLfixed x, GLfixed y, GLfixed z )

gdzie x, y, z sa współrzędnymi wektora przesunięcia. Podobnie jak poprzednio opisane funkcje, glTranslatef i glTranslatex mnożą bieżącą macierz przez macierz translacji, która ma postać:

Mnożenie macierzy

W przypadku, gdy zestaw standardowych przekształceń macierzowych okazuje się niewystarczający, biblioteka OpenGL udostępnią możliwość przemnożenia bieżącej macierzy przez macierz określoną funkcja z grupy glMultMatrix:
C/C++
void glMultMatrixd( const GLdouble * m )
void glMultMatrixf( const GLfloat * m )
Parametr m obu funkcji zawiera wskaznik na dane macierzy o rozmiarach 4 × 4, które musza byc ułozone kolejno kolumnami, tj. w kolejności transponowanej w stosunku do przyjetej w jezykach C i C++:

Ładowanie macierzy

Ostatnia z opisywanych operacji na macierzach jest zastepowanie bieżącej macierzy przez dowolnie okreslona macierz o rozmiarach 4 × 4. Operacje te wykonuja funkcje z grupy glLoadMatrix:
C/C++
void glLoadMatrixd( const GLdouble * m )
void glLoadMatrixf( const GLfloat * m )
Format danych ładowanej macierzy jest taki sam jak w przedstawionych wyzej funkcjach glMultMatrixd i glMultMatrixf.

Składanie przekształceń

Macierzowy opis przekształceń geometrycznych znacznie ułatwia składanie kilku rożnych przekształcen obiektów. Wywołując funkcje generujące macierze odpowiednich przekształceń dokonujemy jednocześnie ich składania. W tej sytuacji jedyna rzeczą, o której należy pamiętać, to kolejność przekształceń, bowiem od tego zależy końcowy efekt. Przykładowy program (plik przeksztalcenia.cpp) przedstawia przekształcenia geometryczne dostępne w bibliotece OpenGL. Scena 3D zawiera pojedynczy obiekt geometryczny zawarty w bibliotece GLUT. Klawisze kursora pozwalają na obracanie wyświetlanego obiektu, przyciski ?+? i ?-? na jego skalowanie, a lewy przycisk myszki przesuwa obiekt. W ramach eksperymentu Czytelnik może w programie zamienić kolejność składania wykonywanych tam przekształceń (funkcja Display) np. przesunięcia i obrotu. Poznajmy jeszcze nowe elementy biblioteki GLUT wykorzystanie w przykładowym programie tj. sposób obsługi myszki i zdefiniowane obiekty geometryczne. Biblioteka GLUT udostępnia obiekty 3D w wersji szkieletowej (tzw. ?druciaki?) oraz w wersji pełnej. W najbliższych przykładowych programach wykorzystamy wersje szkieletowe obiektów.

Kula

Powierzchnia kuli rysowana jest jako szkielet składajacy sie z południków i równolezników (rysunek 1). Powierzchnie kuli, której środek znajduje się w poczatku układu współrzednych rysuje funkcja:
C/C++
void glutWireSphere( GLdouble radius, GLint slices, GLint stacks )
której parametry oznaczają:
  • radius - promien kuli,
  • slices - ilosc południków,
  • stacks - ilosc równolezników.
Rysunek 1. Program Przekształcenia - kula
Rysunek 1. Program Przekształcenia - kula

Sześcian

Szescian o boku długosci size i srodku połozonym w poczatku układu współrzednych rysuje funkcja:
C/C++
void glutWireCube( GLdouble size )

Stożek

Stozek rysowany jest podobnie jak kula - jako szkielet oparty na równoległe do podstawy ?południki? i tworzace biegnace od wierzchołka stozka do krawedzi jego podstawy (rysunek 2). Stozek ze srodkiem podstawy umieszczonym w poczatku układu współrzednych i wierzchołkiem umieszczonym na dodatniej półosi OZ rysuje funkcja:
C/C++
void glutWireCone( GLdouble base, GLdouble height, GLint slices, GLint stacks )
której parametry oznaczają:
  • base - promien podstawy stozka,
  • height - wysokosc stozka,
  • slices - ilosc tworzacych,
  • stacks - ilosc ?południków?.

Rysunek 2. Program Przekształcenia - stożek
Rysunek 2. Program Przekształcenia - stożek

Torus

Kolejna bryła obrotowa dostepna w bibliotece GLUT jest torus rysowany jako seria walców o nierównoległych podstawach (rysunek 3) i osi obrotu pokrywajacej sie z osia OZ. Torus rysuje funkcja:
C/C++
void glutWireTorus( GLdouble innerRadius, GLdouble outerRadius, GLint sides, GLint rings )
której parametry oznaczaja odpowiednio:
  • innerRadius - promien koła tworzacego torus,
  • outerRadius - promien torusa,
  • sides - ilosc scian bocznych, z których składa sie pojedynczy walec,
  • rings - ilosc walców, z których składa sie torus.
Rysunek 3. Program Przekształcenia - torus
Rysunek 3. Program Przekształcenia - torus

Dwunastoscian

Dwunastoscian o srodku połozonym w poczatku układu współrzędnych rysuje funkcja:
C/C++
void glutWireDodecahedron( void )

Czajnik

Klasycznym obiektem 3D dostepnym w bibliotece GLUT jest opracowany w 1975 roku przez Martina Newella czajnik - przedstawiony na rysunku 4. Czajnik o wielkosci regulowanej parametrem size rysuje funkcja:
C/C++
void glutWireTeapot( GLdouble size )
Rysunek 4. Program Przekształcenia - czajnik
Rysunek 4. Program Przekształcenia - czajnik

Ośmiościan

Osmioscian o srodku połozonym w poczatku układu współrzednych rysuje funkcja:
C/C++
void glutWireOctahedron( void )

Czworościan

Czworoscian o srodku połozonym w poczatku układu współrzednych rysuje funkcja:
C/C++
void glutWireTetrahedron( void )

Dwudziestościan

Dwudziestoscian o srodku połozonym w poczatku układu współrzędnych rysuje funkcja:
C/C++
void glutWireIcosahedron( void )

Obsługa myszki

Obsługa myszki składa sie z dwóch etapów i jest wykonywana przez dwie funkcje. Pierwsza z nich to:
C/C++
void MouseButton( int button, int state, int x, int y )
która wykrywa nacisniecie i zwolnienie lewego przycisku myszki. W zależności od stanu lewego przycisku myszki ustawiana jest wartosc zmiennej globalnej button state na przekazana przez parametr state. Druga funkcja:
C/C++
void MouseMotion( int x, int y )
wywoływana jest podczas ruchu kursora myszki a jej zadaniem jest odpowiednie (dobrane czesciowo doswiadczalnie) przeliczenie ruchu myszki na przesuniecie obiektu znajdujacego sie na scenie. Efekt przeliczen ruchu kursora myszki umieszczony jest w zmiennych globalnych translatex i translatey, które sa bezposredni przekazywane jako parametry funkcji glTranslatef. Parametry x i y obu funkcji oznaczaja współrzedne kursora myszki w odniesieniu do układu współrzednych okna, które oczywiscie nie maja nic wspólnego ze współrzednymi okreslonymi w scenie 3D. Parametr buton funkcji MouseButton okresla który przycisk myszki został nacisniety lub zwolniony. Parametr ten przyjmuje jedna z wartosci:
  • GLUT LEFT BUTTON - lewy przycisk myszki,
  • GLUT MIDDLE BUTTON - srodkowy przycisk myszki,
  • GLUT RIGHT BUTTON - prawy przycisk myszki.
Jezeli w danym systemie myszka ma tylko dwa przyciski, wartosc GLUT MIDDLE BUTTON nie bedzie generowana. Natomiast w przypadku myszki z jednym przyciskiem funkcja generuje jedynie wartosc GLUT LEFT BUTTON. Ostatni nieopisany parametr funkcji MouseButton to state, który okresla czy przycisk myszki został nacisniety (stała GLUT UP) czy zwolniony (stała GLUT - DOWN).
Aby opisane funkcje obsługi myszki działały nalezy je dołaczyc do listy funkcji zwrotnych wywołujac odpowiednio:
C/C++
void glutMouseFunc( void( * func )( int button, int state, int x, int y ) )
void glutMotionFunc( void( * func )( int x, int y ) )

Menu wielopoziomowe

W programie został uzyty nowy element biblioteki GLUT ? wielopoziomowe menu podreczne. Czesc menu podrecznego, które bedzie znajdowało sie na drugim (lub wyzszym) poziomie tworzymy w taki sam sposób jak dotychczas tworzylismy główne menu, czyli przy uzyciu funkcji glutCreateMenu i glutAddMenuEntry. Tak utworzony fragment menu umieszczamy w menu głównym (nadrzednym) uzywajac funkcji:
C/C++
void glutAddSubMenu( const char * label, int submenu )
Parametr label okresla nazwe menu podrecznego, a submenu jego identyfikator zwrócony przez funkcje glutCreateMenu.

Odrysowanie okna

Aby zmiany sceny 3D zostały wyswietlone na ekranie potrzebne jest odrysowanie biezacego okna. Mozna to zrealizowac wywołujac funkcje:
C/C++
void glutPostRedisplay( void )

Plik przeksztalcenia.cpp

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG(małpeczka)enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>

// stałe do obsługi menu podręcznego

enum
{
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    WIRE_SPHERE, // kula
    WIRE_CONE, // stożek
    WIRE_CUBE, // sześcian
    WIRE_TORUS, // torus
    WIRE_DODECAHEDRON, // dwunastościan
    WIRE_TEAPOT, // czajnik
    WIRE_OCTAHEDRON, // ośmiościan
    WIRE_TETRAHEDRON, // czworościan
    WIRE_ICOSAHEDRON, // dwudziestościan
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// rodzaj obiektu

int object = WIRE_SPHERE;

// rozmiary bryły obcinania

const GLdouble left = - 10.0;
const GLdouble right = 10.0;
const GLdouble bottom = - 10.0;
const GLdouble top = 10.0;
const GLdouble near = 50.0;
const GLdouble far = 70.0;

// współczynnik skalowania

GLfloat scale = 1.0;

// kąty obrotu

GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;

// przesunięcie

GLfloat translatex = 0.0;
GLfloat translatey = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // przesunięcie układu współrzędnych obiektu do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // przesunięcie obiektu - ruch myszką
    glTranslatef( translatex, translatey, 0.0 );
   
    // skalowanie obiektu - klawisze "+" i "-"
    glScalef( scale, scale, scale );
   
    // obroty obiektu - klawisze kursora
    glRotatef( rotatex, 1.0, 0, 0 );
    glRotatef( rotatey, 0, 1.0, 0 );
   
    // kolor krawędzi obiektu
    glColor3f( 0.0, 0.0, 0.0 );
   
    // rysowanie obiektu
    switch( object )
    {
        // kula
    case WIRE_SPHERE:
        glutWireSphere( 1.0, 20, 10 );
        break;
       
        // stożek
    case WIRE_CONE:
        glutWireCone( 1.0, 2.0, 20, 10 );
        break;
       
        // sześcian
    case WIRE_CUBE:
        glutWireCube( 1.0 );
        break;
       
        // torus
    case WIRE_TORUS:
        glutWireTorus( 0.2, 1, 10, 20 );
        break;
       
        // dwunastościan
    case WIRE_DODECAHEDRON:
        glutWireDodecahedron();
        break;
       
        // czajnik
    case WIRE_TEAPOT:
        glutWireTeapot( 1.0 );
        break;
       
        // ośmiościan
    case WIRE_OCTAHEDRON:
        glutWireOctahedron();
        break;
       
        // czworościan
    case WIRE_TETRAHEDRON:
        glutWireTetrahedron();
        break;
       
        // dwudziestościan
    case WIRE_ICOSAHEDRON:
        glutWireIcosahedron();
        break;
    }
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    Display();
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    // klawisz +
    if( key == '+' )
         scale += 0.1;
    else
   
    // klawisz -
    if( key == '-' && scale > 0.1 )
         scale -= 0.1;
   
    // odrysowanie okna
    Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}

// obsługa klawiszy funkcyjnych i klawiszy kursora

void SpecialKeys( int key, int x, int y )
{
    switch( key )
    {
        // kursor w lewo
    case GLUT_KEY_LEFT:
        rotatey -= 1;
        break;
       
        // kursor w górę
    case GLUT_KEY_UP:
        rotatex -= 1;
        break;
       
        // kursor w prawo
    case GLUT_KEY_RIGHT:
        rotatey += 1;
        break;
       
        // kursor w dół
    case GLUT_KEY_DOWN:
        rotatex += 1;
        break;
    }
   
    // odrysowanie okna
    Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

void MouseMotion( int x, int y )
{
    if( button_state == GLUT_DOWN )
    {
        translatex += 1.1 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
        button_x = x;
        translatey += 1.1 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
        button_y = y;
        glutPostRedisplay();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // kula
    case WIRE_SPHERE:
        object = WIRE_SPHERE;
        Display();
        break;
       
        // cylinder
    case WIRE_CONE:
        object = WIRE_CONE;
        Display();
        break;
       
        // sześcian
    case WIRE_CUBE:
        object = WIRE_CUBE;
        Display();
        break;
       
        // torus
    case WIRE_TORUS:
        object = WIRE_TORUS;
        Display();
        break;
       
        // dwunastościan
    case WIRE_DODECAHEDRON:
        object = WIRE_DODECAHEDRON;
        Display();
        break;
       
        // czajnik
    case WIRE_TEAPOT:
        object = WIRE_TEAPOT;
        Display();
        break;
       
        // ośmiościan
    case WIRE_OCTAHEDRON:
        object = WIRE_OCTAHEDRON;
        Display();
        break;
       
        // czworościan
    case WIRE_TETRAHEDRON:
        object = WIRE_TETRAHEDRON;
        Display();
        break;
       
        // dwudziestościan
    case WIRE_ICOSAHEDRON:
        object = WIRE_ICOSAHEDRON;
        Display();
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 400, 400 );
   
    // utworzenie głównego okna programu
    #ifdef WIN32
   
    glutCreateWindow( "Przekształcenia" );
    #else
   
    glutCreateWindow( "Przeksztalcenia" );
    #endif
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // dołączenie funkcji obsługi klawiszy funkcyjnych i klawiszy kursora
    glutSpecialFunc( SpecialKeys );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie podmenu - aspekt obrazu
    int MenuAspect = glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
    #else
   
    glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
    #endif
   
    glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
   
    // utworzenie podmenu - obiekt
    int MenuObject = glutCreateMenu( Menu );
    glutAddMenuEntry( "Kula", WIRE_SPHERE );
    #ifdef WIN32
   
    glutAddMenuEntry( "Stożek", WIRE_CONE );
    glutAddMenuEntry( "Sześcian", WIRE_CUBE );
    glutAddMenuEntry( "Torus", WIRE_TORUS );
    glutAddMenuEntry( "Dwunastościan", WIRE_DODECAHEDRON );
    glutAddMenuEntry( "Czajnik", WIRE_TEAPOT );
    glutAddMenuEntry( "Ośmiościan", WIRE_OCTAHEDRON );
    glutAddMenuEntry( "Czworościan", WIRE_TETRAHEDRON );
    glutAddMenuEntry( "Dwudziestościan", WIRE_ICOSAHEDRON );
    #else
   
    glutAddMenuEntry( "Stozek", WIRE_CONE );
    glutAddMenuEntry( "Szescian", WIRE_CUBE );
    glutAddMenuEntry( "Torus", WIRE_TORUS );
    glutAddMenuEntry( "Dwunastoscian", WIRE_DODECAHEDRON );
    glutAddMenuEntry( "Czajnik", WIRE_TEAPOT );
    glutAddMenuEntry( "Osmioscian", WIRE_OCTAHEDRON );
    glutAddMenuEntry( "Czworoscian", WIRE_TETRAHEDRON );
    glutAddMenuEntry( "Dwudziestoscian", WIRE_ICOSAHEDRON );
    #endif
   
    // menu główne
    glutCreateMenu( Menu );
    glutAddSubMenu( "Aspekt obrazu", MenuAspect );
    glutAddSubMenu( "Obiekt", MenuObject );
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}

Stos macierzy modelowania

Do składania przekształcen biblioteka OpenGL wykorzystuje wspomniany juz wczesniej stos macierzy modelowania. W dotychczasowych programach, oraz pierwszym z przykładowych programów z tego odcinka kursu, mechanizm stosu nie był faktycznie wykorzystywany. Wszystkie operacje zwiazane ze stosem macierzy modelowania wykonywane były na macierzy połozonej na szczycie stosu. Pozwala to jednak na efektywna manipulacje jedynie jednym obiektem na scenie. Oczywiscie obsługa wielu obiektów także jest mozliwa, ale wymaga to stosowania mniej wygodnych i efektywnych mechanizmów. W szczególnosci mogło by sie okazac konieczne przechowywanie macierzy modelowania - czyli dublowanie mozliwosci oferowanej przez mechanizm stosu.
Po lekturze powyzszego wstepu Czytelnik zastanawia sie zapewne jak w praktyce efektywnie mozna wykorzystac mechanizm stosu macierzy modelowania. Klasycznym przykładem jest budowa obiektów złozonych z wielu obiektów podstawowych (np. takich jak wyzej opisane obiekty 3D dostępne w bibliotece GLUT). Drugim, równie czesto wykorzystywanym przykładem, jest animacja sceny składajacej sie z wielu obiektów 3D.

Operacje na stosie

Specyfikacja biblioteki OpenGL przewiduje, ze stos macierzy modelowania przechowuje co najmniej 32 macierze. Oczywiscie konkretne implementacje OpenGL moga dowolnie zwiekszac maksymalna pojemnosc tego stosu. Opisane ponizej funkcje słuza do operacji na kazdym rodzaju stosu dostepnym w OpenGL. Operacja zawsze zostanie wykonana na aktualnie wybranym rodzaju stosu. Operacje odłozenia biezacej macierzy na stos realizuje funkcja:
C/C++
void glPushMatrix( void )
Natomiast operacje zdjecia macierzy ze stosu wykonuje funkcja:
C/C++
void glPopMatrix( void )
Próba wykonania niedozwolonej operacji na stosie spowoduje wygenerowanie kodu błedu zwracanego przez funkcje glGetError:
  • GL_STACK_UNDERFLOW - próba pobrania elementu z pustego stosu (niedobór stosu),
  • GL_STACK_OVERFLOW - przepełnienie stosu.
Przykładowy program (plik stos modelowania.cpp) opiera sie na poprzednim przykładzie. Podstawowa róznica jest rysowanie jednego obiektu - piramidy, ale złozonego z duzej ilosci elementów podstawowych - szescianów. Dla ułatwienia pracy program zawiera dwie funkcje rysujace czesci piramidy w postaci bloków 3 × 3 (funkcja Cube3x3) i bloków 2 × 2 (funkcja Cube2x2). Pierwszy poziom piramidy ma rozmiary 6 × 6 i jest rysowany z czterech bloków 3 × 3 (funkcja Pyramid). Przed narysowaniem kazdego z elementów pierwszego poziomu macierz modelowania odkładana jest na stos ? po zakonczeniu rysowania nastepuje zdjecie macierzy ze stosu. Przy rysowaniu drugiego i trzeciego poziomu piramidy nastepuje dwukrotne odłozenie macierzy modelowania na stos. Po pierwszym przesuwamy układ współrzednych od odpowiednia ilosc jednostek do góry. Drugie w kolejności odkładanie macierzy na stos wiaze sie juz bezposrednio z rysowaniem kolejnych elementów danego poziomu piramidy. Rysowanie kolejnych poziomów nie wymaga juz dodatkowego opisu ? wystarcza jednokrotne odłozenie macierzy modelowania na stos. Przykładowy efekt tych wszystkich przekształcen zawiera rysunek 5. Oczywiscie uzyskanie takiej figury nie wymaga stosowania mechanizmu stosu macierzy modelowania. Jednak juz w tak prostym przykładzie widoczne sa korzysci wynikajace z takiego rozwiazania. Po pierwsze wszystkie elementy obiektu 3D, w tym wypadku poziomy piramidy, sa od siebie niezalezne. Czytelnik moze to łatwo sprawdzic odpowiednio modyfikujac tekst zródłowy programu. Trudno nie docenic takiej cechy przy tworzeniu bardziej skomplikowanych obiektów. Druga zaleta jest duza przejrzystosc rozwiazania, co ułatwia pózniejsza modyfikacje programu i usuwanie ewentualnych błedów.Warto takze dodac, ze mechanizm stosu jest bardzo szybki i dotyczy to wszystkich implementacji biblioteki OpenGL, nie tylko sprzetowych.
W programie wykorzystano takze kilka innych nieopisanych wczesniej funkcji biblioteki OpenGL i GLU. W poprzednim przykładzie przy przesuwaniu obiektu za pomoca myszki obliczanie wektora przesuniecia oparto o dobrane doswiadczalnie współczynniki. W przypadku, gdy zmienimy parametry bryły odcinania te współczynniki trzeba bedzie dobierac ponownie. Biblioteka GLU zawiera funkcje przeliczajace współrzedne w przestrzeni okna na współrzedne w przestrzeni widoku i odwrotnie.
Przeliczenie współrzednych w przestrzeni widoku (objx, objy, objz) na współrzedne w przestrzeni okna (winx,winy,winz) wykonuje funkcja gluProject:
C/C++
GLint gluProject( GLdouble objx, GLdouble objy, GLdouble objz,
const GLdouble modelMatrix[ 16 ], const GLdouble projMatrix[ 16 ], const GLint viewport[ 4 ],
GLdouble * winx, GLdouble * winy, GLdouble * winz )
Do przeliczania współrzednych funkcja gluProject wykorzystuje macierz modelowania, macierz rzutowania oraz współrzedne okna renderingu, które zawarte sa kolejno w parametrach: modelMatrix, projMatrix i viewport
Przeliczenie współrzednych w przestrzeni okna (winx,winy,winz) na współrzedne w przestrzeni widoku (objx, objy, objz) wykonuje funkcja gluUnProject:
C/C++
GLint gluUnProject( GLdouble winx, GLdouble winy, GLdouble winz,
const GLdouble modelMatrix[ 16 ], const GLdouble projMatrix[ 16 ], const GLint viewport[ 4 ],
GLdouble * objx, GLdouble * objy, GLdouble * objz )
której parametry maja analogiczne znaczenie jak parametry poprzedniej funkcji gluProject.
W wersji 1.3 biblioteki GLU dodano funkcje gluUnProject4:
C/C++
GLint gluUnProject4( GLdouble winx, GLdouble winy, GLdouble winz, GLdouble clipw,
const GLdouble modelMatrix[ 16 ], const GLdouble projMatrix[ 16 ], const GLint viewport[ 4 ],
GLdouble near, GLdouble far, GLdouble * objx,
GLdouble * objy, GLdouble * objz, GLdouble * objw )
stanowiaca rozszerzenie gluUnProject umozliwiajaca obliczenia przy niestandardowych ustawieniach bufora głebokosci (parametry near i far odpowiadające parametrom funkcji glDepthRange) lub w przypadku, gdy czwarta współrzedna (parametr clipw) przestrzeni okna przyjmuje inna wartość niz 1. Funkcja zwraca dodatkowo wartosc czwartej współrzednej w przestrzeni widoku (parametr objw). Znaczenie tych dodatkowych współrzędnych poznamy w kolejnych odcinkach kursu. Wartosci macierzy modelowania, macierzy rzutowania oraz obszaru renderingu stanowia zmienne stanu maszyny stanu OpenGL. Do odczytu zmiennych stanu słuzy bardzo liczna grupa funkcji glGet, z których najbardziej uniwersalne sa nastepujace funkcje:
C/C++
void glGetBooleanv( GLenum pname, GLboolean * params )
void glGetDoublev( GLenum pname, GLdouble * params )
void glGetFloatv( GLenum pname, GLfloat * params )
void glGetIntegerv( GLenum pname, GLint * params )
Parametr pname okresla która wartosc maszyny stanów OpenGL chcemy pobrac (tabela wszystkich mozliwych wartosci zajmuje 35 stron specyfikacji OpenGL 2.1), a params wskaznik na zwracana wartosc. W zależności od rodzaju pobieranej wartosci params wskazuje na pojedyncza zmienna lub tablice. Rodzaj zwracanej (zwracanych) wartosci jednoznacznie okresla koncowa czesc nazwy funkcji.

Rysunek 5. Program Stos modelowania
Rysunek 5. Program Stos modelowania

Plik stos modelowania.cpp

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG(małpeczka)enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>

// stałe do obsługi menu podręcznego

enum
{
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// rozmiary bryły obcinania

const GLdouble left = - 10.0;
const GLdouble right = 10.0;
const GLdouble bottom = - 10.0;
const GLdouble top = 10.0;
const GLdouble near = 50.0;
const GLdouble far = 70.0;

// współczynnik skalowania

GLfloat scale = 1.0;

// kąty obrotu

GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;

// przesunięcie

GLfloat translatex = 0.0;
GLfloat translatey = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// funkcja rysująca blok 3x3

void Cube3x3()
{
    glutWireCube( 1.0 );
    glTranslatef( 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 0.0, - 1.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( - 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( - 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 0.0, 1.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 0.0, 1.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
}

// funkcja rysująca blok 2x2

void Cube2x2()
{
    glutWireCube( 1.0 );
    glTranslatef( 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( 0.0, - 1.0, 0.0 );
    glutWireCube( 1.0 );
    glTranslatef( - 1.0, 0.0, 0.0 );
    glutWireCube( 1.0 );
}

// funkcja rysująca piramidę z sześcianów

void Pyramid()
{
    // podstawa 6x6
    glPushMatrix();
    glTranslatef( 1.5, 1.5, 0.0 );
    Cube3x3();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( 1.5, - 1.5, 0.0 );
    Cube3x3();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 1.5, - 1.5, 0.0 );
    Cube3x3();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 1.5, 1.5, 0.0 );
    Cube3x3();
    glPopMatrix();
   
    // drugi poziom 5x5
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 1.0 );
    glPushMatrix();
    glTranslatef( 1.0, 1.0, 0.0 );
    Cube3x3();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( 1.0, - 1.0, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 2.0, 2.0, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 1.0, - 1.0, 0.0 );
    Cube3x3();
    glPopMatrix();
    glPopMatrix();
   
    // trzeci poziom 4x4
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 2.0 );
    glPushMatrix();
    glTranslatef( 0.5, - 0.5, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( 0.5, 1.5, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 1.5, 1.5, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPushMatrix();
    glTranslatef( - 1.5, - 0.5, 0.0 );
    Cube2x2();
    glPopMatrix();
    glPopMatrix();
   
    // czwarty poziom 3x3
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 3.0 );
    Cube3x3();
    glPopMatrix();
   
    // piąty poziom 2x2
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 4.0 );
    glTranslatef( - 0.5, 0.5, 0.0 );
    Cube2x2();
    glPopMatrix();
   
    // szósty poziom 1x1
    glPushMatrix();
    glTranslatef( 0.0, 0.0, 5.0 );
    glutWireCube( 1.0 );
    glPopMatrix();
}

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // przesunięcie układu współrzędnych obiektu do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // przesunięcie obiektu - ruch myszką
    glTranslatef( translatex, translatey, 0.0 );
   
    // skalowanie obiektu - klawisze "+" i "-"
    glScalef( scale, scale, scale );
   
    // obroty obiektu - klawisze kursora
    glRotatef( rotatex, 1.0, 0, 0 );
    glRotatef( rotatey, 0, 1.0, 0 );
   
    // kolor krawędzi obiektu
    glColor3f( 0.0, 0.0, 0.0 );
   
    // rysowanie piramidy
    Pyramid();
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    Display();
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    // klawisz +
    if( key == '+' )
         scale += 0.1;
    else
   
    // klawisz -
    if( key == '-' && scale > 0.1 )
         scale -= 0.1;
   
    // odrysowanie okna
    Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}

// obsługa klawiszy funkcyjnych i klawiszy kursora

void SpecialKeys( int key, int x, int y )
{
    switch( key )
    {
        // kursor w lewo
    case GLUT_KEY_LEFT:
        rotatey -= 1;
        break;
       
        // kursor w górę
    case GLUT_KEY_UP:
        rotatex -= 1;
        break;
       
        // kursor w prawo
    case GLUT_KEY_RIGHT:
        rotatey += 1;
        break;
       
        // kursor w dół
    case GLUT_KEY_DOWN:
        rotatex += 1;
        break;
    }
   
    // odrysowanie okna
    Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

void MouseMotion( int x, int y )
{
    if( button_state == GLUT_DOWN )
    {
        // pobranie macierz modelowania
        GLdouble model[ 16 ];
        glGetDoublev( GL_MODELVIEW_MATRIX, model );
       
        // pobranie macierzy rzutowania
        GLdouble proj[ 16 ];
        glGetDoublev( GL_PROJECTION_MATRIX, proj );
       
        // pobranie obszaru renderingu
        GLint view[ 4 ];
        glGetIntegerv( GL_VIEWPORT, view );
       
        // tablice ze odczytanymi współrzędnymi w przestrzeni widoku
        GLdouble prev[ 3 ], curr[ 3 ];
       
        // pobranie współrzędnych w przestrzeni widoku
        // dla poprzedniego położenia kursora myszki
        gluUnProject( button_x, button_y, 0.0, model, proj, view, prev + 0, prev + 1, prev + 2 );
       
        // pobranie współrzędnych w przestrzeni widoku
        // dla bieżącego położenia kursora myszki
        gluUnProject( x, y, 0.0, model, proj, view, curr + 0, curr + 1, curr + 2 );
       
        // obliczenie współrzędnych wektora przesunięcia obiektu
        translatex += curr[ 0 ] - prev[ 0 ];
        translatey += prev[ 1 ] - curr[ 1 ];
       
        // zapamiętanie położenia kursora myszki
        button_x = x;
        button_y = y;
       
        // odrysowanie okna
        glutPostRedisplay();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 400, 400 );
   
    // utworzenie głównego okna programu
    glutCreateWindow( "Stos modelowania" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // dołączenie funkcji obsługi klawiszy funkcyjnych i klawiszy kursora
    glutSpecialFunc( SpecialKeys );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie podmenu - aspekt obrazu
    int MenuAspect = glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
    #else
   
    glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
    #endif
   
    glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
   
    // menu główne
    glutCreateMenu( Menu );
    glutAddSubMenu( "Aspekt obrazu", MenuAspect );
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}
Poprzedni dokument Następny dokument
Definiowanie sceny 3D Okrawanie i obcinanie