WOJSKOWA AKADEMIA TECHNICZNA
Laboratorium NR 3 z przedmiotu:
Grafika komputerowa
Autor:
Michał Popławski
I9X6S1
Prowadzący:
mgr inż. Wojciech Sulej
Treść wykonywanych zadań.
Napisać program przedstawiający obiekt zbudowany z prymitywów przestrzennych udostępnianych przez biblioteki GLU i GLUT. Użytkownik za pomocą klawiatury mieć możliwość wprowadzania zmian następujących parametrów:
Kąta obrotu lampy,
Kąta podniesienia ramienia.
W programie uwzględnić możliwość interakcyjnej zmiany położenia obserwatora poprzez podanie następujących parametrów:
Odległości obserwatora od obiektu,
Wysokości obserwatora względem płaszczyzny, na której położony jest obiekt,
Kąta obrotu wokół obiektu w zakresie [0;360] z krokiem 1.
Schemat obiektu do zbudowania:
Wstęp teoretyczny.
Do wykonania zadania laboratoryjnego należało użyć odpowiednich funkcji związanych z modelowaniem przestrzeni zawartych w bibliotekach OpenGL oraz GLUT.
Przydatne funkcje:
Działania na stosie macierzowym
glPushMatrix(); - funkcja odkładająca aktualną macierz przekształceń na wierzchołek stosu,
glPopMatrix(); - funkcja zdejmująca macierz ze stosu,
Przekształcenia modelujące
glTranslatef(x,y,z); -funkcja przesuwająca układ współrzędnych o dany wektor
glRotatef(kat,x,y,z); - funkcja obracająca układ współrzędnych o dany kąt w kierunku przeciwnym do ruchu wskazówek zegara, ujemny kąt oznacza rotację zgodnie z ruchem wskazówek zegara, obrót wykonywany jest wokół osi równoległej do wektora (x,y,z)
glScalef(sx,sy,sz); - funkcja skaluje osie zgodnie z nadanymi współczynnikami skalowania
Funkcje rysujące bryły przestrzenne (GLUT)
glutWireCube(a); -funkcja rysująca sześcian, gdzie a jest długością boku
Funkcje rysujące kwadryki (GLU)
gluCylinder(*obj, R1, R2, H, a,b); - funkcja rysująca walec bez podstaw, gdzie *obj to wskaźnik do wcześniej utworzonej kwadryki, R1-promień dolnej podstawy, R2-promień górnej podstawy, H- wysokość, a-ilość podziałów promienistych, b – ilość podziałów koncentrycznych.
gluDisc(*obj, Rw, Rz, a, b); - funkcja rysująca dysk, gdzie *obj – wskaźnik do kwadryki, Rw – promień wewnętrzny, Rz – promień zewnętrzny, a – ilość podziałów koncentrycznych, b – ilość podziałów promienistych.
Analiza kodu źródłowego.
Pracę należało rozpocząć od stworzenia zadanego obiektu za pomocą funkcji modelujących oraz rysujących bryły oraz kwadryki. By móc wykorzystywać kwadryki inicjujemy je w specjalnej funkcji inicjującej znajdującej się przed rozpoczęciem rysowania właściwego obiektu:
// Zainicjowanie scian bocznych walca
podstawaSciany = gluNewQuadric();
gluQuadricDrawStyle(podstawaSciany, GLU_LINE);
// Zainicjowanie podstawy walca
podstawaDyskG = gluNewQuadric();
gluQuadricDrawStyle(podstawaDyskG, GLU_LINE);
Budowanie układu współrzędnych:
glBegin(GL_LINES);
// Os X
glColor3f(1.0, 0.0, 0.0);
glVertex3f(-20.0, 0.0, 0.0);
glVertex3f(20.0, 0.0, 0.0);
// Os Y
glColor3f(0.0,1.0,0.0);
glVertex3f(0.0, -20.0, 0.0);
glVertex3f(0.0, 20.0, 0.0);
// Os Z
glColor3f(0.0,0.0,1.0);
glVertex3f(0.0, 0.0, -20.0);
glVertex3f(0.0, 0.0, 20.0);
// Koniec tworzenia ukladu wspolrzednych
glEnd();
Teraz możemy przejść do właściwego tworzenia obiektu. Zaczynamy od odłożenia na stos aktualnej macierzy modelowania. Dzięki temu będziemy mogli potem po różnych operacjach obrotu, translacji lub skalowania powrócić do poprzedniego stanu sprzed tych zmian sposobu tworzenia obiektu. Piszemy więc:
// Przygotowanie stosu macierzy modelowania
glPushMatrix();
Podstawę lampki tworzymy za pomocą walca. Walec jest tworzony wzdłuż osi Z od z=0, tak że jego podstawa znajduje się w płaszczyźnie OXY. Jednak zgodnie z układem do jakiego jesteśmy przyzwyczajeni chcemy, aby lampka była tworzona w górę wyświetlanego podglądu, a nie w bok. W związku z tym musimy obrócić osie układu współrzędnych. Aby obrót był wykonany poprawnie powinniśmy dokonać go wokół osi OX o -90°.
// - walec podstawy
glRotatef(-90.0, 1, 0, 0);
gluCylinder(podstawaSciany, 2.5, 2.5, 0.5, 20, 4);
// - podstawa dolna walca
gluDisk(podstawaDyskG, 0.0, 2.5, 20, 4);
// - podstawa górna walca
glTranslatef(0.0, 0.0, 0.5);
gluDisk(podstawaDyskG, 0.0, 2.5, 20, 4);
Widać zatem, że po obrocie przystępujemy do rysowania walca. Zgodnie z rysunkiem powinien mieć on średnicę 5, więc promienie podstaw będą równe 2,5. Wysokość jest równa 0.5, natomiast podziały pionowe i poziome wynoszą 20 i 4 (mogły być dowolnie dobrane). Następnie rysujemy dolną podstawę walca za pomocą dysku o promieniu wewnętrznym równym 0 i zewnętrznym równym promieniowi walca, czyli 2,5. Podziały pozostają takie jak przy walcu. Aby narysować podstawę górną musimy przenieść układ współrzędnych o 0,5 do góry. W związku z tym używany funkcji glTranslatef(0.0, 0.0, 0.5), z parametrem 0,5 podwyższającym oś Z do góry ze względu na to, że aktualnie taką mamy orientację osi po wcześniejszym obrocie. Teraz możemy już bez przeszkód utworzyć górną podstawę walca.
Gotowa podstawa:
Widok pod kątem: Widok z góry:
Teraz będziemy rysować pionową nóżkę lampy. W związku z tym najwygodniej powrócić do tradycyjnego układu współrzędnych, gdzie oś Y skierowana jest do góry. Zdejmujemy zatem ze stosu macierz modelowania, w której był zapisany pierwotny stan. Aby ułatwić sobie obliczenia oraz aby mieć później możliwość dodania obrotu lampki bez podstawy podwyższamy oś Y o 0,5 do góry za pomocą funkcji glTranslatef.
glPopMatrix();
glTranslatef(0.0, 0.5, 0.0);
glRotatef(kat, 0, 1, 0);
glPushMatrix();
glScalef(0.5, 5.0, 0.5);
glTranslatef(0.0, 2.5/5, 0.0);
glutWireCube(1);
Następnie określamy kąt o jaki nastąpi obrót. Górna część lampki będzie się obracała w płaszczyźnie OXZ, zatem obrotu dokonujemy wokół osi OY. Aby mieć możliwość późniejszego obracania lampką za pomocą klawiatury nie wpisujemy stałego kąta, a jedynie zmienną, która będzie mogła być zmieniana. Zmienne służące jako parametry w obrotach są zdefiniowane globalnie na początku programu:
GLfloat katObr = 0.0;
GLfloat kat = 0.0;
Na początku przyjmują wartości 0, abyśmy mogli zobaczyć lampkę w stanie podstawowym. Gdy już określimy obrót musimy odłożyć przygotowaną koncepcję rysowania obiektu na stos. Dzięki temu będzie się obracała cała górna część lampki a nie jedynie następny tworzony element. Do narysowania prostopadłościanu, takiego aby odpowiadał temu z rysunku przedstawiającego lampkę musimy zeskalować osie, a następnie przesunąć odpowiednio oś Y, gdyż narysowany prostopadłościan będzie miał środek w punkcie określonej przez aktualną macierz modelowania jako (0,0,0). Środek znajduje się na wysokości 2,5, ale ze względu na to, że osie zostały wcześniej zeskalowane musimy tą wartość podzielić na 5, gdyż w aktualnej koncepcji 5 dawnych jednostek odpowiada 1 teraźniejszej. Możemy teraz narysować sześcian o boku 1, który po zeskalowaniu przez program będzie miał proporcje tego z rysunku podglądowego.
Podstawa z dorysowaną nóżką pionową:
Teraz rysować będziemy ramię poziome. Ponieważ będzie ono ruchome, znów musimy odpowiednio przygotować macierz modelowania.
glPopMatrix();
glTranslatef(0.0, 4.5, 0.0);
glRotatef(katObr, 0, 0, 1);
glPushMatrix();
glTranslatef(-1, 0.0, -0.375);
glScalef(5.0, 0.5, 0.25);
glutWireCube(1);
Zdejmujemy ze stosu ostatnio odłożoną macierz modelowania, aby pozbyć się skalowania osi i pozostałych niepotrzebnych już przekształceń. Podwyższamy oś Y o 4,5, gdyż na tej wysokości będzie znajdował się środek poziomego ramienia. Następnie określamy kąt obrotu poziomego ramienia (będzie on się obracał wzdłuż osi znajdującej się na wysokości 4,5). Ponieważ chcemy aby ramię poruszało się do góry i do dołu, to za oś obrotu przyjmujemy oś Z. Tak przygotowaną macierz modelowania odkładamy na stos, aby wszystkie elementy rysowane później poruszały się razem i zgodnie z określonym kątem. Następnie przesuwamy środek układów współrzędnych o -1, gdyż chcemy aby figura ‘wystawała’ bardziej w kierunku strony, z której będzie później rysowany klosz oraz o -0.375 (połowa ‘grubości’ ramienia pionowego i poziomego w widoku z góry) na osi Z, bo ramię poziome ma być przystające do pionowego i nie przecinać go. Dzięki temu przesunięciu i późniejszym zeskalowaniu odpowiednio osi (5-krotnie zwiększamy oś X, pozostałe osie zmniejszamy) otrzymujemy prostopadłościan podzielony w stosunku 3,5:1,5.
Gotowa kolejna część lampki:
Przechodzimy teraz do rysowania klosza lampki.
//klosz
glPopMatrix();
glTranslatef(-3.75, -0.25, -0.375);
glRotatef(-90.0, 1, 0, 0);
gluCylinder(przegubDyskD, 0.25, 0.25, 1.0, 20, 4);
gluDisk(podstawaDyskG, 0.0, 0.25, 20, 4);
glTranslatef(0.0, 0.0, 1.0);
gluDisk(podstawaDyskG, 0.0, 0.25, 20, 4);
glTranslatef(0.0, 0.0, -2.0);
gluCylinder(przegubDyskD, 1.0, 0.5, 1.0, 20, 4);
glTranslatef(0.0, 0.0, 1.0);
gluDisk(podstawaDyskG, 0.0, 0.5, 20, 4);
Zdejmujemy ze stosu poprzednią macierz modelowania. Rozpoczynamy zatem znowu z punktu znajdującego się na wysokości 4,5 i umożliwiającego obroty lampki. Aby narysować odpowiednio górną część klosza musimy dokonać translacji o -3,75 na osi X (na -3,5 kończy się poziome ramię lampki i musimy odjąć od tego jeszcze długość promienia walca, który będziemy tworzyć), -0,25 na osi Y (obniżamy środek walca tak, aby znajdował się na równym poziomie z poziomym ramieniem) oraz -0,375 na osi Z (gdyż ramię poziome jest przesunięte o tyle wzdłuż osi Z i dokonujemy przesunięcia, aby wyrównać te dwa obiekty i żeby nie były względem siebie przesunięte). Następnie dokonujemy obrotu takiego jak przy rysowaniu podstawy lampki gdyż będziemy rysować walec w takiej samej płaszczyźnie. Teraz możemy już narysować odpowiedni walec o wymiarach takich jak na rysunku oraz jego podstawę dolną (promień pokrywa się z promieniem walca). Żeby narysować górną podstawę walca dokonujemy translacji o 1 do góry, czy wzdłuż osi Z, gdyż taką wysokość ma walec. Do narysowania dolnej części klosza wykorzystujemy także walec, jednak o różnych podstawach dolnej i górnej. Teraz musimy przemieścić się na poziom dolnej podstawy dolnego klosza i stąd rozpocząć rysowanie. Dokonujemy translacji o -2 na osi Z i rysujemy walec o dolnej podstawie długości 1 i górnej – 0,5. Pozostała teraz jedynie górna podstawa dolnego walca. Walec ma wysokość 1, więc przesuwamy oś Z o 1 w górę i rysujemy ostatni element lampki.
Gotowa lampka (różne kąty wychylenia):
Aby możliwa była zmiana odległości obserwatora od obiektu dodana została zmienna
GLfloat odleglosc = 30.0;
Jest ona używana w funkcji wyświetlania obrazu w następujący sposób:
glTranslatef(0, 0, -odleglosc);
Dzięki nadaniu tu zmiennej zamiast stałej wartości będziemy mogli odległość tę zmieniać w funkcji obsługi klawiatury, która opisana będzie niżej.
Wysokość nad obiektem i obrót wokół obiektu zmieniane są w następującej funkcji:
void ObslugaKlawiszySpecjalnych(int klawisz, int x, int y)
{
switch(klawisz)
{
case GLUT_KEY_UP:
rotObsX = (rotObsX < 360.0) ? rotObsX + 1.0 : rotObsX;
break;
case GLUT_KEY_DOWN:
rotObsX = (rotObsX > 0.0) ? rotObsX - 1.0 : rotObsX;
break;
case GLUT_KEY_LEFT:
rotObsY = (rotObsY < 360.0) ? rotObsY + 1.0 : rotObsY;
break;
case GLUT_KEY_RIGHT:
rotObsY = (rotObsY > 0.0) ? rotObsY - 1.0 : rotObsY;
break;
}
}
Gdzie rotObsX jest to zmiana wysokości nad obiektem i umożliwia nam spojrzenie na obiekt z góry i z dołu, nie zmienia poza tym odległości obserwatora od obiektu. Zmiana ta następuje w zakresie [0;360]. Podobnie Jest ze zmienną rotObsY, która umożliwia obrót poziomy wokół obiektu.
Ruchy lampki i odległość obserwatora od obiektu zmieniane są w następującej funkcji:
void ObslugaKlawiatury(unsigned char klawisz, int x, int y)
{
switch(klawisz)
{
case 'q':
katObr=(katObr<360)? katObr+1.0 :0; // zmiana kąta nachylenia ramienia
break; //poziomego
case 'a':
katObr=(katObr>0)? katObr-1.0:360;
break;
case 'w':
kat=(kat<360)? kat+1.0 :0; //zmiana kąta obrotu lampki
break;
case 's':
kat=(kat>0)? kat-1.0:360;
break;
case 'e': //zmiana odległości od obiektu
odleglosc+=1.0;
break;
case 'd':
odleglosc-=1.0;
break;
}
if(klawisz == 27)
exit(0);
}
Wnioski:
Wszystkie zadania postawione przede mną zostały wykonane poprawnie. Na zajęciach laboratoryjnych nie miałem wykonanego polecenia „zmiana wysokości obserwatora względem płaszczyzny, na której położony jest obiekt”. Operacje odkładania macierzy na stos i zdejmowania z niego pozwalają nam na tworzenie praktycznie każdego obiektu jaki możemy sobie wymyśleć i można przedstawić w postaci podstawowych figur, których tworzenie umożliwia nam OpenGL i dodatkowe biblioteki. Dzięki zajęciom mogliśmy doświadczyć jak ważne w budowaniu tych obiektów są odpowiednie współrzędne umożliwiające idealne dopasowanie do siebie tworzonych obiektów. Ciekawym doświadczeniem było stworzenie obiektów umożliwiających poruszanie nimi z poziomu klawiatury.