Spis treści
Wprowadzenie
Kontekst przedsięwzięcia
Komputery odgrywają ważną rolę w życiu każdego człowieka. Poziom zaawansowania cywilizacyjnego doprowadził do stanu, w którym każdy obywatel, żyjący w kraju uprzemysłowionym, jest w pewien sposób zależny od komputera.
Nawet ludzie unikający tej dziedziny, próbujący oddzielić się od wszelkiej informatyzacji lub na pierwszy rzut oka niemający z nią nic wspólnego, są na komputery skazani. Informatyzacja jest coraz bardziej powszechna w administracji, medycynie, szkolnictwie, sztuce, motoryzacji, komunikacji, rozrywce. Dotyka praktycznie każdej dziedziny codziennego życia.
Wraz z rozwojem technologii komunikacja człowieka z komputerem przyjmuje bardziej typowy dla ludzi charakter. Dzisiejsza młodzież (i nie tylko) chętnie wykorzystuje Internet do zdobycia interesujących informacji, wypierane zostają tradycyjne nośniki muzyki, grafika komputerowa staje się bardziej realistyczna. Standardem staje się przenoszenie filmów adresowanych do młodzieży na ekrany komputerów i konsol do gier.
Jaki jest powód takiego działania? Otóż poziom realizmu gier komputerowych stał się wystarczający, aby gracz poczuł się jak bohater uczestniczący w akcji. W dużej mierze jest to zasługa prężnie rozwijającej się dziedziny informatyki jaką jest grafika komputerowa. Jednakże rozrywka to nie jedyne zastosowanie dla nowości w zakresie multimediów.
Did not finish!
Cel i zakres pracy
Celem naszej pracy jest stworzenie interaktywnej aplikacji graficznej, która ma służyć studentom pierwszego roku informatyki jako narzędzie pomocne w zapoznaniu się z Instytutem Informatyki. Ma to być narzędzie o przyjemnym interfejsie użytkownika umożliwiające przechadzkę po korytarzu, bibliotece, gabinetach pracowników, pomieszczeniach administracyjnych i salach laboratoryjnych a także zapoznanie się z informacjami o poszczególnych pomieszczeniach i pracownikach.
Zakres pracy:
Stworzenie silnika graficznego zdolnego do prawidłowego renderowania elementów graficznych, umożliwiającego prawidłowe nałożenie tekstur na obiekty, oświetlenia ich i wyświetlenia,
Zaprojektowanie korytarza i poszczególnych sal należących do Instytutu Informatyki i mieszczących się na IV piętrze w budynku Wydziału Elektrycznego Politechniki Poznańskiej,
Zaprojektowanie obiektów występujących na korytarzu i w salach instytutu takich jak:
krzesła i fotele,
biurka i stoły,
szafy,
lampy,
okna i drzwi,
sprzęt komputerowy,
gabloty, plakaty, zegary, itp.
Przygotowanie aplikacji wykorzystującej silnik graficzny, zdolnej do odtwarzania dźwięków i sekwencji wideo, która w intuicyjny sposób pozwoli użytkownikowi na eksplorację Instytutu Informatyki. Przewidziane są dwa tryby pracy:
Przechadzka, w której użytkownik może samodzielnie poruszać się po wirtualnym instytucie,
Wycieczka, gdzie program zaprezentuje Instytut według przygotowanego scenariusza
Na potrzeby projektu powstały następujące programy pomocnicze:
Edytor pokoi - program umożliwia rozmieszczanie obiektów w pomieszczeniach,
Edytor detali - aplikacja wspomagająca tworzenie szczegółowych obiektów,
Edytor poziomów - program służy do rozmieszczenia poszczególnych pomieszczeń (zdefiniowania ich współrzędnych) na danym poziomie,
Inspektor obiektów - program umożliwia uzyskanie informacji o rodzajach obiektów wykorzystywanych na danym poziomie,
TextEditor - program wspomaga definiowanie tekstów wycieczki.
Szerszy opis ww. aplikacji znajduje się w dalszej części pracy.
Zespół zarządzający i wykonawczy
promotor |
Dr inż. Maciej Zakrzewisz |
koreferent |
Dr inż. Marek Wojciechowski |
projekt / wykonanie |
Michał Kaftański Jakub Malicki Bartosz Rudnicki Marcin Tucholski |
Podziękowania
Podziękowania kierujemy przede wszystkim do dr inż. Macieja Zakrzewicza za odkrycie przed nami arkan grafiki komputerowej, za bardzo ciekawe przekazanie swojej wiedzy, za wzorowe prowadzenie naszej pracy inżynierskiej, za cierpliwość, dobry humor i uśmiech na twarzy oraz do dr inż. Marka Wojciechowskiego za jego bardzo cenne uwagi podczas prac związanych z powstawaniem projektu.
Osobne podziękowania kierujemy do mgr inż. Dawida Weissa za pracę włożoną w sprawne działanie repozytorium CVS, bez którego nasza praca byłaby znacznie utrudniona i powstawałaby znacznie dłużej.
Dziękujemy również wszystkim pracownikom Instytutu Informatyki, którzy niejednokrotnie tolerowali nasze wizyty, zaglądanie w zakamarki sal i fotografowanie ich stanowisk pracy.
Dziękujemy również naszym rodzicom i dziewczynom za wsparcie duchowe i wyrozumiałość.
Opis technologii
OpenGL - historia i rozwój
Pierwsze wzmianki o OpenGL pojawiły się już w 1992 roku na konferencji Win32 Developers Conference w San Francisco. Firma Silicon Graphics, Inc. niepodważalny do dzisiaj lider w grafice i animacji komputerowej, przy okazji prezentacji swoich nowych stacji graficznych, na których powstawały efekty specjalne do znanych filmów przedstawiła zupełnie nowy wtedy standard graficzny - OpenGL. Tego samego dnia Microsoft potwierdził wsparcie dla OpenGL na swoich Windows`ach. Podstawą do opracowania nowego standardu był własny język GL firmy Silicon Graphics - IRIS GL. Był on wówczas interfejsem API grafiki 3D dla profesjonalnych stacji roboczych IRIS. Nie były to w powszechnym rozumieniu normalne komputery osobiste, lecz superszybkie i drogie zarazem stacje graficzne zoptymalizowane od początku do końca tylko pod kątem generowania grafiki. Wykorzystywany sprzęt pozwalał wówczas na rekordowo szybkie obliczania różnych działań macierzowych, co jest podstawą grafiki trójwymiarowej. Nowy standard nie jest do końca „otwarty”, jest kontrolowany przez OpenGL Architecture Review Board w skład, którego wchodzą twórcy języka - Silicon Graphics oraz także Digital Equipment Corporation, IBM, Intel i Microsoft. Rada zbiera się najczęściej dwa razy w roku w celu zatwierdzenia nowych pomysłów czy zaktualizowania poprawek. Owe spotkania są publiczne i wiele czołowych firm komputerowych udziela głosu w rozmowach. W samym Internecie powstała grupa dyskusyjna comp.graphics.api.open.gl gdzie każdy może wysłać swoje uwagi i spostrzeżenia. Taka forma publikacji bardzo korzystnie wpływa na rozwój standardu OpenGL, bowiem staje się bardziej „otwarty”.
OpenGL nigdy nie był dostępny dla 16-bitowych wersji Windows (tyczy się 3.1 oraz 3.11 itp.). W Windows NT 3.1 była już zaimplementowana wersja alfa (a później beta), a wkrótce podczas wydania wersji beta Windows`a NT 3.5 można było pierwszy raz podziwiać efekty grafiki 3D w środowisku OpenGL na domowych komputerach (były to wówczas prymitywne wygaszacze ekranu). Ten pierwszy zestaw bibliotek obejmował wówczas wszystkie funkcje OpenGL 1.0. W Windows NT 4.0 zostały zaktualizowane do wersji 1.1. Obecna wersja wydana 29 lipca 2003 roku jest już piątą wersją od momentu wydania pierwszej i nosi numer 1.5. Poprawki odnoszą się do polepszania dotychczas stworzonych algorytmów, ale w większości obejmują one dodawanie nowych technologii w grafice 3D - w OpenGL 1.5 dla przykładu zostaje usprawniona obsługa PixelShader oraz VertexShader, która dla porównania w DirectX występowała już w wersji 8.0, a w najnowszej 9.0 zaimplementowano najnowsze rozkazy PixelShader oraz VertexShader 2.0.
Wielką zaletą OpenGL jest jego niezależność implementacji na praktycznie wszystkich możliwych platformach i systemach. Począwszy od Windows`ów (NT 3.5 i wzwyż), przez X Window gdzie jest obsługiwany przez praktycznie wszystkie dystrybucje Linux`a oraz na większości stacjach UNIX`a (SUN Solaris, IBM muliprocessor AIX, HP - UX, SGI IRIX, Compaq Tru64 Unix i inne). OpenGL zaimplementowany został również w Java (najnowsza wersja JavaOpenGL 1.0a3), w Fortranie (f90gl), w Perl`u (OpenGL Perl Module), w Pike, w Python`ie (PyOpenGL), w Ada. Do ciekawostek można zaliczyć mniej lub bardziej stabilne implementacje na konsoli do gier PlayStation2 czy do poczciwej Amigi (OpenGL for Amiga pod kierunkiem StormMesa oraz Amiga MiniGL)
OpenGL - zakres i zastosowania
OpenGL jest zoptymalizowany pod kątem wyświetlania i manipulowania grafiką trójwymiarową. Można powiedzieć, iż OpenGL jest językiem proceduralnym - programista opisuje kroki, które są wymagane, aby uzyskać żądany efekt. Ma do dyspozycji ponad 300 poleceń i funkcji za pomocą, których może stworzyć między innymi najprostsze figury i obiekty trójwymiarowe. Oczywiście dozwolona jest manipulacja wieloma różnymi niezbędnymi rzeczami w rzeczywistości trójwymiarowej takimi jak na przykład ustawianie kamery i oświetlenia czy mapowanie tekstur.
OpenGL od samego początku był przeznaczony do pracy na systemach z akceleracją sprzętową. Oczywiście jest możliwa i programowa akceleracja jednakże spadek wydajności jest ogromny. Wywołania OpenGL przekazywane są poprzez sterownik klienta OpenGL oraz interfejs producenta do interfejsu sterownika grafiki trójwymiarowej (3D DDI), który z kolei jest tak zaprojektowany, aby umożliwić akcelerację sprzętową. Renomowani producenci kart graficznych od samego początku obiecywali sprzętowe wsparcie i do dzisiaj prześcigają się w szybkości generowania obiektów. Dostawcy różnych chipsetów graficznych mogą instalować własne sterowniki klienta OpenGL oraz interfejsu producenta, aby w pełni wykorzystać swoje układy.
OpenGL wkroczył w wiele dziedzin komputerowych - od programów CAD`owskich i aplikacji architektonicznych przez najnowsze gry komputerowe kończąc na zaawansowanych środowiskach graficznych za pomocą, których powstają efekty specjalne do najnowszych filmów.
OpenGL jako interfejs API
Jak już zostało wspomniane OpenGL nie jest odrębnym językiem programowania można go najbardziej traktować jako interfejs programowania aplikacji - API (Application Programming Interface). Tak, więc pisząc program w OpenGL korzystamy z dowolnego języka programowania (np. C++ czy VisualBasic) i dopiero bezpośredniego z niego wywołujemy funkcje czy procedury biblioteki OpenGL. Standardowo zostały one podzielone na trzy osobne biblioteki.
Pierwsza z nich - opengl32.dll zawiera oficjalne funkcje zdefiniowane według zasad Architecture Review Board. Nazwy kolejnych funkcji zaczynają się od przedrostka gl.
Druga z nich - glaux.lib, czyli Auxiliary (zwana pomocniczą) zawiera w sobie funkcje, które nie są de-facto napisane według oficjalnej specyfikacji. Nazwy kolejnych funkcji zaczynają się od przedrostka aux. Stanowią jedynie pomoc dla początkujących programistów, można za ich pomocą w szybkim czasie uzyskać gotowy projekt. Najczęściej można z niej skorzystać przy obsłudze okien, myszy lub klawiatury czy rysowaniu najprostszych figur. Wspomnieć należy bowiem, iż sam OpenGL nie potrafi obsługiwać okien czy urządzeń zewnętrznych, zająć się tym musi sam system operacyjny - w przypadku pisania programu - język programowania, w którym tworzony jest projekt. Biblioteka AUX, mimo że stała się popularna i w związku z tym często zostaje zaimplementowana na innych platformach niż Windows, korzystanie jednak z niej sprawia, że program staje się mniej „przenośny”.
Z kolei trzecia z dołączanych bibliotek - glu32.dll czyli Utility Library (zwana potocznie narzędziowa) jest oparta o oficjalne funkcje OpenGL, co sprawia, że będzie poprawnie działać na wszystkich systemach. Nazwy kolejnych funkcji zaczynają się od przedrostka glu. Ma w sobie oszczędzające wiele czasu pomocnicze funkcje - miedzy innymi obsługę rysowania kwadryg.
Typy danych
W OpenGL istnieją typy danych podobne do tych spotykanych w C. I tak odpowiednio:
GLbyte |
8-bitowa liczba całkowita |
GLshort |
16-bitowa liczba całkowita |
GLint, GLsizei |
32-bitowa liczba całkowita |
GLfloat, GLclampf |
32-bitowa liczba zmiennoprzecinkowa |
GLdouble, GLclampd |
64-bitowa liczba zmiennoprzecinkowa |
GLubyte, GLboolean |
8-bitowa liczba całkowita bez znaku |
GLushort |
16-bitowa liczba całkowita bez znaku |
GLuint, Glenum, GLbitfield |
32-bitowa liczba całkowita bez znaku |
Wszystkie typy rozpoczynają się od przedrostka GL. Jak widać nazwy są adekwatne do tych, co są używane w języku C.
Prymitywy
Każdy obiekt, mniej lub bardziej złożony stworzony w OpenGL składa się z
wielu małych i prostych kształtów. Są one rozmaicie ułożone i połączone, razem dają efekt w postaci żądanego obiektu. Właśnie te „klocki” figur nazywane są inaczej prymitywami - jedno- lub dwu-wymiarowe obiekty od prostych linii po skomplikowane wielokąty.
Wierzchołek
Implementacja wierzchołka ma różne postacie, najczęściej używana to
glVertex3f(xf,yf,zf)
gdzie parametrami są jak można łatwo rozpoznać po przyrostku 3f - 3 współrzędne w postaci liczb typu float.
Pozostałe prymitywy
Szkielet kodu można przedstawić następująco:
glBegin (Glenum mode)
…
glVertex(xf,yf,zf);
…
//(w tej części definiuje się współrzędne wierzchołków, a także różne opcje np. ustawianie grubości linii czy określanie krawędzi wielokąta jako wewnętrzne czy zewnętrzne)
…
glEnd
GLenum mode określa się rodzaj prymitywu. Najczęściej korzystane to:
GL_POINTS |
Każdy wierzchołek staje się pojedynczym punktem |
GL_LINES |
Tworzy linie pomiędzy kolejnymi parami wierzchołków |
GL_LINE_STRIP, GL_LINE_LOOP |
Tworzy linię łamaną (wersja LOOP daje łamaną zamkniętą) |
GL_TRIANGLES |
Tworzy trójkąty pomiędzy kolejnymi 3-ma wierzchołkami |
GL_QUAD |
Tworzy czworokąty pomiędzy kolejnymi 4-ma wierzchołkami |
GL_POLYGON |
Tworzy wielokąt wypukły ze wszystkich podanych wierzchołków |
Bufor głębokości, ukrywanie niewidocznych powierzchni
Przy tworzeniu trójwymiarowych obiektów ważną kwestią staje się ustalenie tzw. stron tylnych (czyli niewidocznych) oraz przednich. Dzięki temu przy renderowaniu obiektu na ekran wyświetlana jest tylko widoczna część. Aby włączyć testowanie głębokości należy wywołać:
glEnable(GL_DEPTH_TEST)
W tym momencie przed renderowaniem tego samego piksela należącego do kolejnego obiektu porównywana jest wcześniejsza odległość tego piksela od obserwatora. Jeżeli jest odległość nowego piksela jest mniejsza to jest on na nowo renderowany.
Na duży wzrost wydajności ma wpływ mechanizm nie rysowania tylnych ścian prymitywów. Poprzez definiowanie punktów prymitywu w określonej kolejności (zgodnie lub przeciwnie do ruchu wskazówek zegara) samemu określamy strony wewnętrzne i zewnętrzne.
Mechanizm pomijania tylnych ścian jest aktywowany za pomocą funkcji:
glEnable(GL_CULL_FACE)
Do samego określenia przedniej strony wielokątu służy funkcja:
glFrontFace(GLenum mode)
gdzie kierunek jest wyznaczany zgodnie z ruchem wskazówek zegara (glFrontFace(GL_CW)) lub przeciwnie (glFrontFace(GL_CCW)).
Optymalizacja renderowania
Ze względu, iż najnowsze akceleratory graficzne są konstruowane najczęściej pod kątem szybkości renderowania trójkątów radzi się korzystanie tylko z nich, bowiem inne wymyślne kształty po prostu będą dłużej obliczane.
Przekształcenia
W OpenGL, aby uzyskać żądany widok sceny tak naprawdę musimy dokonać wielu przekształceń. Można je podzielić na przekształcenia:
Punktu Obserwatora |
Określa położenie kamery |
Modelu |
Określa położenie obiektów w rzeczywistości trójwymiarowej |
Rzutowania |
Określa rozmiary bryły widzenia |
Widoku okna |
Określa rozmiary końcowych wymiarów okna |
Przekształcenia punktu obserwatora
Implementacja:
gluLookAt(x,y,z)
Współrzędne punktu obserwatora można inaczej interpretować jako układ odniesienia w rzeczywistości. Domyślną wartością jest początek układu współrzędnych a kierunek patrzenie pokrywa się z osią Z w kierunku do „ekranu monitora”.
Przekształcenia modelu
Implementacja:
glScale
glTranslate
glRotate
Przekształcenia służą do ustawienia modelu i jego odrębnych obiektów. Można w ten sposób dany obiekt zarówno przesunąć (glTranslate), obrócić (glRotate) jak i zmienić rozmiar (glScale). Przy czym zmiana rozmiaru nie musi być wprost proporcjonalna w każdym kierunku - istnieje możliwość rozciągnięcia lub ściśnięcia obiektu. W przypadku wielu różnych przekształceń ważna jest kolejność wykonywanych przekształceń, bowiem efekt końcowy będzie inny - z powodów czysto matematycznych - nieprzemienności działań mnożenia macierz, o czym będzie dalej mowa.
Przekształcenia rzutowania
Implementacja:
gluOrtho2D(left,right,bottom,top) //rzut. równoległe
gluPerspective(fovy,aspect,zNear,zFar) //rzut. perspektywiczne
Stosuje się w końcowym etapie tworzenia sceny. Określa bryłę widzenia - ustawia płaszczyzny obcinania widoku.
Przekształcenia okna
Mając wykonany dwuwymiarowy rzut sceny pozostaje na koniec przekształcenie okna, w którym rzut jest wyświetlany.
Macierze, kolejka przekształceń
Wszystkie wyżej wymienione przekształcenia są obliczane w OpenGL w postaci macierzy (względnie wektorów). Zanim dany wierzchołek z oryginalnych współrzędnych trafi na ekran w wyznaczone miejsce czeka go wiele obliczeń. Matematyczną drogę przedstawia poniżej rysunek:
Rysunek 1.Kolejność przekształceń w OpenGL
Gdzie [x y z w] to wektor wierzchołka o współrzędnych (x,y,z) oraz skali w.
Macierz tożsamościowa, stosy macierzy
Należy pamiętać, iż każde przekształcenie jest kumulowane w macierzy widoku. Dla przykładu: po przesunięciu danego obiektu o pewne współrzędne, dla kolejnego obiektu, układem odniesienia będzie układ już po wcześniejszych przekształceniach. W celu uniknięcia tych niedogodności należy załadować macierz tożsamościową, która spowoduje powrót punktu odniesienia do początkowego układu obserwatora. Implementacja wygląda następująco:
glMatrixMode(GL_MODELVIEW); //odwołanie do macierzy widoku modelu
glLoadIdentity(); //załadowanie macierzy tożsamościowej do bieżącej macierzy
Jednak nie zawsze załadowanie macierzy tożsamościowej jest korzystne - w przypadku, gdy punkt obserwacji nie znajduje się na początku układu współrzędnych. W takich przypadkach można skorzystać z mechanizmu stosu macierzy. Umieszcza się wtedy aktualną macierz na stosie. Następnie dokonuje się niezbędnych przekształceń, a na samym końcu odtwarza się stan macierzy zdejmując ją ze stosu. Przykład:
glPushMatrix(); //Zapisanie dotychczasowych przekształceń
…
//Nowe przekształcenia
…
glPopMatrix(); //Odtworzenie początkowych przekształceń
Rzutowania
W ogólności mamy do czynienia z dwoma rodzajami rzutowań. W zależności od zastosowań - równoległe oraz perspektywistyczne.
Rzutowanie perspektywistyczne
Efekt jaki się uzyskuje przy tym rzutowaniu najbardziej oddaje rzeczywiste rozmiary i położenia. Obiekty wprost proporcjonalnie do odległości od punktu obserwatora zmniejszają swoje rozmiary oraz nabierają prawdziwych kształtów. Niżej podany rysunek przedstawia w jaki sposób jest renderowana scena :
Rysunek 2.Rzutowanie perspektywiczne
Zasięg widoku można porównać do bryły geometrycznej - ostrosłupa. Do określenia parametrów rzutowania używamy funkcji :
gluPerspective(GLdouble fov, GLdouble aspect, GLdouble zNear, GLdouble zFar);
gdzie: fov - kąt widzenia
aspect - stosunek wysokości bliższej płaszczyzny do jej szerokości
zNear, zFar - określają odległość bliższą i dalszą płaszczyznę obcinania
Rzutowanie równoległe
W tym przypadku zasięg widoku nabiera kształtu prostopadłościanu. W związku z tym nie jest brana pod uwagę odległość obiektów od obserwatora. Wszystkie figury zachowują swoje oryginalne kształty i rozmiary. Wykorzystują takie rzutowanie najczęściej programy CAD`owskie oraz wszelakiego rodzaju aplikacje architektoniczne.
Kolory
Podstawowe głębokości kolorów
W komputerach osobistych możemy spotkać się z różnymi głębokościami kolorów. Najczęściej używane to 8-bitowy (256 kolorów), 16-bitowy (high color) oraz 32-bitowy (true color). Jednakże paleta 256 kolorów praktycznie nie już jest wykorzystywana, ponieważ jest to zbyt mała liczba żeby móc sensownie przedstawić obraz czy animację.
Tryb 16-bitowy potrafi wyświetlić na ekranie 65 536 dostępnych kolorów i jak się okazuje w praktyce jest to wystarczająca ilość.
Z kolei tryb 32-bitowy tak naprawdę wykorzystuje 24-bity, bowiem tylko tyle można wyświetlić na ekranie - ponad 16 milionów kolorów. Jednak ze względu na optymalizację 32-bitowych procesorów - dostęp do adresów w pamięci jest szybszy po dołożeniu dodatkowych bitów.
W OpenGL mamy do dyspozycji dwa rodzaje trybów obsługiwania kolorów.
Tryb koloru RGBA
Można w nim precyzyjnie określić barwę koloru przez podanie nasycenia każdego ze składników (RGB - czerwony, zielony oraz niebieski) na wzór powszechnie wykorzystywanego sposobu w GDI Windows`a.
Czwartym, opcjonalnym składnikiem jest współczynnik przejrzystości alpha .
Implementacja :
glColorPR(red, green, blue, alpha)
Gdzie:
P - liczba parametrów - 3 lub 4 (w przypadku podawania współczynnika alpha)
R - rodzaj parametrów - integer, float , double i inne
Najczęściej wykorzystywana jest wersja glColor3f gdzie natężenie kolejnych składowych koloru podaje się w zakresie <0,1>.
Pomocna jest również odmiana glColor3ub, w której podanie nasycenie każdego składnika jest podobne jak w gamie Windows`a - od 0 do 255.
Tryb z indeksem koloru
W tym przypadku podaje się wskaźnik na wybraną barwę w palecie zdefiniowanych już kolorów. Najczęściej jest on wykorzystywany w przypadku 8-bitowej głębokości kolorów. Istnieje, bowiem większa „elastyczność” wyboru kolorów - można uzyskać takie, które za pomocą standardowego RGB 8-bitowego nie można by było osiągnąć. Poza tym jest trochę szybszy gdyż wymaga mniejszej ilości obliczeń ze względu na mniejszą ilość bitów.
Niestety nałożonych na niego jest wiele ograniczeń - nie można wykorzystywać pewnych efektów specjalnych w OpenGL na przykład antyaliazingu, alpha blendingu czy kilku efektów oświetlenia i cieniowania.
Światła i oświetlenia
Oświetlenie to jedna z najciekawszych i najefektywniejszych funkcji zaimplementowanych w OpenGL. W rzeczywistości obiekty i sceny nabierają dopiero uroku przy dobrze skonfigurowanych źródłach światłach i przeróżnych efektach wspieranych przez OpenGL.
Na każdy obiekt tak naprawdę przypadają trzy rodzaje świateł światło otaczające, światło rozproszone, światło odbłyskowe.
Światło otaczające - nie pochodzi z żadnego określonego kierunku - jest wszechobecne na scenie. Tak więc dany obiekt odbija promienie światła z każdej strony równomiernie.
Światło rozproszone już ma swój określony kierunek - oświetla tym mocniej powierzchnie im mniejszy kąt tworzy z kierunkiem padania światła (najmocniej gdy pada bezpośrednio pod kątem prostym) i jest odbijane od powierzchni równomiernie w każdym kierunku.
Na koniec pozostaje trzeci rodzaj światła - odbłyskowe - podobne do rozproszonego z taką różnicą, że jest odbijane w jedną określoną stronę.
Praktycznie każde źródło światła w OpenGL jest zdefiniowane za pomocą każdego z wyżej wymienionych składowych rodzajów światła. Podobnie jak kolor, światło jest określone przez wartości w skali RGBA.
Oświetlenie w OpenGL
W celu włączenia obliczeń oświetleń należy wywołać funkcję :
glEnable(GL_LIGHTING);
Model oświetlenia
Następnie należy określić model oświetlenia za pomocą funkcji :
glLightModel();
Posiada ona różne odmiany w zależności od określania rodzaju parametru, na przykład podstawowa używana do ustawienia domyślnego światła otaczającego scenę to :
glLightModelfv(GLenum pname, const GLfloat *p)
gdzie jako parametr :
pname - decyduje się na rodzaj parametru (w tym przypadku GL_LIGHT_MODEL_AMBIENT)
*p - wskaźnik do tablicy określający dany parametr (w tym przypadku element będący składowymi RGBA)
Właściwości materiału
Aby dany obiekt odbijał w odpowiedni sposób światło należy zdefiniować materiał.
Wykorzystuje się najczęściej do tego funkcję :
glMaterialfv(GLenum side, GLenum pname, const GLfloat *p)
gdzie :
side - podaje się stronę wielokąta do której odnoszą się właściwości (GL_FRONT, GL_BACK albo GL_FRONT_AND_BLACK)
pname - podaje się żądaną właściwość odbicia światła (na przykład GL_AMBIENT_AND_DIFFUSE czyli światła rozpraszającego oraz otaczającego)
*p - jest tablicą zawierająca wartości w standardzie RGBA
Istnieje alternatywny sposób określania materiału - bardziej praktyczny z - nazywa się „śledzeniem kolorów”. W tym przypadku ustala się właściwości materiału tak, aby były aktywne przy każdym wywołaniu fukcji glColor. Do poprawnego działania należy najpierw włączyć obsługę :
glEnable(GL_COLOR_MATERIAL);
Oraz ustalić rodzaj materiału podobnie do wyżej wspomnianej funkcji :
glColorMaterial(GLenum side, GLenum pname);
Źródła światła
Aby zrozumieć mechanizm oświetlenia w OpenGL należałoby zapoznać się pojęciem wektora normalnego, czyli w ogólnym mniemaniu półprostej prostopadłej do określonej płaszczyzny. Jednakże kąt padania nie zawsze musi być prosty - można nim manipulować, co stwarza wrażenie wypukłości linii czy płaszczyzny bowiem kąt padania i odbicia światła jest załamywany pod określonym innym kątem. Zwrot jest skierowany ku „górze” określając przednią stronę wielokąta. Standardowo przyjęto, iż przednia strona jest określona w miejscu, w którym wierzchołki są ułożone w kierunku przeciwnym do ruchu wskazówek zegara.
OpenGL obsługuje maksymalnie osiem niezależnych źródeł światła. Implementacja przykładowego wygląda następująco :
glLightfv(GL_LIGHT0,GL_AMBIENT,a); // „a” to jest wartości w skali RGBA
glLightfv(GL_LIGHT0,GL_POSITION,b); // “b” to współrzędne
glEnable(GL_LIGHT0); // włączenie światła
Efekty oświetlenia - odbłyski
Jest to efekt często dostrzegany na wodzie lub powierzchniach metalicznych. W języku OpenGL jest określany mianem specular i wygląda to następująco :
w przypadku źródła światła należy dodać własność GL_SPECULAR:
glLightfv(GL_LIGHT0,GL_SPECULAR,rgba_specular);
podobnie należy także dodać własność materiału danego obiektu :
glMaterialfv(GL_FRONT, GL_SPECULAR, rgba_specular);
dodatkowo można określić połysk :
glMaterialfv(GL_FRONT, GL_SHININESS,shine_scale);
gdzie shine_scale jest w zakresie <0,128>
Światła punktowe
Aby promienie światła nie rozchodziły się we wszystkich kierunkach równomiernie potrzebne jest sprecyzowanie źródła światła jako punktowe. W takim przypadku światło rozchodzi się na kształt stożka jak w poniższym rysunku :
GL_SPOT_CUTOFF wyznacza kąt rozwarcia figury (w przypadku pozycyjnego wynosi 180˚). W kodzie definicji źródła światła należy zatem dodać :
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF,kat_f);
Aby uzyskać efekt świecącego punktu :
glLightf(GL_LIGHT0, GL_SPOT_EXPONENT,size_f);
Listy wyświetlania
Tworzy się w celu optymalizacji projektu - w szczególności przy złożonych aplikacjach gdzie występuje duża liczba wielokątów. Jest również pomocna przy „katalogowaniu” obiektów w klasy. Listą wyświetlania możemy nazwać określoną grupę operacji OpenGL, która może być później wielokrotnie wykorzystywana w kodzie programu. Zaoszczędza się w ten sposób zużycie procesora na dodatkowe te same obliczenia.
Implementacja
Mechanizm działania jest bardzo podobny budowy prymitywów :
glNewList(id, GL_COMPILE)
…
// kod OpenGL
…
glEndList();
Gdzie:
id - podajemy identyfikator listy, natomiast dyrektywę GL_COMPILE można zmienić na GL_COMPILE_AND_EXECUTE co dodatkowo po kompilacji spowoduje wykonanie danych komend.
Wywołać daną listę można przez :
glCallList(id);
Listy wyświetlania mogą mieć dodatkową strukturę zagnieżdżoną. W MS Windows jest ona dozwolona do maksymalnie 64 poziomów.
Mapowanie tekstur
Pliki bitmap
Najpopularniejszy format BMP używany w MS Windows jest zarazem najprostszym w użyciu w programach. OpenGL ma wbudowane funkcje obsługi bitmap, tak więc implementacja wszelakich operacji następuje dosłownie krok po kroku. Najczęściej nie występuje w nim żadna kompresja przez co łatwo jest odczytać poszczególne piksele obrazu. Generalnie występują dwa rodzaje organizacji pliku BMP - z kolorem RGB oraz z gotową paletą kolorów.
Pojęcie teksturowania
Mapowanie tekstur to nic innego jak nakładanie obrazów na wielokąty tudzież obiekty. W ten sposób obrazy stają się bardziej realistyczne i nabierają bardziej prawdziwych kształtów. W pierwszych programach czy grach był stosowany tzw. raycasting gdzie sam mechanizm nakładania tekstur jest wydajniejszy od OpenGL jednak jest pozbawiony wielu cech. Musi być na przykład nakładany na gładkie 2-wymiarowe powierzchnie. Zastosowany w OpenGL mechanizm jest bardziej elastyczny, posiada więcej opcji do jak najdokładniejszego rozmieszczenia tekstur. Dzisiejsze chipsety kart graficznych w pełni już wspierają sprzętowo ów mechanizm, więc procesor jest w coraz bardziej znacznym stopniu odciążony.
Definiowanie tekstur
Najczęściej używany rodzaj tekstur to obrazy dwuwymiarowe. Dostępne są również jedno- i 3-wymiarowe ale są rzadko spotykane. Przykład implementacji samej definicji dwuwymiarowej tekstury :
void glTexImage2D(GL_TEXTURE_2D, GLint poziom, GLint komponenty, GLsizei w, GLsizei h, GLint border, GLenum format, GLenum typ, const GLvoid *pixels)
gdzie :
poziom - poziom szczegółów mipmapy
komponenty - ilość komponentów koloru w zakresie <1,4>
w i h - wymiary tekstury (w najogólniejszym przypadku muszą być potęgą liczby 2)
order - szerokość ramki dookoła tekstury w zakresie <0,2>
format - format danych pikseli (najpopularniejszy to GL_RGBA)
typ - typ danych dla wartości pikseli
pixels - dane pikseli
Inną pomocną funkcją jest glTexCoord dzięki której określa się współrzędne tekstury na wielokącie.
Do przechowywania obrazów tekstur najłatwiej używać list wyświetlania. Przyspiesza to działania statycznych rozkazów operacji na teksturach.
Poza tym w nadchodzących nowych wersjach OpenGL mechanizm ten ma być coraz bardziej polepszony - załadowanie kolejnych tekstur odbywać się będzie bezpośrednio w pamięci karty graficznej.
Przy pisaniu kodu należy pamiętać o uaktywnieniu funkcji gl_Enable(GL_TEXTURE_2D). Następnie o wywołaniu z listy wyświetlania danej definicji tekstury glCallList(Jakas_tekstura). Od tej chwili każdy wielokąt będzie automatycznie pokrywany daną teksturą. Na koniec należy wyłączyć obsługę tekstur gl_Disable(GL_TEXTURE_2D).
Mipmaping
Specyfikacja OpenGL obsługuje standardowo mipmaping tekstur. Jest to technika tym bardziej pomocna gdzie 3-wymiarowa scena, którą tworzymy będzie służyła jako świat po którym obserwator będzie się poruszał. Podczas ruchu kamery niepotrzebne bowiem jest dokładne renderowanie wszystkich obiektów w zasięgu wzroku. Można najbliższe obiekty dokładnie odświeżać natomiast dalsze, które są daleko „na horyzoncie” widoku kamery wyświetlać w mniejszej rozdzielczości. Biblioteka narzędziowa GLUT posiada dwie bardzo pomocne funkcje, które w pełni zajmują się generowaniem mipmap w oparciu o tekstury o wysokiej rozdzielczości. Dla obrazów dwuwymiarowych jest to gluBuild2DMipmaps.
Kwadryki
W bibliotece narzędziowej GLUT możemy znaleźć definicję kilku figur geometrycznych, bez których trudno byłoby modelować złożone obiekty. Są to między innymi sfery, cylindry i dyski.
Definiowanie i rysowanie kwadryk
Aby móc utworzyć gotową kwadrykę należy najpierw zainicjalizować stan Kwadryki która jest zmienną swojego typu opisującą różne charakterystyki figury. Służy do tego funkcja :
GLUquadricObj *obj;
obj = gluNewQuadric();
Można kontrolować sposób rysowania kwadryki - dzięki funkcji :
gluQuadricDrawStyle (GLUquadric *obj, GLenum drawStyle)
gdzie za drawStyle można przyjąć :
GLU_FILL |
Wypełnianie za pomocą wielokątów i pasków prymitywów |
GLU_LINE |
Tworzona jest jedynie siatka z odcinków |
GLU_SILHOUTTE |
Rysowane są tylko zewnętrzne krawędzie |
GLU_POINT |
Rysowane jako zbiór punktów |
Obliczanie wektorów normalnych jest w pełni zautomatyzowane - można dodatkowo zdefiniować dokładnie sposób ich tworzenia poprzez podanie parametru normals funkcji :
gluQuadricNormals(GLUquadricObj *obj, GLenum normals);
GLU_NONE |
Normalne nie są generowane |
GLU_FLAT |
Normalne są generowane dla całych wielokątów |
GLU_SMOOTH |
Normalne są generowane indywidualnie dla każdego z wierzchołków |
Cylindry
Do tworzenia cylindrów służy funkcja :
gluCylinder(GLUquadric *obj, GLdouble bRadius, GLdouble tRadius, GLdouble h, GLint slices, GLint stacks)
gdzie za parametry :
bRadius i tRadius - promień cylindra na górnej i dolnej podstawie
h - to wysokość cylindra
slices i stacks - określa z ilu ścian oraz z ilu segmentów będzie generowany cylinder (zwykle często używaną wartością jest 15 do 20 ścian)
W ten sposób powstaje tuba cylindra (bez podstaw). Za pomocą tej funkcji można również otrzymać stożek bez podstawy podając jedynie za jeden z promieni podstaw wartość 0.
Dyski
Dyski zaimplementowane w GLUT mogą posiadać wewnątrz. dodatkowo „dziurę”:
gluDisk(GLUquadricObj *obj, GLdouble inRadius, GLdouble outRadius, GLint slices, GLint loops)
gdzie :
inRadius / outRadius - promień dysku oraz dziury zawartej w środku (w przypadku inRadius równego 0 rysowany jest bez dziury)
slices - określa z ilu ścian się składa (dla poprawnych rezultatów wizualnych powinna to być wartość <15,20>)
loops - określa ilość segmentów pomiędzy wewnętrznym a zewnętrznym promieniem (wystarczy 1 dla dysków oraz 2 dla pierścieni)
Dodatkowo istnieje funkcja rysowania tylko określonej części dysku czy cylindru - gluPartialDisk - składnia jest podobna do wyżej wymienionej z wyjątkiem, że dwa ostatnie dodatkowe parametry określają początkowy i końcowy kąt rysowania.
Sfery
Rysowanie kul odbywa się za pomocą funkcji :
gluShpere(GLUquadric *obj, GLdouble radius, GLint slices, GLint stacks)
podając promień kuli (radius) oraz dokładność - interpretując slices jako liczbę południków oraz stacks jako liczbę równoleżników.
Renderowanie kwadryk
Odbywa się zawsze względem początku układu współrzędnych, tak więc przed ich rysowaniem należy przesunąć układ odniesienia.
Ogólne zasady działania engine'a
Wczytywanie danych
Dane wczytywane są z szeregu plików oraz przez różne mechanizmy:
Pliki *.lev
Pliki te przechowują dane o całym malowanym poziomie - ilość i rozmieszczenie pomieszczeń oraz nazwy plików, w których są one zdefiniowane. Każde pomieszczenie jest opisane przez dwa rodzaje plików: rom i dyn.
Pliki *.rom
Pliki te przechowują informacje o ścianach oraz wszystkich obiektach statycznych znajdujących się w danym pomieszczeniu.
Pliki *.dyn
Pliki te przechowują informacje o obiektach dynamicznych znajdujących się w danym pomieszczeniu.
Pliki *.ld
Pliki te przechowują informacje obiektach i elementach, z którymi zachodzi interakcja, czyli o: punktach informacyjnych, drzwiach i punktach wycieczki.
Umieszczanie danych w pamięci
Po wczytaniu ściany oraz obiekty statyczne są umieszczane w listach wyświetlania, a następnie usuwane z pamięci. Obiekty dynamiczne oraz wszystkie dane wczytane z plików ld są przechowywane w odpowiednio dostosowanych strukturach danych.
Obiekty umieszczane są w tablicy jednowymiarowej i opisane przez typ i podtyp obiektu. Typ obiektu oznacza, czy jest to np.: zegarek, biurko, czy inny element, natomiast podtyp określa jego rodzaj, np.: biurko duże drewniane, czy małe metalowe.
Dane o drzwiach i punktach wycieczki również umieszczane są w odpowiednich tablicach.
Inaczej jest w przypadku punktów informacyjnych - są one umieszczone w tablicach zagnieżdżonych - jedna tablica zawiera wskaźniki do struktur (klas) CInfoPoint, natomiast te (każda sobie) przechowują informacje o ekranach, które znowu (każdy sobie) informacje o ramkach.
Malowanie obiektów w listach wyświetlania
Po wczytaniu obiektów statycznych i ścian z danego pokoju są one malowane w listach wyświetlania.
Mechanizm do tego użyty wygląda następująco:
Zdefiniowana jest tablica o elementach typu CObject, natomiast każdy z nich inicjowany jest jako konkretny obiekt, który dziedziczy z klasy CObject, np.: n-ty element tej tablicy jest inicjowany jako obiekt typu CKrzeslo. Tak więc wcześniej omawiany typ tutaj jest indeksem w tablicy obiektów, podczas gdy podtyp jest argumentem funkcji malującej dany obiekt.
Obsługa animacji drzwi zaimplementowana jest w klasie CDoor. Na podstawie zadanego stanu wylicza ona kąt pod jakim w danej klatce animacji mają być wyświetlane drzwi oraz czy są otwarte, otwierane, zamknięte czy zamykane. Tak więc procedury malujące drzwi wywołują tylko metodę malowania, nie interesując się, co się aktualnie z drzwiami dzieje.
Malowanie sceny
Malowanie sceny odbywa się w dwóch głównych procedurach: DrawLevel oraz DrawGLScene.
Procedura DrawLevel odpowiedzialna jest za umieszczenie w scenie wszystkich ścian i obiektów (zarówno statycznych, jak i dynamicznych) oraz za optymalizacje szybkości wyświetlania. Pierwsze z tych zadań realizowane jest przez przeglądnięcie wszystkich obiektów i umieszczenie ich w scenie. Optymalizacja polega na ograniczeniu przeglądanego zbioru obiektów do obiektów (mówimy cały czas również o ścianach) spełniających jeden z dwóch warunków:
obiekt znajduje się w tym samym pomieszczeniu, co obserwator,
obiekt znajduje się w pomieszczeniu, w którym chociaż jedne drzwi są otwarte.
Do sprawdzenia pierwszego warunku wykorzystywany jest detektor kolizji. Ponieważ tworzona przez niego mapa kolizji była tylko częściowo wykorzystywana, mechanizm otrzymał drugą funkcję - podczas wczytywania obiektów, z którymi zachodzi kolizja, na mapie, w miejscach, gdzie nie ma żadnych obiektów kolizyjnych, zaznacza się numer pomieszczenia. W ten sposób, odwołując się do mapy kolizji, otrzymujemy praktycznie bez żadnych wyliczeń numer listy wyświetlania, która zawiera w sobie wszystkie obiekty z danego pomieszczenia. W ten sposób otrzymujemy numer listy wyświetlania, która musi być wyświetlona.
Drugi warunek sprawdzany jest na podstawie stanu drzwi. Każde drzwi mają przypisany numer listy wyświetlania - pomieszczenia do którego prowadzą. Jeżeli chociaż jedne drzwi do pomieszczenia są otwarte, to malowane jest całe pomieszczenie.
Należy przy tym wspomnieć, iż każde drzwi mają w sobie mechanizm, który regularnie sprawdza, czy drzwi są otwarte i, jeżeli tak, to jak długo są otwarte. Jeżeli są otwarte dłużej niż 10 sekund, to automatycznie wywoływana jest procedura ich zamknięcia.
Procedura DrawGLScene decyduje, czy w danym momencie wyświetlany jest poziom, czy menu początkowe. Jeżeli wyświetlany jest wczytany poziom, to rozpatrywany jest tryb oglądania (malowanie sceny rozumiane jest tutaj jako wywołanie funkcji DrawLevel):
wycieczka - należy obliczyć kolejne współrzędne obserwatora (funkcja CalculateTripPositions) oraz namalować scenę,
punkt informacyjny podczas wycieczki - należy namalować scenę oraz wyświetlić konkrety punkt informacyjny,
otwieranie/zamykanie drzwi podczas wycieczki - należy namalować scenę oraz animację otwieranych/zamykanych drzwi,
zwiedzanie - należy obliczyć kolejne współrzędne obserwatora (funkcja CalculatePositions) oraz namalować scenę,
punkt informacyjny podczas zwiedzania - należy namalować scenę oraz wyświetlić odpowiedni punkt informacyjny,
otwieranie/zamykanie drzwi podczas zwiedzania - należy namalować scenę oraz wyświetlić animację otwieranych/zamykanych drzwi.
Ponadto do poszczególnych trybów przypisana jest obsługa klawiszy sterujących.
Wycieczka
Funkcja CalculateTripPositions pobiera z obiektu klasy CTrip dane dotyczące następnego punktu, w którym obserwator powinien się znaleźć. Dane punktu zawierają w sobie również indeks akcji, jaka ma być podjęta w tym punkcie (przemieszczenie z lub bez efektu ruchu, otwieranie/zamykanie drzwi, wyświetlanie punktów informacyjnych, zakończenie wycieczki). W zależności od położenia obserwatora w osi Y odtwarzane są dźwięki chodzenia.
Przemiszczanie obserwatora
Funkcja CalculatePositions oblicza współrzędne obserwatora w zależności od sygnałów dochodzących z klawiatury. Na podstawie detektora kolizji sprawdzane jest, czy przemieszczenie może się odbyć. Osobno przeprowadzana jest detekcja kolizji z drzwiami, lecz na podobnych zasadach. Ponadto, w razie potrzeby, odtwarzane są odgłosy chodzenia. Zwrócić należy również uwagę na zmienną speed, która na podstawie czasu pomiędzy poszczególnymi klatkami określa prędkość przemieszczania się, co w rezultacie pozwala zachować podobną prędkość na mniej i bardziej wydajnych komputerach.
Punkty informacyjne (InfoPoints)
Wyświetlanie InfoPoint'ów polega na zmianie trybu wyświetlania na wyświetlanie InfoPoint'ów i wywoływania metody malowania zawartej w klasie DCcd. Ona przechowuje w sobie dane o punktach informacyjnych i to ona zarządza, który aktualnie ma być wyświetlany.
Punkty informacyjne składają się z ekranów, z których każdy ma określony czas, po którym musi zniknąć (chyba, że będzie to przyspieszone naciśnięciem klawisza <enter>). Każdy ekran składa się z ramek. Ramka określona jest przez typ i położenie na ekranie. Ramka może być ramką tekstową, obrazkową lub muzyczną.
Wyświetlanie ramek na ekranie realizuje obiekt klasy CTextWriter. Implementuje ona w sobie metody wyświetlające każdy typ ramek, a teksty pobiera z obiektu klasy CTextHandler lub odbiera z parametru wywołania funkcji.
Obiekt klasy CTextHandler przechowuje wszystkie teksty, które są wyświetlane na ekranie podczas podróży po modelowanym świecie. Została ona wprowadzona, aby ułatwić edycję tych tekstów oraz obsługę wielu języków.
Obsługa klawiatury
Obsługa klawiatury polega na odczytywaniu wartości w tablicy jednowymiarowej, indeksowanej kodami ASCII klawiszy. Wartość true oznacza, że dany klawisz jest aktualnie wciśnięty. Tablica ta jest aktualizowana przez procedurę obsługującą komunikaty odbierane przez okno programu.
Klasy i moduły
Projekt ma formę modularną. Składa się z wielu funkcjonalnie spójnych części, z których każda może zostać ponownie wykorzystana bez konieczności wprowadzania żadnych przeróbek.
Każdy moduł jest zdefiniowany jako oddzielna klasa i udostępnia pewne publiczne metody.
Funkcjonalność i sposób wykorzystania poszczególnych modułów została opisana poniżej.
Klasa: CAnimationModule
Klasa CAnimationModule odpowiada za animowanie obiektów na scenie. Animację w rozumieniu tego modułu rozumieć należy jako zestaw przekształceń (obrót, przesunięcie, skalowanie, zmiana koloru) wykonywanych na danym obiekcie. De facto nie jest przesuwany obiekt tylko cały układ współrzędnych.
Każda animacja zdefiniowana jest jako zbiór klatek. Każda klatka jest strukturą przechowującą informacje o przesunięciu, obrocie, skalowaniu, kolorze oraz czasie trwania:
struct frame {
GLfloat translate[3];
GLfloat rotate[4];
GLfloat scale[3];
GLfloat color[4];
unsigned long durration;
bool makeT, makeR, makeS, makeC;
}
Definiowanie animacji rozpoczynamy od wywołania konstruktora i podania liczby klatek. Następnie dodajemy kolejne klatki przy użyciu metody addFrame.
Mechanizm animacji jest następujący:
w momencie wykonania metody animate sprawdzany jest czas jaki upłynął od chwili rozpoczęcia animacji do chwili obecnej. Animację rozpoczyna metoda start,
w przypadku klatek niezależnych wszystkie klatki, których sumaryczny czas jest mniejszy od obliczonej różnicy są pomijane. W przypadku klatek zależnych wszystkie klatki, których sumaryczny czas jest mniejszy od obliczonej różnicy są wykonywane. Wykonanie pojedynczej klatki polega na przeprowadzeniu kolejno przekształceń: transformacja, obrót, skalowanie i zmiana koloru).
Może się zdarzyć, że niecała klatka mieści się w ramie czasowej, np. gdy zdefiniowana długość klatki to 5 sekund, a od początku animacji minęła zaledwie sekunda. Wtedy wszystkie przekształcenia są proporcjonalnie mniejsze. Najlepiej obrazuje to poniższy przykład:
Klatki zależne; klatka1: długość 1 sek, przesunięcie o [1, 0, 0]; klatka2: długość 4 sek, przesunięcie o [0,4,0].
t = 1 sek - układ współrzędnych zostanie przesunięty o wektor [1, 0, 0],
t = 2 sek - układ współrzędnych zostanie przesunięty o wektor [1, 1, 0] (wektor [1, 0, 0] - 1 klatka i 1/4 wektora [0, 4, 0] - 2 klatka)
t = 5 sek - układ zostanie przesunięty o wektor [1,4,0].
W celu ułatwienia definiowania animacji można stworzyć plik tekstowy z animacją. Format pliku opisany poniżej.
CAnimationModule (int numberOfFrames, bool indep)
Autor
Marcin Tucholski
Opis metody
Konstruktor obiektu klasy CAnimationModule
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
numberOfFrames |
liczba klatek animacji |
bool |
indep |
true - kolejne klatki animacji nie zależą od siebie (wszelkie transformacje odbywają się względem stanu początkowego) false - klatki zależne - transformacja odpowiadająca n-tej klatce animacji poprzedzana jest przez transformacje odpowiadające klatkom 1..n-1 |
Argumenty wyjściowe
Brak
CAnimationModule (char *fileName)
Autor
Marcin Tucholski
Opis metody
Konstruktor obiektu klasy CAnimationModule. Konstruktor tworzy animację na podstawie parametrów zapisanych w pliku. Format pliku:
LiczbaKlatek KlatkiNiezalezne
tX1 tY1 tZ1
rA1 rX1 rY1 rZ1
sX1 sY1 sZ1
cR1 cG1 cB1 cA1
Czas1
mT1 mR1 mS1 mC1
... ... ... ...
tXn tYn tZn
rAn rXn rYn rZn
sXn sYn sZn
cRn cGn cBn cAn
Czasn
mTn mRn mSn mCn
Oznaczenia:
tXn tYn tZn - współrzędne X, Y i Z przesunięcia odpowiadającego n-tej klatce animacji,
rAn rXn rYn rZn- kąt i wektor obrotu odpowiadającego n-tej klatce aniamacji,
sXn sYn sZn - współczynniki skalowania X, Y i Z dla n-tej klatki animacji,
cRn cGn cBn cAn-składowe R,G, B i współczynnik alfa dla n-tej klatki animacji,
Czasn -czas trwania n-tej klatki animacji,
mTn mRn mSn mCn-wskazuje, które ze składowych animacji będą przetwarzane (transformacja, obrót, skalowanie i zmiana koloru - 1 1 1 1 oznacza wszystkie, 0 0 0 0 - żadne),
W pliku nagłówkowym zdefiniowana jest stała ANIMATIONDIR. Konstruktor przyjmuje, że podany jako argument plik znajduje się we wskazanym przez ANIMATIONDIR katalogu.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
fileName |
nazwa pliku przechowującego animację. |
Argumenty wyjściowe
Brak
void addFrame(frame *frame_)
Autor
Marcin Tucholski
Opis metody
Dodaje klatkę do animacji
Argumenty wejściowe
Typ |
Nazwa |
Opis |
frame* |
frame_ |
wskaźnik na klatkę, która ma być dodana |
Argumenty wyjściowe
Brak
void start()
Autor
Marcin Tucholski
Opis metody
Rozpoczyna odtwarzanie animacji. Animacja ustawiana jest na początku.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
void reset()
Autor
Marcin Tucholski
Opis metody
Ustawia animację na początku.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
void stop()
Autor
Marcin Tucholski
Opis metody
Zatrzymuje animację
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
bool finished()
Autor
Marcin Tucholski
Opis metody
Zwraca status animacji.
Argumenty wejściowe
Brak
Argumenty wyjściowe
true |
animacja się zakończyła |
false |
animacja jeszcze trwa |
void animate()
Autor
Marcin Tucholski
Opis metody
Przekształca układ współrzędnych zgodnie z postępem odtwarzania animacji.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
void animate(int delay)
Autor
Marcin Tucholski
Opis metody
Przekształca układ współrzędnych zgodnie z postępem odtwarzania animacji. Początek animacji zostaje opóźniony o wskazaną ilość milisekund.
Argumenty wejściowe
int |
delay |
opóźnienie odwarzania animacji w milisekundach |
Argumenty wyjściowe
Brak
Klasa: CAudioCtrl
Obiekty tej klasy zarządzają dźwiękiem odtwarzanym podczas działania programu.
Klasa ta korzysta ze struktury:
struct MusicFile
{
HWND hAudioWnd;
bool bLoop;
char FileName[100];
};
Struktura MusicFile przechowuje informacje o dźwiękach. Jej elementami są: uchwyt do okna odtwarzającego dźwięk, określenie zapętlenia oraz ścieżkę do pliku i jego nazwę.
Klasa ta przechowuje informacje o ośmiu plikach jednocześnie plus melodia odtwarzana w tle. Dane o dźwięku, po jego odtworzeniu, pozostają w pamięci tak długo, jak długo nie zaistnieje potrzeba zastąpienia jego danych danymi innego dźwięku, który dopiero ma być odtworzony.
Dźwięki w tle są odtwarzane losowo, przy czym żadna melodia nie będzie odtwarzana zaraz po swoim zakończeniu.
CAudioCtrl(bool SoundOn)
Autor
Bartosz Rudnicki
Opis metody
Konstruktor obiektu klasy CAudioCtrl.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
bool |
SoundOn |
Zmienna określająca, czy dźwięk ma być włączony |
Argumenty wyjściowe
Brak
~CAudioCtrl()
Autor
Bartosz Rudnicki
Opis metody
Destruktor obiektu klasy CAudioCtrl.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
Play(char *FileName, bool bLoop, int iMode)
Autor
Bartosz Rudnicki
Opis metody
Metoda odtwarza dźwięk.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
FileName |
Nazwa pliku do odtworzenia |
bool |
bLoop |
Wartość określająca, czy dźwięk ma być zapętlony |
int |
iMode |
Tryb odtwarzania |
Argumenty wyjściowe
Brak.
Stop(char *FileName)
Autor
Bartosz Rudnicki
Opis metody
Metoda zatrzymuje odtwarzany dźwięk.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
FileName |
Nazwa pliku do zatrzymania |
Argumenty wyjściowe
Brak.
Manage()
Autor
Bartosz Rudnicki
Opis metody
Metoda zarządza odtwarzanymi dźwiękami - sprawdza, czy nieodtwarzany dźwięk ma ustawione zapętlenie i, jeżeli tak, to odtwarza go ponownie.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
HandleBackgroundMusic()
Autor
Bartosz Rudnicki
Opis metody
Metoda zarządza dźwiękami odtwarzanymi w tle.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
PlayMenu()
Autor
Bartosz Rudnicki
Opis metody
Metoda odtwarza melodię w menu.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
PlayTrip()
Autor
Bartosz Rudnicki
Opis metody
Metoda odtwarza melodię podczas trwania wycieczki.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
PlayFree()
Autor
Bartosz Rudnicki
Opis metody
Metoda odtwarza melodię podczas trwania zwiedzania.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
InitBackgroundMusic()
Autor
Bartosz Rudnicki
Opis metody
Metoda inicjalizuje melodie odtwarzane w tle.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
StopMenu()
Autor
Bartosz Rudnicki
Opis metody
Metoda zatrzymuje odtwarzanie melodii w menu.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
Klasa: CBkgMenuModule
Klasa bkgMenuModule odpowiada za wyświetlanie zmieniających się obrazów tła podczas wyświetlania menu. Może znaleźć szerokie zastosowanie wszędzie tam, gdzie mają być wyświetlane w kolejności obrazy z efektami przejścia.
CBkgMenuModule (int liczbaObrazow, char *animation, int delay)
Autor
Marcin Tucholski
Opis metody
Konstruktor obiektu klasy CBkgMenuModule
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
liczbaObrazów |
liczba obrazów tła |
char* |
animation |
plik definiujący animację przejść między obrazami tła |
int |
delay |
czas przez który dany obraz jest wyświetlany |
Argumenty wyjściowe
Brak
void addImage(char *fileName)
Autor
Marcin Tucholski
Opis metody
Funkcja dodaje obraz do puli obrazów.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
fileName |
nazwa pliku z obrazem |
Argumenty wyjściowe
Brak
void paint()
Autor
Marcin Tucholski
Opis metody
Metoda rysuje aktualny obraz.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
Klasa: CCd
Klasa CCd początkowo miała być mechanizmem odpowiadającym za detekcję kolizji, jednakże wykorzystane w niej struktury okazały się niezwykle pomocne w detekcji pomieszczenia, w którym się znajdujemy.
Klasa ta przechowuje informacje o scenie w postaci dwuwymiarowej mapy, w której wartości większe od zera oznaczają ściany, natomiast elementy ujemne oznaczają numer pomieszczenia, w którym się znajdujemy (wartość bezwzględna plus jeden daje nam numer listy wyświetlania danego pomieszczenia).
Mechanizm wykrywania kolizji działa następująco: dla podanych dwóch punktów (początkowy i końcowy punkt przemieszczenia) sprawdzamy, czy nie ma między nimi żadnego obiektu ograniczającego ruch. Jeżeli nie, to przemieszczenie jest prawidłowe. W przeciwnym wypadku sprawdzamy, czy można się przemieścić tylko w jednym wymiarze (w X lub w Y). Jeżeli tak, to przemieszczenie następuje tylko w tym wymiarze. Jeżeli żadne z przemieszczeń nie może być dokonane, przemieszczenie jest anulowane.
CCd(char *LevelName)
Autor
Bartosz Rudnicki
Opis metody
Konstruktor obiektu klasy CCd. Metoda przegląda podany plik i odczytuje z niego pozycje obiektów, które ograniczają przemieszczenia, a następnie nanosi te obiekty na dwuwymiarową mapę.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
Char* |
LevelName |
Nazwa pliku przechowującego dane o poziomie. |
Argumenty wyjściowe
Brak
~CCd()
Autor
Bartosz Rudnicki
Opis metody
Destruktor obiektu klasy CCd
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
void CreateRoom(int iRoom)
Autor
Bartosz Rudnicki
Opis metody
Nanosi na mapę teren zajmowany przez pokój (listę wyświetlania) o indeksie iRoom
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
iRoom |
Index pokoju |
Argumenty wyjściowe
Brak
void RoomSize(Swall W, bool bMode)
Autor
Bartosz Rudnicki
Opis metody
Metoda sprawdza, czy podana ściana leży wewnątrz aktualnie analizowanego pokoju. Jeżeli nie, to zwiększany jest prostokąt opisujący powierzchnię danego pokoju.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
SWall |
W |
Struktura opisująca ścianę |
bool |
bMode |
Wartość logiczna objaśniająca, czy jest to pierwsza ściana w tym pokoju. |
Argumenty wyjściowe
Brak
void SetTranslation(float *NewTranslation)
Autor
Bartosz Rudnicki
Opis metody
Ustala przesunięcie pomieszczenia względem środka sceny.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
*NewTranslation |
Wskaźnik na dwuwymiarową tablicę typu float, określającą przesunięcie |
Argumenty wyjściowe
Brak
int min(int a, int b)
Autor
Bartosz Rudnicki
Opis metody
Wyznacza mniejszą z dwóch wartości
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
a |
Pierwsza wartość |
int |
b |
Druga wartość |
Argumenty wyjściowe
Mniejsza z dwóch podanych wartości.
int max(int a, int b)
Autor
Bartosz Rudnicki
Opis metody
Wyznacza większą z dwóch wartości
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
a |
Pierwsza wartość |
int |
b |
Druga wartość |
Argumenty wyjściowe
Większa z dwóch podanych wartości.
int GetRoomNumber(float fX, float fZ)
Autor
Bartosz Rudnicki
Opis metody
Zwraca indeks pomieszczenia, w którym znajduje się zadany punkt.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
fX |
Współrzędna X |
float |
fY |
Współrzędna Y |
Argumenty wyjściowe
Indeks pomieszczenia.
void AddObject(SWall W, int iRoom)
Autor
Bartosz Rudnicki
Opis metody
Dodaje ścianę do mapy kolizji.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
SWall |
W |
Dane ściany |
int |
iRoom |
Indeks pomieszczenia |
Argumenty wyjściowe
Brak
int isCollision(int X1, int Y1, int X2, int Y2)
Autor
Bartosz Rudnicki
Opis metody
Metoda, dla podanych dwóch punktów, sprawdza, czy możliwe jest przemieszczenie z jednego punktu do drugiego.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
X1 |
Współrzędna X punktu początkowego |
int |
Y1 |
Współrzędna Y punktu początkowego |
Int |
X2 |
Współrzędna X punktu docelowego |
int |
Y2 |
Współrzędna Y punktu docelowego |
Argumenty wyjściowe
Typ |
Wartość |
Znaczenie |
int |
0 |
Przemieszczenie jest możliwe |
int |
1 |
Przemieszczenie jest możliwe tylko wzdłuż osi Y |
int |
2 |
Przemieszczenie jest możliwe tylko wzdłuż osi X |
int |
3 |
Przemieszczenie nie jest możliwe |
int isCollision(float xp, float yp, float oxp, float oyp)
Autor
Bartosz Rudnicki
Opis metody
Metoda działa identycznie, jak poprzednia, ale przyjmuje parametry typu float.
Klasa: CObiekt
Klasa CObiekt jest klasą abstrakcyjną z której dziedziczą wszystkie obiekty rysowane na scenie. Klasa uzgadnia wspólny interfejs dla różnych obiektów oraz udostępnia zestaw metod wspomagających modelowanie.
void glMyCube(float size)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje sześcian o zadanej krawędzi.
Została stworzona w celu zastąpienia standardowej funkcji glutSolidCube() ze względu na trudności z nanoszeniem tekstur na obiekty stworzone przy jej pomocy.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
size |
długość krawędzi sześciana |
Argumenty wyjściowe
Brak
void glMyCube(float x, float y, float z)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje prostopadłościan o zadanych krawędziach.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
x |
długość krawędzi leżącej na osi X prostopadłościanu |
float |
y |
długość krawędzi leżącej na osi Y prostopadłościanu |
float |
z |
długość krawędzi leżącej na osi Z prostopadłościanu |
Argumenty wyjściowe
Brak
void glMyCube(float x, float y, float z, float rozmiarTekstury)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje prostopadłościan o zadanych krawędziach. Umożliwia również rozciąganie tekstury nakładanej na prostopadłościan.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
x |
długość krawędzi leżącej na osi X prostopadłościanu |
float |
y |
długość krawędzi leżącej na osi Y prostopadłościanu |
float |
z |
długość krawędzi leżącej na osi Z prostopadłościanu |
float |
rozmiarTekstury |
rozmiar nakładanej na ściany tekstury |
Argumenty wyjściowe
Brak
void glMyCube(float x, float y, float z, char *Fr, char *Le,
char *Pr, char *Go, char *Do, char *Ty, float rozmiarTekstury,
bool smudge=false)
Autor
Jakub Malicki, Marcin Tucholski
Opis metody
Funkcja rysuje prostopadłościan o zadanych krawędziach. Umożliwia również nałożenie różnych tekstur na poszczególne jego boki i wygładzanie krawędzi metodą manipulacji normalnymi.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
x |
długość krawędzi leżącej na osi X prostopadłościanu |
float |
y |
długość krawędzi leżącej na osi Y prostopadłościanu |
float |
z |
długość krawędzi leżącej na osi Z prostopadłościanu |
char* |
Fr |
nazwa tekstury na przednią ścianę |
char* |
Le |
nazwa tekstury na lewą ścianę |
char* |
Pr |
nazwa tekstury na prawą ścianę |
char* |
Go |
nazwa tekstury na górną ścianę |
char* |
Do |
nazwa tekstury na dolną ścianę |
char* |
Ty |
nazwa tekstury na tylną ścianę |
float |
rozmiarTekstury |
rozmiar nakładanej na ściany tekstury |
bool |
smudge |
czy mają być zaokrąglane krawędzie poprzez uśrednianie normalnych |
Argumenty wyjściowe
Brak
void glMyKula(float promien, float czescKuli,
int rozdzielczoscPionowa, int rozdzielczoscPozioma, bool strona)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje fragment lub całość sfery o zadanych parametrach
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
promien |
promień rysowanego pierścienia |
float |
czescKuli |
wyrażona w procentach cześć rysowanej sfery |
int |
rozdzielczoscPionowa |
szczegółowość w pionie |
int |
rozdzielczoscPozioma |
szczegółowość w poziomie |
bool |
strona |
rysowanie wewnętrznej lub zewnętrznej części |
Argumenty wyjściowe
Brak
void glMyRing(float promien, int kat, double rozmiar, int stacks)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje fragment lub całość pierścienia o zadanych parametrach
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
promien |
promień rysowanego pierścienia |
int |
kat |
kąt rysowanego fragmentu pierścienia |
double |
rozmiar |
grubość pierścienia |
int |
stacks |
szczegółowość |
Argumenty wyjściowe
Brak
void glMyWalec(float promien, int kat, float wysokosc, bool podstawa,
bool scianyBoczne, int stacks, bool strona)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje fragment lub całość walca o zadanych parametrach.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
promien |
promień rysowanego walca |
int |
kat |
kąt rysowanego fragmentu |
float |
wysokość |
wysokość walca |
bool |
podstawa |
parametr określa, czy maja być rysowane podstawy odpowiadające rysowanemu fragmentowi walca |
bool |
scianyBoczne |
parametr określa, czy maja być rysowane ściany boczne dla danego fragmentu walca (jeżeli parametr kat ma wartość 360, to ściany boczne nie będą widoczne |
int |
stacks |
szczegółowość |
bool |
strona |
paramert określa, czy ma być rysowane wnętrze czy zewnętrze walca |
Argumenty wyjściowe
Brak
void glMyWalec(float promien, int kat, float wysokosc, bool podstawa,
bool scianyBoczne, int poziomZmniejszeniaRozdzielnosci, int stacks,
bool strona)
Autor
Jakub Malicki
Opis metody
Funkcja rysuje fragment lub całość walca o zadanych parametrach. Dodatkowy atrybut poziomZmniejszeniaRozdzielnosci umożliwia generowanie obiektów o mniejszej ilości szczegółów i, dzięki temu, zwiększa się szybkość działania projektu.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
float |
promien |
promień rysowanego walca |
int |
kat |
kąt rysowanego fragmentu |
float |
wysokość |
wysokość walca |
bool |
podstawa |
parametr określa, czy maja być rysowane podstawy odpowiadające rysowanemu fragmentowi walca |
bool |
scianyBoczne |
parametr określa, czy maja być rysowane ściany boczne dla danego fragmentu walca (jeżeli parametr kat ma wartość 360, to ściany boczne nie będą widoczne |
int |
poziomZmniejszeniaRozdzielnosci |
parametr określa jak bardzo szczegółowo ma być rysowany obiekt - umożliwia zmniejszenie szczegółowości |
int |
stacks |
szczegółowość |
bool |
strona |
parametr określa, czy ma być rysowane wnętrze czy zewnętrze walca |
Argumenty wyjściowe
Brak
Klasa: CSoundModule
Klasa CSoundModule odpowiada za odtwarzanie dźwięków w projekcie.
Implementuje ona dwa sposoby odtwarzania dźwięków: wykorzystując bibliotekę DirectSound (biblioteka DirectSound wchodzi w skład pakietu DirectX) oraz wykorzystując bibliotekę VfW (Video For Windows).
Możliwe jest jednoczesne odtwarzanie wielu dźwięków. Jest to ograniczone przez możliwości sprzętowe i system operacyjny.
Klasa obsługuje wszystkie standardowe formaty dźwięków. Możliwe jest również odtworzenie dźwięków w niestandardowym formacie. Trzeba wówczas zainstalować wymagane kodeki w systemie operacyjnym.
CSoundModule *getSoundModule()
Autor
Marcin Tucholski
Opis metody
Metoda zwraca instancję klasy CSoundModule.
Jeżeli został już wcześniej utworzony obiekt tej klasy, to zwracany jest tylko adres tego obiektu. Jeżeli jest pierwsze wykorzystanie menadżera dźwięków, to zostanie utworzony obiekt klasy CSoundModule i zwrócony wskaźnik do utworzonego obiektu.
Ponieważ konstruktor klasy CSoundModule jest konstruktorem prywatnym, to jest to jedyny mechanizm pozwalający utworzyć / skorzystać z menadżera dźwięków.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Typ |
Opis |
CSoundModule* |
wskaźnik na instancję obiektu klasy CSoundModule |
void play(char *fileName)
Autor
Marcin Tucholski
Opis metody
Metoda odtwarza dźwięk przy wykorzystaniu biblioteki DirectSound.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
fileName |
nazwa pliku dźwiękowego |
Argumenty wyjściowe
Brak
void play2(char *fileName)
Autor
Marcin Tucholski
Opis metody
Metoda odtwarza dźwięk przy wykorzystaniu biblioteki VfW.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
fileName |
nazwa pliku dźwiękowego |
Argumenty wyjściowe
Brak
Klasa: CTekst2DModule
Klasa tekst2DModule umożliwia wyświetlanie tekstów dwuwymiarowych.
int init(HDC hDC_, char *fontName, double fontSize_)
Autor
Marcin Tucholski
Opis metody
Metoda inicjuje daną czcionkę, tworzy odpowiadające jej listy wyświetlania i zwraca numer bazowy dla danej czcionki. Jeżeli czcionka została już wcześniej użyta zwracany jest tylko numer bazowy bez ponownego tworzenia listy wyświetlania. Maksymalna liczba fontów z których można skorzystać zdefiniowana jest w stałej MAXFONTNUM w pliku tekst2DModule.h.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
HDC |
hDC_ |
uchwyt urządzenia wyświetlającego |
char* |
fontName |
nazwa czcionki |
double |
fontSize_ |
wysokość czcionki |
Argumenty wyjściowe
Typ |
Opis |
int |
numer bazowy danej czcionki |
void PrintString(unsigned int base, char *str, double xPos, double yPos)
Autor
Marcin Tucholski
Opis metody
Metoda wypisuje na ekranie tekst przy użyciu wybranej czcionki.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
unsigned int |
base |
numer bazowy czcionki |
char* |
str |
tekst do wypisania |
double |
xPos |
współrzędna X napisu |
double |
yPos |
współrzędna Y napisu |
Argumenty wyjściowe
Brak
void PrintLines(int base, char *str, double xPos, double yPos, double vOdl)
Autor
Marcin Tucholski
Opis metody
Metoda wypisuje na ekranie tekst przy użyciu wybranej czcionki. W przypadku napotkania znaku nowej linii pozostała część wypisywana jest niżej. Przesunięcie między kolejnymi liniami zdefiniowane jest przez argument vOdl.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
unsigned int |
base |
numer bazowy czcionki |
char* |
str |
tekst do wypisania |
double |
xPos |
współrzędna X napisu |
double |
yPos |
współrzędna Y napisu |
double |
vOdl |
odległość między kolejnymi liniami tekstu |
Argumenty wyjściowe
Brak
Klasa: CTekst3DModule
Klasa CTekst3DModule umożliwia wyświetlanie tekstów trójwymiarowych.
Moduł tworzy listy wyświetlania dla każdej litery. Zapewnia również to, że jeżeli dana czcionka została wcześniej użyta, to nie zostaną ponownie wygenerowane listy wyświetlania.
int init(HDC hDC_, char *fontName, double fontDepth)
Autor
Marcin Tucholski
Opis metody
Metoda inicjuje daną czcionkę, tworzy odpowiadające jej listy wyświetlania i zwraca numer bazowy dla danej czcionki. Jeżeli czcionka została już wcześniej użyta zwracany jest tylko numer bazowy bez ponownego tworzenia listy wyświetlania. Maksymalna liczba fontów z których można skorzystać zdefiniowana jest w stałej MAXFONTNUM w pliku tekst3DModule.h.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
HDC |
hDC_ |
uchwyt urządzenia wyświetlającego |
char* |
fontName |
nazwa czcionki |
double |
fontDepth |
głębokość czcionki |
Argumenty wyjściowe
Typ |
Opis |
int |
numer bazowy danej czcionki |
void PrintString(unsigned int base, char *str)
Autor
Marcin Tucholski
Opis metody
Metoda wypisuje tekst na ekranie przy użyciu wcześniej zainicjowanej czcionki.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
unsigned int |
base |
numer bazowy czcionki, która ma być użyta |
char* |
str |
tekst do wypisania |
Argumenty wyjściowe
Brak
Klasa: CTextHandler
Obiekty tej klasy przechowują teksty wyświetlane na ekranie podczas zwiedzania / wycieczki. Mechanizm ten został wprowadzony, aby usprawnić edycję tekstów i ułatwić obsługę wielu języków.
CTextHandler(char *filename)
Autor
Bartosz Rudnicki
Opis metody
Konstruktor obiektu klasy.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
filename |
Nazwa pliku, z którego należy wczytać dane (teksty) |
Argumenty wyjściowe
Brak
~CTextHandler()
Autor
Bartosz Rudnicki
Opis metody
Destruktor obiektu klasy.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
char *ReadString(FILE *F)
Autor
Bartosz Rudnicki
Opis metody
Metoda odczytuje segment tekstu z pliku.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
FILE* |
F |
Strumień, z którego należy odczytać |
Argumenty wyjściowe
Typ |
Opis |
char* |
Odczytany fragment |
CreateEmptyFile()
Autor
Bartosz Rudnicki
Opis metody
Metoda tworzy nowy, pusty plik z tekstami. Obecnie nieużywana, lecz była pomocna przy tworzeniu całego mechanizmu.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
LoadFromFile()
Autor
Bartosz Rudnicki
Opis metody
Metoda wczytuje teksty z pliku.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
SaveToFile()
Autor
Bartosz Rudnicki
Opis metody
Metoda zapisuje teksty do pliku. Metoda jest używana tylko w edytorze tekstów.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Brak.
SetText(int iNumber, char *string)
Autor
Bartosz Rudnicki
Opis metody
Metoda zapisuje podany ciąg znaków na określoną pozycję w tablicy. Używana jest tylko w edytorze tekstów.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
iNumber |
Indeks w tablicy |
char* |
string |
Ciąg znaków do zapisania |
Argumenty wyjściowe
Brak.
Char *GetText(int iNumber)
Autor
Bartosz Rudnicki
Opis metody
Metoda pobiera z tablicy tekst o zadanym indeksie.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
iNumber |
Indeks w tablicy |
Argumenty wyjściowe
Typ |
Opis |
char* |
Zadany tekst |
char * GetFileName()
Autor
Bartosz Rudnicki
Opis metody
Metoda zwraca nazwę pliku powiązanego z obiektem.
Argumenty wejściowe
Brak.
Argumenty wyjściowe
Typ |
Opis |
char* |
Nazwa pliku |
Klasa: CTextureManager
Klasa CTextureManager wspomaga zarządzanie teksturami i ułatwia ich wykorystywanie. Korzystanie z obiektu tej klasy zwalnia programistę z konieczności wczytywania bitmap, konwertowania ich na tekstury i pamiętania które bitmapy zostały już wcześniej wczytane. Mechanizm menadżera tekstur zapobiega sytuacjom w których jakaś tekstura byłaby wielokrotnie wczytywana i tworzona (np. w wyniku wykorzystania tej samej tekstury w różnych obiektach przez różnych członków zespołu).
Menadżer tekstur obsługuje również 3 różne tryby dokładności tekstur. Jakość tworzonych tekstur zależy od tego w jakim trybie dokładności został uruchomiony projekt.
Konstruktor klasy jest prywatny. W celu uzyskania instancji klasy należy skorzystać z metody getTextureManager. Gwarantuje ona wystąpienie tylko 1 instancji obiektu w całym projekcie.
static CTextureManager* getTextureManager()
Autor
Marcin Tucholski
Opis metody
Metoda zwraca instancję klasy CTextureManager.
Jeżeli został już wcześniej utworzona instancja tej klasy, to zwracany jest tylko adres tego obiektu.
Jeżeli jest to pierwsze wykorzystanie menadżera tekstur, to zostanie utworzony obiekt klasy CTextureManager i zwrócony wskaźnik do utworzonego obiektu.
Ponieważ konstuktor klasy CTextureManager jest konstruktorem prywatnym, to jest to jedyny mechanizm pozwalający utworzyć / skorzystać z menadżera tekstur.
Argumenty wejściowe
Brak
Argumenty wyjściowe
Typ |
Opis |
CTextureManager* |
wskaźnik na instancję obiektu klasy CTextureManager |
void setMode(int mode)
Autor
Marcin Tucholski
Opis metody
Metoda ustawia tryb działania menadżera tekstur.
W pliku nagłówkowym TextureManager.h zdefiniowane są obecnie 3 tryby:
#define MODELOW 3
#define MODEMEDIUM 2
#define MODEHIGH 1
Argumenty wejściowe
Typ |
Nazwa |
Opis |
int |
mode |
tryb pracy menadżera tekstur |
Argumenty wyjściowe
Brak
int getMode()
Autor
Marcin Tucholski
Opis metody
Metoda zwraca tryb pracy menadżera tekstur.
W pliku nagłówkowym TextureManager.h zdefiniowane są obecnie 3 tryby:
#define MODELOW 3
#define MODEMEDIUM 2
#define MODEHIGH 1
Argumenty wejściowe
Brak
Argumenty wyjściowe
Typ |
Opis |
int |
tryb pracy menadżera tekstur |
unsigned int useTexture(char *name)
Autor
Marcin Tucholski
Opis metody
Metoda ustawia bieżącą teksturę odpowiadającą nazwie pliku podanego jako argument.
Jeżeli tekstura jest użyta po raz pierwszy, to następuje jej wczytanie i utworzenie.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
name |
nazwa pliku z teksturą |
Argumenty wyjściowe
Typ |
Opis |
unsigned int |
id tekstury przydzielonej przez OpenGL |
unsigned int initTexture(char *name)
Autor
Marcin Tucholski
Opis metody
Metoda inicjuje daną teksturę.
Metoda sprawdza, czy tekstura odpowiadająca plikowi podanemu jako argument, została już wcześniej utworzona.
Jeżeli tak, to zwracany jest tylko identyfikator tekstury.
Jeżeli nie, to tekstura jest wczytywana i zwracany jest identyfikator tekstury.
Metoda nie ustawia wczytanej tekstury jako bieżącej (tak jak robi to metoda useTexture).
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
name |
nazwa pliku z teksturą |
Argumenty wyjściowe
Typ |
Opis |
unsigned int |
id tekstury przydzielonej przez OpenGL |
Plik winapi.h
Plik ten przechowuje metody używane do tworzenia i zarządzania oknem.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
Autor
Bartosz Rudnicki
Opis metody
Metoda odpowiada za zmianę rozmiaru okna.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
GLsizei |
width |
Nowa szerokość okna |
GLsizei |
height |
Nowa wysokość okna |
Argumenty wyjściowe
Brak
GLvoid KillGLWindow(GLvoid)
Autor
Bartosz Rudnicki
Opis metody
Metoda wywoływana przy zamknięciu okna. Niszczy ona okno oraz wywołuje procedury niszczące wszystkie obiekty w programie
Argumenty wejściowe
Brak
Argumenty wyjściowe
Brak
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
Autor
Bartosz Rudnicki
Opis metody
Metoda tworzy nowe okno.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
char* |
title |
Tytuł nowego okna |
int |
width |
Szerokość okna |
int |
height |
Wysokość okna |
int |
bits |
Określa paletę kolorów (8, 16, 24 lub 32 bitowa) |
bool |
fullscreenflag |
Określa stan okna (false - okno, true - cały ekran) |
Argumenty wyjściowe
Wartość logiczna określająca, czy powiodło się utworzenie nowego okna.
LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg, WPARAM wParam, LPARAM lParam)
Autor
Bartosz Rudnicki
Opis metody
Metoda obsługująca komunikaty odbierane przez okno.
Argumenty wejściowe
Typ |
Nazwa |
Opis |
HWND |
hWnd |
Uchwyt do okna |
UNIT |
uMsg |
Odbierana wiadomość |
WPARAM |
wParam |
Dodatkowa informacja do wiadomości |
LPARAM |
lParam |
Dodatkowa informacja do wiadomości |
Argumenty wyjściowe
Wynik domyślnej obsługi komunikatu.
Narzędzia pomocnicze
Na potrzeby projektu powstał zestaw narzędzi mających na celu usprawnienie procesu modelowania obiektów i pomieszczeń. Dzięki ich zastosowaniu udało się znacznie przyspieszyć te procesy.
Edytor detali
Ogromna większość obiektów wykorzystywanych w projekcie jest modelowana przy wykorzystaniu funkcji biblioteki GLu32. Zawiera ona funkcje wspomagające rysowanie takich obiektów jak: kule, walce, dyski. Oprócz tego powstał zestaw bibliotek rysujących teksturowane prostopadłościany, wycinki walca i obcięte kule.
Wykorzystanie tych funkcji znacznie przyspieszyło modelowanie poszczególnych obiektów. Jak wiadomo, podstawową figurą geometryczna wykorzystywaną w OpenGL'u jest trójkąt. Modelowanie złożonych obiektów przy wykorzystaniu pojedynczych trójkątów byłoby bardzo żmudne, czasochłonne i w wielu przypadkach bezcelowe.
Dla przykładu:
Aby zamodelować nogę od stołu za pomocą trójkątów (GL_TRIANGLES) trzeba zdefiniować 10 trójkątów. Jak wiemy definicja każdego trójkąta składa się z określenia 3 punktów w przestrzeni. Definicja pojedynczej nogi zajęłaby zatem ok. 30 linii kodu i wymagałaby żmudnych obliczeń współrzędnych punktów. Gdyby do tego doliczyć definiowanie wektorów normalnych dla każdej ściany i nałożenie na każdą ścianę innej tekstury liczba linii kodu wzrosłaby do ok. 40.
W celu zamodelowania nogi można by również wykorzystać mechanizm czworokątów (GL_QUADS). Wówczas potrzebowalibyśmy 5 czworokątów, a co za tym idzie, definicji 20 punktów. Daje to ok. 20 linii kodu.
Tą samą rzecz można zamodelować w jednej linii kodu - wykorzystując zdefiniowane w bibliotece GLu32 i utworzone przez nas funkcje pomocnicze. W tym celu można np. wykorzystać funkcję glMyCube która stworzy prostopadłościan o zadanych wymiarach. Co więcej - dla każdej wygenerowanej ściany zostaną obliczone i wygenerowane wektory normalne, a ściany zostaną pokryte zadaną teksturą.
Jak dowodzi powyższy przykład, wykorzystanie wbudowanych funkcji okazało się znacznie pomocne.
W praktyce okazało się jednak, że nie wszystko da się zaprojektować przy użyciu prostopadłościanów, cylindrów, walców itp. Czasami trzeba zejść na niższy poziom szczegółowości i modelować przy użyciu prymitywów.
Właśnie taki był cel powstania edytora detali.
Głównym zadaniem edytora detali było wspomaganie modelowania szczegółów przy użyciu trójkątów i czworokątów.
Interfejs Edytora detali
Rysunek 3. Interfejs Edytora detali
Tworzenie, otwieranie, zapisywanie, eksport:
Rysunek 4. Przyciski podstawowe
Nowy - tworzy nowy obiekt edytora detali;
Otwórz - otwiera zapisany obiekt edytora detali ;
Zapisz - zapisuje bieżący obiekt do pliku;
Eksportuj - generuje kod C++ i eksportuje go do pliku tekstowego obiekt.tmp;
Generuj normalne - generuje normalne dla każdego zdefiniowanego trójkąta i czworokąta;
Opcje podglądu
Rysunek 5. Opcje podglądu
Pokaż wybrany obiekt - po wybraniu tej opcji wyświetla się okno z podglądem rysowanego obiektu w OpenGL'u.
Rysunek 6.Okno podglądu
Wszystkie punkty, trójkąty i czworokąty rysowane są w kolorze zielonym.
Aktualnie zaznaczony punkt rysowany jest w kolorze czerwonym.
Wybrany trójkąt i wybrany czworokąt rysowane są w kolorze niebieskim.
Dzięki temu łatwo można zidentyfikować i zmodyfikować żądaną część.
Gdy aktywne jest okno z podglądem działają następujące klawisze:
a/z |
obrót obiektu względem osi OX |
q/w |
obrót obiektu względem osi OY |
s/d |
obrót obiektu względem osi OZ |
g/j |
przesunięcie obiektu wzdłuż osi OX |
h/y |
przesunięcie obiektu wzdłuż osi OY |
-/= |
przesunięcie obiektu wzdłuż osi OZ (zbliżenie/oddalenie) |
Q |
włączenie / wyłączenie trybu automatycznego obracania |
Przezroczystość - po wybraniu tej opcji formatka staje się półprzezroczysta. Dzięki temu możemy umieścić okno edytora detali nad zdjęciem obiektu. W niektórych sytuacjach może ułatwić to modelowanie (wymagany jest system Windows XP).
Zaznaczanie punktów
Rysunek 7.Zaznaczanie punktów
Po naciśnięciu przycisku Zaznacz przechodzimy w tryb zaznaczania punktów. Aby zaznaczyć punkty najeżdżamy w wybrane miejsce okna edycyjnego i trzymając wciśnięty lewy klawisz przemieszczamy wskaźnik myszy definiując punkty zaznaczone.
W danej chwili w oknie edycji widoczne są punkty z bieżącej warstwy (kolor zielony) i z warstwy poprzednio wybranej (kolor niebieski).
Jeżeli opcja All jest nieaktywna, wówczas zaznaczane są tylko widoczne punkty.
Jeżeli opcja All jest aktywna, wtedy zaznaczone zostaną punkty wszystkich warstw, również te niewidoczne.
Zaznaczone punkty można dowolnie przesuwać.
Nawigacja
Rysunek 8.Przyciski nawigacyjne
Przyciski te służą do nawigowania po obszarze edycji. Umożliwiają powiększanie i pomniejszanie skali, a także przesuwanie obiektu w poziomie i pionie.
Dodawanie elementów
Rysunek 9.Przyciski edycyjne
Przyciski umożliwiają definiowanie odpowiednio punktów, trójkątów i czworokątów. Po wybraniu któregoś z przycisków edytor przechodzi w odpowiedni tryb.
Przy dodawaniu punktów każde naciśnięcie lewego przycisku myszy na obszarze edycyjnym powoduje dodanie punktu na bieżącej warstwie.
Przy dodawaniu trójkątów każde naciśnięcie przycisku na punkcie powoduje dodanie punktu do trójkąta. Po wybraniu 3 punktów program automatycznie przechodzi do definiowania kolejnego trójkąta.
Podobnie wygląda definiowanie czworokątów. Różnica polega wyłącznie na tym, że przy definiowaniu czworokątów wybieramy 4, a nie 3 punkty.
Obok przycisków widnieją liczby wskazujące ile elementów danego typu występuje w projekcie.
Warstwy
Rysunek 10.Lista warstw
Komponent ten służy do zarządzania warstwami w projekcie. Widoczne są tu wszystkie zdefiniowane warstwy. Wybór bieżącej warstwy odbywa się przez naciśnięcie lewego przycisku myszy na nazwie warstwy.
Poniżej listy wyświetlana jest wysokość wybranej warstwy. Można ją modyfikować albo przy użyciu strzałek (wówczas można obserwować płynną zmianę wysokości warstwy) albo przez wpisanie odpowiedniej wartości i naciśnięciu przycisku Odswiez.
Istnieje możliwość skopiowania danej warstwy wraz z wszystkimi jej punktami (przycisk Kopiuj).
Aby dodać nową warstwę należy przycisnąć przycisk +.
Listy prymitywów
Rysunek 11.Lista trójkątów
Rysunek 12.Lista czworokątów
Nieco poniżej listy warstw znajduje się lista zdefiniowanych trójkątów i czworokątów. Po wybraniu trójkąta lub czworokąta odpowiadające mu punkty na obszarze edycji zostają wyróżnione.
Rysunek 13. Zaznaczony czworokąt
W oknie podglądu wybrany trójkąt/czworokąt jest rysowany w kolorze niebieskim.
Możliwe jest usunięcie trójkąta/czworokąta (przycisk Usun).
Edycja danego trójkąta/czworokąta jest możliwa jedynie poprzez przesuwanie danej warstwy lub przez zmianę położenia punktu należącego do niego.
Statusbar
Rysunek 14. Statusbar edytora detali
Bardzo pomocne w modelowaniu są informacje wyświetlane są na Statusbarze. Mamy tu informacje nt.:
Pixels - nad jakim pikselem formatki znajduje się wskaźnik myszy,
Coord - jakim współrzędnym OpenGL'a odpowiada miejsce pod wskaźnikiem myszy,
MODE - w jakim trybie znajduje się program,
SELECTED - nad którym punktem znajduje się wskaźnik myszy.
Modelowanie detali przy użyciu Edytora detali
Każdy obiekt zamodelowany za pomocą edytora składa się z trójkątów (GL_TRIANGLES) i czworokątów (GL_QUADS). Końcowym produktem działania edytora jest kod w C++ opisujący modelowany obiekt. Kod ten jest eksportowany do pliku tekstowego obiekt.tmp dzięki czemu możliwe jest jego "przeklejenie" kodu do projektu.
Modelowanie obiektu rozpoczynamy od zdefiniowania płaszczyzn. Na płaszczyznach umieszczamy punkty. Liczba zdefiniowanych płaszczyzn i punktów zależy tylko i wyłącznie od poziomu szczegółowości jaki chcemy w rezultacie uzyskać.
Płaszczyzny umieszczone są na różnych wysokościach. Płaszczyznę należy traktować jako przekrój poprzeczny modelowanego przedmiotu na danej wysokości. Punkty znajdujące się na płaszczyźnie powinny definiować brzegi obiektu w danym przekroju.
Po zdefiniowaniu płaszczyzn i punktów można przystąpić do definiowania trójkątów i czworokątów. Każdy trójkąt definiujemy przez wskazanie 3 kolejnych punktów. Czworokąt definiujemy przez wskazanie 4 kolejnych punktów.
Trzeba przy tym pamiętać, że kolejność definiowanych punktów ma duże znaczenie ze względu na generowanie normalnych do powierzchni. Główna zasada jest taka, że punkty figury wskazujemy w kierunku odwrotnym do ruchu wskazówek zegara.
Przed wyeksportowaniem obiektu możemy nacisnąć przycisk Generuj normalne. Wówczas dla każdego punktu każdego zdefiniowanego trójkąta i czworokąta zostanie wygenerowana normalna. Normalna dla danego punktu obliczana jest jako średnia wszystkich normalnych figur które zawierają dany punkt. Dzięki temu krawędzie i zgięcia nie są ostre lecz wygładzone.
Wektory normalne nie podlegają jednak normalizacji w edytorze detali. Dlatego wykorzystując obiekty wygenerowane przez edytor detali należy włączyć opcje GL_NORMALIZE.
Edytor pokoi
Każdy pokój występujący w projekcie jest zdefiniowany w osobnym pliku .rom i .dyn. Plik .rom jest plikiem binarnym przechowującym informacje o:
liczbie, położeniu i teksturze ścian,
liczbie, rodzaju, skali, obrocie obiektów,
liczbie i charakterystyce źródeł światła.
Plik .dyn przechowuje natomiast informacje o obiektach dynamicznych i obiektach przezroczystych, które nie mogą być umieszczone w liście wyświetlania.
Ponieważ pliki .rom i .dyn są plikami binarnymi praktycznie niemożliwa byłaby ich edycja z poziomu edytora tekstu np. notatnika. Nawet, gdyby pliki te miały strukturę XML'a to i tak edycja pokoju byłaby bardzo uciążliwa.
W każdym pokoju znajduje się średnio 50 obiektów (w niektórych jest ich 30, a w niektórych 150). Ręczne definiowanie dla każdego obiektu położenia, obrotu, skali byłoby bardzo żmudne i miałoby charakter metody "prób i błędów".
Aby usprawnić proces modelowania pomieszczeń powstał program Edytor pokoi.
Główne funkcje edytora:
dodawanie, usuwanie i modyfikacja ścian,
dodawanie, usuwanie i modyfikacja podłóg i sufitów,
dodawanie, usuwanie, przeskalowanie, obracanie obiektów,
podgląd wybranego obiektu w oknie OpenGL,
podgląd pokoju w OpenGL (rzut równoległy i perspektywiczny),
generowanie plików z obiektami dynamicznymi,
możliwość definiowania źródeł światła dla pomieszczenia,
możliwość dodawania i definiowania tekstur dla ścian.
Niewątpliwie jedną z większych zalet edytora jest możliwość podglądu pokoju w OpenGLu. Dzięki temu każda zmiana w położeniu obiektu lub ściany jest od razu widoczna. Pociąga to za sobą ogromną oszczędność czasu, gdyż nie trzeba za każdym razem od nowa uruchamiać projektu, żeby zobaczyć, czy np. szafa stoi na dobrym miejscu.
Edytor pokoi nie wprowadza żadnego nowego formaty pliku. Obsługuje on pliki .rom i .dyn.
Interfejs Edytora pokoi
Rysunek 15. Interfejs Edytora pokoi
Tworzenie, otwieranie i zapisywanie
Rysunek 16. Funkcje podstawowe
Nowy pokój - tworzy nowy pokój o wskazanej nazwie;
Otworz - otwiera pokój z pliku;
Zapisz - zapisuje pokój do pliku;
Wybór obiektu
Rysunek 17.Wybieranie obiektu
Do wyboru obiektu służą dwie rozwijane listy. Pierwsza z list przechowuje typ obiektu. Po wybraniu typu obiektu lista druga zostaje wypełniona rodzajami obiektów wybranego typu.
Jeżeli mamy włączony podgląd obiektu, to zostanie on uaktualniony zaraz po zmianie wyboru. Wybrany obiekt staje się obiektem bieżącym i może być umieszczony w pokoju. Również w momencie przeglądania obiektów znajdujących się w pokoju listy przyjmują wartości odpowiadające aktualnie zaznaczonego obiektu.
Dodawanie elementów pokoju
Rysunek 18. Dodawanie elementów
Do dodawania elementów do pokoju służą trzy powyżej przedstawione przyciski.
Dodawanie obiektu
Aby dodać obiekt klikamy na przycisk Dodaj obiekt, a następnie wskazujemy miejsce gdzie chcemy aby obiekt był dodany. Po naciśnięciu lewego klawisza myszy obiekt zostanie dodany we wskazanym miejscu.
Dodawanie ściany
Aby dodać ścianę klikamy na przycisk Dodaj sciane, a następnie wskazujemy początek i koniec ściany. Początek ściany wskazujemy przez naciśnięcie i przytrzymanie lewego klawisza myszki. Następnie ustawiamy mysz na końcu ściany i puszczamy lewy klawisz. Ściana zostanie dodana.
Należy jednak pamiętać, że obowiązuje zasada ukrywania niewidocznych ścian. Ściany należy więc definiować w kierunku odwrotnym do ruchu wskazówek zegara.
Dodawanie podłogi
Aby dodać podłogę (lub sufit) wybieramy przycisk Dodaj podloge a następnie wskazujemy 4 punkty definiujące podłogę. Każde lewe kliknięcie oznacza 1 punkt. Punkty podłogi również definiujemy w kolejności odwrotnej do ruchu wskazówek zegara.
Odwrotnie ma się sytuacja z sufitem. Tutaj ścianą widoczną powinna być druga ściana aniżeli w przypadku podłogi. Dlatego punkty sufitu definiujemy w kolejności zgodnej z ruchem wskazówek zegara.
Opcje podglądu pokoju
Rysunek 19. Opcje podglądu
Pokaż wybrany obiekt - po wybraniu tej opcji zostanie utworzone okno OpenGL z wyświetlonym aktualnie wybranym obiektem. Okno powinno się zamykać poprzez ponowne przełączenie przełącznika, a nie przez klikanie na "X", gdyż może to spowodować niestabilną pracę edytora.
Podgląd w OpenGL - po wybraniu tej opcji zostanie utworzone okno OpenGL z podglądem pokoju. Nowe okno podglądu zostanie nakryte starym oknem z ustawionym wysokim współczynnikiem przezroczystości. Dzięki temu możliwe jest ustawianie obiektów z jednoczesnym podglądem. Nie można jednak przesuwać okien.
Rysunek 20.Podgląd pokoju w OpenGL
Przezroczystość - po wybraniu tej opcji formatka z podglądem pokoju staje się przezroczysta. Zastosowanie tej opcji jest następujące: w momencie włączenia podglądu w OpenGL formatka automatycznie staje się przezroczysta. Podgląd pokoju w OpenGLu jest bardzo funkcjonalny, nie jest jednak pozbawiony wad. Główną wadą jest to, że nie widać numeracji obiektów. Jeżeli więc chcemy zmodyfikować jakiś obiekt możemy wyłączyć przezroczystość i powrócić do starego podglądu pokoju.
Rysunek 21.Stary podgląd pokoju
Rysunek 22. Podgląd pokoju
Wszystkie obiekty w podglądzie reprezentowane są przez okręgi z wpisanym w środku numerem. Numer ten służy do szybkiej identyfikacji obiektu na liście.
Okrąg czerwony oznacza aktualnie wybrany obiekt.
Okrąg żółty oznacza obiekt dynamiczny.
Ściany reprezentowane są przez niebieskie linie.
Podłogi i sufity przez niebieskie koperty.
Wybrana ściana i podłoga, podobnie jak wybrany obiekt, rysowana jest w kolorze czerwonym.
Nawigacja po pokoju
Rysunek 23.Przyciski nawigacyjne
Przyciski nawigacyjne służą do poruszania się po pokoju i do przybliżania/oddalania.
Edycja parametrów obiektów i ścian
Rysunek 24.Lista obiektów
Wszystkie obiekty i ściany występujące w pokoju są zebrane w przewijanej liście. Wybrany element listy jest malowany dla odróżnienia w kolorze czerwonym.
W zależności od tego czy wybrany zostało obiekt, czy ściana dostępne są różne opcje.
Parametry obiektu
W przypadku wyboru obiektu dostępne są następujące opcje:
Rysunek 25.Opcje dla obiektu
xPos, yPos, zPos - współrzędne X, Y, Z obiektu;
xScale, yScale, zScale - parametry przeskalowania obiektu;
Kat, xWektor, yWektor, zWektor - kąt i wektor obrotu obiektu;
Po naciśnięciu przycisku Free move obiekt zostaje "przyklejony" do wskaźnika myszy i możliwe jest jego swobodne przesuwanie po pokoju. Aby upuścić obiekt należy nacisnąć lewy klawisz myszy.
Aby skopiować obiekt należy nacisnąć przycisk Kopiuj.
Po zmianie któregokolwiek parametru trzeba zatwierdzić zmiany przez naciśnięcie przycisku Zapisz. Zasada ta nie dotyczy jednak opcji Free move.
W celu usunięcia obiektu należy nacisnąć przycisk Usun.
Parametry ściany
W przypadku, gdy wybranym obiektem jest ściana, wówczas opcje wyglądają następująco:
Rysunek 26.Opcje dla ściany
Widzimy tu współrzędne X, Y i Z dla 4 punktów opisujących ścianę P1, P2, P3, P4. Poza tym dla ściany określony jest kolor RGB.
Przełącznik Collision detection mówi o tym, czy dana ściana ma być brana pod uwagę przy detekcji kolizji, czy ma być pomijana.
Opcja tekstura pozwala zmieniać teksturę ściany. W rozwijanej liście dostępne są wszystkie tekstury wykorzystane do tej pory w danym pokoju. Jeżeli chcemy dodać nową teksturę należy skorzystać z przycisku +.
Poniżej wyświetlany jest obraz bieżącej tekstury. Obok niego po prawej stronie i na dole są dwie wartości. Wskazują one ile razy dana tekstura ma być odłożona na ścianie w poziomie i pionie. W tym przypadku tekstura zostanie odłożona 10 razy w pionie i raz w poziomie.
Podobnie jak miało to miejsce przy edycji obiektu, wszelkie zmiany należy zatwierdzić przyciskiem Zapisz parametry.
Przycisk Usun służy do usuwania ściany.
Definiowanie źródeł światła
Rysunek 27.Definiowanie światła
Aby zdefiniować źródła światła najprościej skorzystać z przycisku Definiuj swiatla. Po naciśnięciu na niego pokaże się nowy dialog, który umożliwia definiowanie i edycję źródeł światła w pokoju.
Rysunek 28. Dialog definiujący światła
Dla każdego z ośmiu źródeł światła możemy zdefiniować jego położenie, a także parametry specyficzne dla światła, czyli: światło otoczenia, światło rozproszone, światło odbicia. Możemy również określić czy światło jest kierunkowe, czy nie i ew. określić jego właściwości. Możemy również włączyć i wyłączyć wybrane źródła światła.
Informacja o źródłach światła zostaje dopisana na końcu pliku .rom.
Edytor poziomów
Każdy poziom składa się z 1 lub więcej pokoi. Plik zawierający opis poziomu ma rozszerzenie .lev i składa się ze zbioru rekordów. Każdy z rekordów określa nazwę pliku opisującego pokój i przesunięcie pokoju względem punktu (0, 0).
Istnieje założenie, że wszystkie pokoje na danym poziomie znajdują się na jednakowej wysokości, dlatego przy określaniu przesunięcia pokoju posługujemy się tylko 2 współrzędnymi.
Edytor poziomów jest w gruncie rzeczy bardzo prostym programem. Powstał po to, aby umożliwić generowanie binarnych plików .lev.
Interfejs Edytora poziomów
Rysunek 29. Interfejs Edytora pokoi
Po lewej stronie Edytora pokoi znajduje się lista zdefiniowanych pokoi dla danego poziomu. Po wybraniu któregoś z pokoi po prawej stronie okna wyświetlane są informacje o jego przesunięciu.
Możliwa jest zmiana przesunięcia pokoju oraz nazwy pliku opisującego dany pokój. Każdą zmianę należy zatwierdzić przyciskiem Zapisz.
Aby dodać nowy pokój do poziomu należy uzupełnić pola wpisując nazwę pliku opisującego pokój oraz jego przesunięcie a następnie nacisnąć przycisk Dodaj.
Do zapisywania i wczytywania poziomu służą przyciski Zapisz poziom i Otworz poziom.
Inspektor obiektów
Kolejnym narzędziem wspomagającym modelowanie pokoi i poziomów jest Inspektor obiektów.
Podczas modelowania pokoi pojawił się następujący problem: ponieważ różne obiekty były modelowane przez różne osoby, więc po wstawieniu obiektów do pokoju okazało się, że niektóre z nich wymagają przeskalowania. Co więcej - okazało się, że niektóre obiekty mogą być wielokrotnie wykorzystane, tylko muszą być np. inaczej przeskalowane.
Oczywiste jest, że przeskalowanie obiektu najlepiej robić na poziomie kodu, gdyż wówczas znajduje się ono w obrębie listy wyświetlania danego obiektu. Zyskuje się dzięki temu wydajność.
Alternatywnym sposobem jest skalowanie obiektu przez zapisanie w jego parametrach na poziomie pliku .rom informacji o skali.
Dla obiektów które występują w różnej skali jedynym rozwiązaniem jest podejście 2. Natomiast obiekty które w obrębie całego projektu są identycznych rozmiarów sensowne jest wykorzystanie podejścia 1-go.
Jak się okazało, po stworzeniu kilku pokoi, bardzo łatwo stracić rachubę w tym, które obiekty zostały już wykorzystane, a które jeszcze nie. Oczywistym jest, że jeżeli jakiś obiekt został już umieszczony w którymś z pokoi to wszelkie jego modyfikacje będą miały wpływ na wszystkie pokoje w których się on znajduje.
Aby usprawnić ten proces powstał Inspektor obiektów. Jest to narzędzie, które sprawdza w jakich pokojach jakie obiekty występują. Co ważne, wyniki są grupowane po typie obiektu a nie numerze pokoju. Dzięki temu łatwo i szybko można znaleźć odpowiedź na szukane pytanie.
Plik wykonywalny programu należy przed uruchomieniem umieścić w katalogu zawierającego wszystkie pokoje i poziomy. Dopiero wtedy można go uruchomić.
Interfejs Inspektora obiektów
Rysunek 30. Interfejs Inspektora obiektów
Po naciśnięciu przycisku Szukaj obszar tekstowy zostaje wypełniony wynikami poszukiwań. Wyniki prezentowane są w następującej postaci:
rodzaj obiektu, typ obiektu, pokoje, w których występuje ...
Wynika stąd, że każdemu obiektowi odpowiada 1 linijka tekstu.
Program znalazł szerokie zastosowanie w projekcie.
TextEditor
TextEditor powstał z myślą o edycji tekstów używanych w programie.
Elementy tekstowe (lewy i prawy) służą do edycji tekstów. W lewym widzimy wybrany tekst w języku polskim, w prawym w języku angielskim (jedyne dotychczasowo obsługiwane języki):
Funkcje przycisków:
Load - wczytanie danych z pliku. UWAGA! Po naciśnięciu przycisku pojawi się okienko dialogowe z prośbą o wybranie pliku. Wybrany plik będzie otworzony w lewym elemencie tekstowym. W prawym zostanie odtworzony plik, którego nazwa będzie miała postać pliku z lewej strony z zamienionymi literkami (5 i 6 od końca) na „en”.
Save - zapisanie zmian do pliku.
Save field - zapisanie wprowadzonych w danym rekordzie zmian do pamięci (nie jest to czynione automatycznie, zapisywane są teksty z obu pól tekstowych).
Umieszczony u dołu okna suwak służy do przemieszczania się pomiędzy rekordami tekstowymi.
Trip Editor v.2.0
Trip Editor powstał z myślą o edytorze elementów znajdujących się w pliku ld, czyli: drzwi, punkty informacyjne i punkty wycieczki.
Program składa się z kilku części: Pasek główny, Mapa, Edytor drzwi, Edytor Punktów Wycieczki oraz Edytor Punktów Informacyjnych.
Pasek główny
1 - nowy plik ld (opcja nieaktywna)
2 - otwórz plik ld
3 - zapisz plik ld
4 - wyświetl drzwi na mapie
5 - odśwież mapę
6 - pokaż punkty informacyjne na mapie
7 - pokaż punkty wycieczki na mapie (przycisk nieaktywny; opcja aktywuje się po wybraniu dowolnego punktu wycieczki).
Mapa
Wyświetlana jest tutaj mapa edytowanego poziomu.
UWAGA! Istnieje tutaj poważne ograniczenie: mapa ta nie jest generowana podczas wczytywania pliku, lecz wczytywana z pliku wyeksportowanego z obiektu klasy CCd (zapisana na dysku mapa kolizji).
Podczas przesuwania kursora nad mapą możemy na pasku stanu odczytać współrzędne danego punktu w przeliczeniu na współrzędne bezwzględne stosowane w projekcie.
Suwak pod mapą umożliwia przesuwanie mapy.
Edytor drzwi
Edytor drzwi umożliwia edycje wszystkich parametrów umiejscowienia drzwi.
Wszystkie drzwi wyświetlone są na liście. Zaznaczenie dowolnego elementu listy powoduje wyświetlenie jego danych poniżej przycisków sterujących.
Dodawanie drzwi
Przycisk „Add” powoduje dodanie nowych drzwi o wartościach wpisanych w odpowiednie pola tekstowe.
Modyfikowanie parametrów drzwi
Przycisk „Update” powoduje nadpisanie danych zaznaczonego elementu danymi z pól tekstowych.
Usuwanie drzwi
Przycisk „Remove” powoduje usunięcie zaznaczonych drzwi.
UWAGA! Ponieważ drzwi zostały zdefiniowane przed powstaniem edytora, możliwości dodawania i usuwania drzwi nie zostały należycie przetestowane.
Przeglądanie drzwi na mapie
Gdy włączone zaznaczanie drzwi na mapie, kliknięcie na dowolnym elemencie listy powoduje podświetlenie go na mapie.
Edytor Punktów Wycieczki
Edytor punktów wycieczki powstał z myślą tworzeniu i edycji punktów wycieczki.
Edycja punktów wycieczki
Po wybraniu dowolnego elementu z listy wyświetlającej wszystkie punkty dany element zostaje podświetlony na mapie, a w polach tekstowych u dołu okna widać jego dane.
Modyfikowanie punktów wycieczki
Aby zmodyfikować parametry punktu wycieczki należy wybrać dany element z listy, wprowadzić nowe dane w pola tekstowe, a następnie nacisnąć przycisk „Update”.
Dodawanie punktów wycieczki
Aby dodać nowy punkt wycieczki, należy wybrać z listy odpowiedni element, a następnie kliknąć na przycisk „Insert before” (wstaw przed elementem) lub „Insert after” (wstaw po elemencie) (gdy na liście nie ma żadnego elementu, należy wybrać przycisk „Insert before”). Następnie na mapie należy nacisnąć lewy przycisk myszki w miejscu, gdzie chcemy ustawić nowy punkt, a następnie (nie puszczając lewego przycisku myszki) wskazać kąt, pod którym obserwator ma patrzeć na otaczający go wirtualny świat. Kąt będzie ustalony automatycznie: punkt wycieczki będzie w miejscu, gdzie naciśnięty zostanie lewy przycisk myszki, a obserwator będzie patrzył w kierunku punktu, w którym puścimy lewy przycisk myszki. W międzyczasie na pasku stanu będzie wyświetlany kąt, pod którym obserwator będzie patrzył:
Następnie wyświetli się okienko, w którym należy podać pozostałe dane punktu:
Parametry X i Z określają współrzędne punktu. Angle oznacza kąt patrzenia. Time oznacza czas (w klatkach, później pomnożony przez stałą zdefiniowaną w pliku constant.h) w jakim do punktu należy dojść od punktu poprzedniego, Action oznacza akcję przeprowadzaną w tym punkcie (-1 - nic, 1 - otwórz drzwi, 2 - zamknij drzwi, 3 - wyświetl punkt informacyjny, 4 - zakończ wycieczkę), a Object interpretowany jest jako indeks obiektu, na którym ma być przeprowadzona dana operacja.
Po kliknięciu na przycisk „OK.” punkt zostanie dodany.
Usuwanie punktu wycieczki
Aby usunąć punkt wycieczki należy wybrać go z listy i kliknąć na przycisk „Remove”.
Edytor Punktów Informacyjnych
Edytor Punktów Informacyjnych powstał z myślą o ich tworzeniu i edycji.
Dodawanie punktów informacyjnych
Aby dodać punkt informacyjny należy kliknąć na przycisku „Add”, a następnie na mapie, w punkcie, w którym ma znajdować się nowy punkt. Dalej będzie on utworzony automatycznie, bez żadnych ekranów.
Usuwanie punktów informacyjnych
Aby usunąć punkt informacyjny wybrać go z listy, a następnie kliknąć na przycisku „Remove”.
Zmiana położenia punktów informacyjnych
Aby zmienić położenie punktu informacyjnego należy wybrać go z listy, kliknąć na przycisku „Location”, a następnie na mapie w miejscu, gdzie ma się on teraz znajdować.
Edycja i modyfikacje punktów informacyjnych
Aby edytować punkt informacyjny należy wybrać go z listy.
Pierwszą rzeczą do modyfikacji jest określenie, czy punkt jest dostępny w trybie zwiedzania. Jeżeli zaznaczone będzie pole „Trip Only”, to punkt będzie niedostępny podczas zwiedzania.
Aby edytować bardziej zaawansowane właściwości należy wybrać przycisk z listy i kliknąć na przycisku „Edit” (lub dwukrotnie kliknąć na obiekt na liście). Ukaże się nam wtedy okienko edycji Ekranów.
Możemy teraz zarządzać ekranami. Klawisze „Insert before” i „Insert After” służą odpowiednio do wstawiania ekranu przed i po wybranym elemencie (działają identycznie, gdy punkt nie ma żadnych ekranów). „Exit” powoduje zamknięcie okna (i zapamiętanie aktualnego stanu danych), „Remove” usuwa wybrany ekran, a „Edit” edytuje wybrany ekran, co powoduje otwarcie kolejnego okienka:
Jest okienko edycji ramek. Ramki dodajemy przyciskiem „Add”, a usuwamy przyciskiem „Remove”. Przycisk „Exit” powoduje zamknięcie okna i zapamiętanie wszystkich danych. Pole „Delay Time” określa czas wyświetlania ekranu w sekundach. Aby zmienić wartość, należy wpisać nową wartość i kliknąć na przycisku „Update”.
Przycisk „Preview” docelowo ma generować podgląd edytowanego ekranu, jednakże nie jest on oprogramowany.
Wybierając element z listy otrzymujemy jego parametry.
Typ ramki określamy wybierając „Text” (tekst, okno 1), „Image” (obrazek, okno 2) lub „Audio” (dźwięk, okno 3).
Gdy wybierzemy ramkę tekstową, należy podać jej współrzędne na ekranie (X, Y) oraz numer (indeks) tekstu (teksty możemy przeglądać przy użyciu narzędzia TextEditor).
Po wybraniu ramki z obrazkiem należy podać współrzędne ramki na ekranie (X, Y), wielkość obrazka w pixelach (jest to wielkość obrazka po wyświetleniu, a nie zapisanego w pliku) (wielkość podajemy przez podanie szerokośći („width”) oraz wysokości („height)), a następnie nazwy obrazka (bez ścieżki dostępu; obrazek jest traktowany jako tekstura i powinien się znaleźć w odpowiednich katalogach z teksturami).
Gdy wybierzemy ramkę dźwiękową należy podać tylko nazwę pliku do odtworzenie (również bez ścieżki dostępu; plik z dźwiękiem powinien być w odpowiednim podkatalogu katalogu „data\audio”).
Na koniec należy kliknąć na przycisku „Update frame” aby zapamiętać wprowadzone zmiany.
Uwagi dotyczące użytkowania edytorów
Należy pamiętać, iż dodawanie i usuwanie obiektów powoduje przeindeksowanie pozostałych, co może prowadzić do niepoprawnego działania powiązanych ze sobą obiektów.
czerwonej książki - strona 207 rysunek 7.8
czerwonej książki - strona 219 rysunek 7.22
Metoda biblioteki glut
Wirtualny model Instytutu Informatyki w grafice trójwymiarowej
44/94