Dokumenta Cj A
Transcript of Dokumenta Cj A
Wrocław, 16 czerwca 2011
Jakub Jędryszek, 171055
Mateusz Korżel, 171133
Wiktor Ławski, 171062
Bartłomiej Piekarski 171160
Kamil Pokładowski 170993
Michał Skuza 171172
RubiQ Cube 2011
Projekt z przedmiotu „Projekt Zespołowy”
Rok akademicki 2010/2011, kierunek: INF, specjalność: INS.
PROWADZĄCY:
Dr inż. Andrzej Kozik
Spis treści
1. Założenia projektowe ......................................................................................................2
2. Cele projektu ...................................................................................................................2
3. Szczegóły implementacyjne.............................................................................................3
4. Zasady gry..................................................................................................................... 12
5. Elementy dodatkowe ..................................................................................................... 13
6. Interfejs użytkownika .................................................................................................... 14
Bibliografia .......................................................................................................................... 15
2
1. Założenia projektowe
Projekt miał na celu stworzenie symulacji gry, w której gracz układa kostkę Rubika.
Zakładano stworzenie dwóch wersji gry:
gracz steruje za pomocą myszy i klawiatury,
gracz steruje za pomocą urządzenia Kinect.
Ponadto gra miała przechowywać najlepsze wyniki graczy, które będą pamiętane
lokalnie (w pliku XML) oraz globalnie – w centralnej bazie danych, gdzie będą wyniki
różnych graczy. Każdy z graczy powinien mieć możliwość utworzenia własnego profilu – na
jednym komputerze można grać na różnych profilach.
Prace nad projektem zostały podzielone na 3 moduły:
interfejs (GUI) i baza danych użytkowników wraz ze stroną WWW projektu
(Jakub Jędryszek i Mateusz Korżel),
obsługa Kinecta (Wiktor Ławski i Bartłomiej Piekarski),
przygotowanie funkcjonalnego modelu kostki Rubika (Kamil Pokładowski
i Michał Skuza).
2. Cele projektu
Celami projektu były m.in.:
nabycie praktycznych umiejętności programowania z wykorzystaniem
Windows Presentation Foundation (WPF),
zapoznanie się z językiem opisu interfejsu użytkownika XAML (eXtensible
Application Markup Language),
opanowanie komunikacji z bazą danych, z poziomu aplikacji bazującej na
.NET Framework 4.0,
zapoznanie się z urządzeniem Kinect oraz bibliotekami do komunikacji z nim,
poznanie sposobów modelowania grafiki 3D z wykorzystaniem WPF,
3
udoskonalenie umiejętności pracy w grupie i zaznajomienie z narzędziami ją
wpierającymi.
3. Szczegóły implementacyjne
3.1 Graficzny model kostki
Do uzyskania graficznej prezentacji kostki Rubika posłużono się silnikiem graficznym
WPF, bazującym na .NET 3 i wchodzącym w skład WinFX. WPF daje możliwość integracji
m.in. interfejsu użytkownika, dokumentów, grafiki zarówno 2D jaki i 3D, multimediów itd.
Na potrzeby niniejszego projektu wykorzystano interfejs użytkownika, a także elementy
grafiki trójwymiarowej. Ponadto interfejs programowania aplikacji WPF bazuje na języku
XML, a dokładniej na jego zoptymalizowanej części XAML, służącej do opisu bogatych
wizualnych interfejsów, a tym samym pozwala na odseparowanie warstwy prezentacji i logiki
biznesowej. Graficzna część GUI wykorzystuje grafikę wektorową budowaną z użyciem
akceleratorów grafiki 3D i efektów graficznych udostępnianych przez WGF (Windows
Graphics Foundation).
Model kostki został stworzony przy użyciu 27 modeli sześcianów, gdzie każdy z nich
znajduje się w 3 różnych (określonych) płaszczyznach przestrzeni, które wyznaczają ściany
obracanej kostki. W celu uzyskania w/w efektu stworzono dwie klasy:
Cube – klasa odzwierciedlająca sześcian w przestrzeni, tzn. jego graficzną
prezentację, a w szczególności jego umiejscowienie w układzie odniesienia,
rozmiar, kolory ścian;
RubikCube – klasa zawierająca tablicę 27 obiektów klasy Cube,
reprezentujących całą kostkę oraz metody służące m.in. do obrotów całej
kostki lub wybranych ścian.
3.1.1 Klasa Cube
Klasa Cube służy do graficznego przedstawienia sześcianu w układzie współrzędnych
XYZ. Podstawą do jego stworzenia było możliwość rysowania siatki, która reprezentuje
powierzchnię umiejscowioną w przestrzeni trójwymiarowej. W szczególności taką
powierzchnię można jednoznacznie zdefiniować przy użyciu współrzędnych 3 punktów, co
oznacza, że w/w siatka może być złożona z trójkątów. Dla dużych i złożonych powierzchni
4
odpowiednia ilość takich trójkątów stanowi dobre przybliżenie szukanych kształtów.
Prawidłowa reprezentacja siatki w przestrzeni składa się z :
wierzchołków trójkątów, składających się na siatkę;
wektorów normalnych do zdefiniowanych trójkątów, które stanowią iloczyny
wektorowe (rys. 1), które wykorzystywane są m.in. do prawidłowego
oświetlenia trójkąta
Rysunek 1 Iloczyn wektorowy dla jednego trójkąta siatki
określenia barwy światła rozproszenia dla powierzchni oświetlanej
Model sześcianu składa się z 12 trójkątów opisujących jego powierzchnię (po dwa na
każdą ścianę), dla których osobno obliczane są wektory normalne (przy użyciu iloczynu
wektorowego).
Konstruktor klasy Cube przyjmuje w argumentach pozycję jednego punktu sześcianu
– wierzchołek nr 1 (rys. 2), rozmiar boku oraz 6 kolorów określających ściany bryły:
public szescian(Point3D position, double rozmiar, Color c_front, Color c_back, Color c_left, Color c_right, Color c_down, Color c_up)
5
Rysunek 2 Graficzne reprezentacja sześcianu w przestrzeni
Do najważniejszych metod tej klasy należą:
public void transformByMatrix(Matrix3D matrix) – metoda odpowiedzialna za
transformację wszystkich punktów sześcianu (8 wierzchołków) w przestrzeni przez
macierz podawaną w argumencie. Jest ona niezbędna przy takich transformacjach jak
obroty całej kostki lub wybranych ścian oraz przesunięcia o wektor;
public Color GetWallColor(int wallNumber) – metoda zwracająca kolor ściany
sześcianu podany w argumencie. Wykorzystanie tej funkcji jest konieczne przy
sprawdzaniu czy model kostki Rubika jest rozwiązany, tzn. czy kolory na każdej
powierzchni całej kostki są jednakowe;
public Model3DGroup konstruuj_szescian() – wymaga się użycia tej metody po
każdej transformacji wykonanej na punktach sześcianów w celu uzyskania modelu
sześcianu dla nowo wyznaczonych współrzędnych (tworzenie siatki trójkątów oraz
wektorów normalnych dla niej);
public void correctPoint(int nr, Point3D newPoint) – metoda wykorzystywana do
manualnego wpisania nowych współrzędnych dla punktu określonego w argumencie.
6
3.1.2 Klasa RubikCube
Dzięki klasie RubikCube jest możliwe przedstawienie kostki Rubika w przestrzeni
trójwymiarowej. W tym celu klasa przechowuje tablicę 27 obiektów klasy Cube, które
odpowiednio umiejscowione w układzie współrzędnych tworzą model całej kostki.
Rysunek 3 Kostka Rubika złożona z 27 sześcianów
Domyślne ustawienia pozycji obserwatora to (60, 50 , 0), co oznacza, że znajduje się
ona nad osią OX, można zmienić przy użyciu metody SetCamera(). Jednak rekomendowane
jest pozostawienie ustawień predefiniowanych. W przypadku ich zmiany, zalecane jest, aby
zmienić również kierunek padania światła na odwrotny do przyjętej pozycji (np. dla pozycji
(10,20,30) odpowiedni kierunek to (-10, -20, -30)). Niespełnienie tej zasady może skutkować
niewłaściwym wyświetlaniem kolorów kostki.
7
RubikCube wymaga, aby w konstruktorze przekazać referencję do obiektu klasy
ViewPort3D, na którym cała scena będzie rysowana.
Metody klasy RubikCube można podzielić na 4 zasadnicze grupy:
a) Metody generalne
public void przerysujScene() – metoda „czyści” scenę, a następnie wykorzystując
bieżące współrzędne wszystkich obiektów klasy Cube.cs na nowo przerysowuje całą
scenę;
public void SetCamera
public void zmienKierunekSwiatla
b) Metody obrotów całej kostki
public void CubeRotation(int kat, bool horizontal) – metoda odpowiedzialna za obrót
całego modelu kostki. Pierwszy parametr funkcji określa kąt (w stopniach) obrotu,
natomiast drugi określa jeden z dwóch możliwych obrotów: względem osi OY oraz
względem osi OZ;
Rysunek 4 Osie obrotu kostki Rubika
8
dwie metody prywatne odpowiedzialne za faktyczną zmianę położenia wierzchołków.
Jedna z nich odpowiada za stworzenie macierzy transformacji oraz zastosowanie jej,
druga wykonuje animację obrotu o ten sam kąt, dając efekt płynnej zmiany
położenia.
c) Metody obrotów ścian
public void WallFocus(int SetNumber) – dla uzyskania obrotu wszystkie ściany kostki
Rubika wymagane było zdefiniowanie 9 płaszczyzn obrotu, z których każda jest
odpowiedzialna za obrót 9 bazowych sześcianów stworzono tablice przechowujące
numery określonych obiektów klasy Cube. WallFocus wyróżnia wskazaną
w argumencie ścianę (1-9), gdzie pierwsza trójka oznacza ściany od lewego „czoła”
kostki do tyłu, druga trójka od prawego „czoła”, pozostała odpowiada za poziome
warstwy.
Rysunek 5 Wyróżnienie ścian 1-3
9
Rysunek 6 Wyróżnienie ścian 4-6
Rysunek 7 Wyróżnienie ścian 7-9
Samo wyróżnienie ściany polega na przesunięciu 8 sześcianów o pewien, określony
wektor względem środka rozpatrywanej ściany. W ten sposób uzyskiwany jest efekt
„rozszerzania się” sześcianów, będących w danej płaszczyźnie.
public void obroc_sciane(int nr_sciany, int kat) oraz public void
obroc_scianeAnimation (int nr_sciany, int kat) – metody odpowiedzialne za
obracanie ściany podanej w argumencie o wskazany kąt. Obie metody powinny być
wywoływane jednocześnie z identycznymi argumentami, ponieważ dokonuje
kalkulacji nowego położenia punktów, natomiast druga odpowiada za samą
animację.
10
Rysunek 8 Przykłady obrotów wyróżnionych ścian
d) Metody sprawdzające
public bool isSolved() – metoda sprawdzająca, czy kostka jest ułożona
prawidłowo, tzn. wszystkie kolory są na właściwych miejscach. Zwraca wartość
true w przypadku zgodności oraz false, kiedy kostka pozostaje „nieułożona”;
public int ReturnActiveWall() – metoda zwracająca numer aktywnej (wyróżnionej)
ściany;
public void SetAnimationTime(int miliseconds) – ustawia czas animacji
przewidziany na jednorazowy obrót całej kostki, bądź wybranej ściany;
public int repairCube() – kluczowa funkcja, która naprawia błędy zaokrągleń
nakładane przez wielokrotne wywoływanie wszystkich obrotów na kostce.
Rekomendowane jest, aby używać jej przy każdym obrocie kostki. Zwraca wartość
1, gdy dokonano poprawy współrzędnych oraz 0, gdy jej nie dokonano.
11
3.2 Obsługa kinecta
Do obsługi Kinecta wykorzystana została biblioteka openNI.net. Przygotowany został
także system logowania przy pomocy bibliotek NLog.
Tworząc okno rozgrywki, pierwszym elementem jest pobranie referencji do klasy
odpowiadającej za zapis do logów, przy pomocy statycznej metody
GetCurrentClassLogger().
Aby móc wyświetlać obraz z Kinecta oraz rysować na nim punkty identyfikujące
poszczególne części ciała, należało w pliku XAML użyć znaczników typu Canvas oraz
Image. Tworząc okno rozgrywki tworzony jest obiekt klasy zaprojektowanej specjalnie na
potrzeby tego projektu – GameController. W konstruktorze przekazywana jest do niej
referencja do głównego okna, żeby móc z niej manipulować obiektami typu Canvas oraz
Image. Do obsługi biblioteki openNI jest wymagany obiekt klasy Context, do którego
przekazywana jest nazwa pliku XML z odpowiednimi ustawieniami. Na podstawie
utworzonego obiektu klasy Context można utworzyć DepthGenerator i ImageGenerator. Aby
skorzystać z możliwości tej biblioteki, należało dodatkowo utworzyć obiekty klas
UserGenerator, SkeletonCapability i PoseDetectionCapability. Do rozróżniania
poszczególnych pozycji, wymagane jest jeszcze utworzenie „słownika stawów”. Po
dokonaniu preferowanych ustawień, wywoływana jest metoda StartGenerating() dla obiektu
klasy UserGenerator. Aby uzyskać wrażenie równoległości działań, stworzone zostały
3 wątki, które odpowiadały za przerysowywanie sceny, wybór ścian (poprzez odpowiednie
pozycje) i poruszanie kostką (poprzez analizę ruchu).
Aby móc wykorzystywać w późniejszej analizie dane części ciała, należy je dodać do
„słownika stawów” - metoda GetJoints(). W jej wnętrzu należy wywołać metodę GetJoint()
dla odpowiednich części ciała z 2 parametrami – numer użytkownika docelowego oraz jedna
z wartości typu wyliczeniowego SkeletonJoint. Docelowo typ ten przechowuje dużo części
ludzkiego ciała, ale biblioteka jeszcze nie implementuje ich obsługi.
Przerysowywanie obrazu polega na usunięciu z nakładki na obrazie z Kinecta
wszystkich dodatkowych elementów, a następnie dodanie ich na nowo w zaktualizowanych
pozycjach – na potrzeby projektu została wykorzystana jedynie elipsa o tych samych
atrybutach szerokości i wysokości (czyli koło) oraz kostka, która została opisana wcześniej.
Współrzędne XYZ danej części ciała w przestrzeni można odczytywać na bieżąco z tablicy
12
aktualizowanej automatycznie przez bibliotekę openNI. Aby nie doszło do zjawiska
wyścigów wątków, obraz wideo z Kinecta należy zablokować, pobrać pojedynczą bitmapę,
a następnie go odblokować.
Wątek śledzący ruch działa w pętli gorącego czekania z wartością Sleep równą
100 milisekund. Gdy przemieszczenie w osi poziomej lub pionowej przekroczy wartość
progową, wywoływana jest metoda obracająca aktywną ścianę.
Wątek wyboru odpowiedniej ściany również działa w pętli gorącego czekania, ale
ponieważ nie jest tu badane przemieszczenie, to aktualny stan może być sprawdzany znacznie
częściej – 22 milisekundy. W pierwszej kolejności sprawdzane jest czy użytkownik chce
odznaczyć daną ścianę. Ponieważ biblioteka wykorzystująca Kinecta nie jest w swoim
działaniu bezbłędna (szczególnie podczas analizy pozycji użytkowników w luźnych strojach),
należało wprowadzić pewien margines błędu. Marginesy błędów są wykorzystywane podczas
analizy każdej pozycji, gdy użytkownik powinien uzyskać przynajmniej jedną poziomą linię.
System posiada obecnie wewnętrzne ograniczenie na maksymalną liczbę
użytkowników. Nie został przygotowany tryb multiplayer, więc maksymalna liczba
wykrytych użytkowników (obecnie 4) może jednocześnie operować na tej samej kostce
Rubika.
Obsługa Kinecta była utrudniona, ponieważ nie pojawiło się jeszcze SDK Microsoftu
do jego obsługi. Poza tym interfejs należy obsługiwać przy pomocy myszki bo dostępność
urządzenia nie pozwoliła na zaimplementowanie jego obsługi przy pomocy Kinecta.
4. Zasady gry
Zabawa kostką polega na takim ułożeniu kwadratów, aby na każdej ścianie wszystkie
posiadały jeden kolor. Składa się ona z 26 sześcianów i przegubu umieszczonego w środku.
Przegub ten umożliwia każdej z zewnętrznych warstw kostki obrót wokół osi prostopadłej do
danej warstwy i przechodzącej przez środek kostki.
Liczba kombinacji różnych ułożeń kostki 3×3×3 wynosi 43 252 003 274 489 856 000
(ponad 43 tryliony).
13
5. Elementy dodatkowe
5.1 Zarządzanie profilami graczy
Oprócz samej gry został stworzony system zarządzania profilami graczy. Zarządzanie
profilami. Do zarządzania profilami wykorzystywany jest lokalny plik 'profiles.xml', który
zawiera utworzone profile przez użytkowników gry. Budowa pliku zawierającego profile jest
następująca:
1) Węzeł główny o nazwie 'Users' zawierający utworzone profile użytkowników.
Dodatkowo posiada on 2 atrybuty: 'last' - atrybut przechowujący login ostatnio
zalogowanego użytkownika oraz 'level' - atrybut przechowujący ostatnio
ustawiony poziom gry.
2) Dalej w hierarchi definiowane są poszczególne profile. Każdy z profili składa się
z trzech węzłów - jednego głównego 'User' określającego definicję profilu oraz
dwóch podrzędnych węzłów 'UserName' oraz 'Password' zawierających
informację danych użytkownika. Hasło jako węzeł 'Password' przechowywane
jest w formie zaszyfrowanej algorytmem MD5.
Aby użyć wcześniej zdefiniowanego profilu wystarczy na stronie profilów wybrać
dany login z listy oraz wpisać hasło dla profilu, po czym kliknąć 'Use profile'. Hasło jest
szyfrowane funkcją skrótu MD5 i porównywane z hasłem zakodowanym w pliku
‘profiles.xml’. Jeśli hasła są jednakowe użytkownik staje się aktywny, czyli wszystkie wyniki
zostaną zapisane na jego konto oraz przy ponownym uruchomieniu gry, gdy później nie
nastąpi ponowna zmiana aktywnego użytkownika, zostanie użyty ten profil.
Aby utworzyć nowy profil wystarczy na stronie profilów podać login dla nowego
użytkownika oraz wpisać hasło dla profilu, po czym kliknąć 'Create profile'. Jeśli baza danych
jest dostępna oraz podany login nie istnieje już w bazie użytkowników to nowy użytkownik
zostanie utworzony oraz stanie się aktywny, czyli wszystkie wyniki zostaną zapisane na jego
konto, a także przy ponownym uruchomieniu gry, gdy później nie nastąpi ponowna zmiana
aktywnego użytkownika zostanie użyty ten profil. Dodatkowo do bazy danych zostanie
wprowadzony nowy użytkownik z danymi formularza, czyli login oraz hasło zaszyfrowane
w MD5 oraz, w lokalnym pliku 'profiles.xml' zostanie dodany nowy wpis o utworzonym
14
profilu (potrzebne węzły zostaną utworzone według wcześniej opisanej hierarchii w pliku
profilów).
5.2 Ranking wyników
Przy wykorzystaniu profili graczy utworzono centralny ranking wyników. Wyniki
globalne są przechowywane w bazie danych, natomiast lokalne w pliku ‘scores.xml’. Po
przejściu do menu ‘Statistics’ widać lokalne wyniki. Po kliknięciu przycisku ‘Update’ wyniki
lokalne są przesyłane do bazy danych, a z bazy pobierane są nowe, których nie ma jeszcze
lokalnie.
5.3 Strona WWW poświęcona grze oraz Fan Page na Facebooku
Ponadto stworzona została strona informacyjna opisująca grę oraz zawierająca
centralną bazę wyników graczy. Dodatkowo został stworzony fan page na facebooku. Adresy:
Strona WWW: http://lisu.homelinux.org/~angel/rubiq
Fan page: http://www.facebook.com/pages/Rubiq-Cube/105023389589931
6. Interfejs użytkownika
Nie wszystkie elementy można obsłużyć przy pomocy Kinecta, więc przygotowane
zostały 2 wersje aplikacji – jedna częściowo obsługuje Kinecta, a druga korzysta tylko
z myszki i klawiatury. W obu przypadkach menu główne obsługiwane jest przy pomocy
myszki.
Jedyna z opcji, która obsługiwana jest inaczej to ‘Start Game’. Pojawia się nowe okno
z instrukcją po lewej. Na środku ekranu znajduje się kostka Rubika. W wersji
z Kinectem po prawej stronie pojawia się również okno z widokiem, z kamery wbudowanej
w urządzenie. Aby uaktywnić obsługę przy pomocy Kinecta, należy ustawić się w pozycji
przypominającej grecką literę Ψ. Poprawne wykrycie użytkownika zostanie zasygnalizowane
pojawieniem się punktów kontrolnych na szkielecie. Będą one podążać za użytkownikiem.
Możliwe jest 9 rodzajów zaznaczonych ścian oraz można niczego nie zaznaczać
i wtedy dokonuje się obrotu całej kostki. Lewą ręką wybiera się odpowiednie ściany,
a prawą wykonuje obroty. Badane są różnice położeń prawej dłoni w krótkich odcinkach
15
czasu. Jeżeli przesunięcie będzie wystarczająco duże, wykonany zostanie obrót o 45°. Dla
bardziej energicznego ruchu możliwe jest jednorazowe wykonanie obrotu o 90°.
W przypadku obrotu całej kostki możliwe są obroty o 90° i 180°. Kostka lub jej wybrana
ściana jest obracana w kierunku zgodnym z kierunkiem przemieszczenia prawej dłoni.
Patrząc na nachyloną do poziomu kostkę, można w pionie wyodrębnić 6 różnych ścian. Z
przodu widoczne są 2 ściany boczne. Aby wybrać odpowiednią z nich, należy stanąć
w rozkroku lub ze złączonymi nogami. W obu przypadkach lewy łokieć powinien być na
wysokości ramienia. Kąt w łokciu powinien wynosić około 90°, a lewa ręka powinna być
skierowana w pionie do góry. Jedną z 3 ścian wybiera się poprzez skręty ciała – lewa strona z
przodu, na równi z prawą i lewa strona z tyłu. Należy jednak uważać, żeby lewa dłoń nie
znalazła się za ramieniem, ponieważ przy pomocy wykorzystanej biblioteki, tracony jest
obraz szkieletu. Aby wybierać w ścianach poziomych, należy lewą dłoń umieszczać na
wysokości czoła, na wysokości torsu lub na poziomie pasa. Aby dokonać odznaczenia ścian,
należy wyprostować lewą rękę w łokciu i unieść lewą dłoń na wysokość ramienia. Te same
czynności można wykonać przy pomocy klawiatury w wersji, która nie wykorzystuje Kinecta.
W trakcie gry mierzony jest czas – od wykonania pierwszego ruchu kostką. Ułożenie
kostki sygnalizowane jest odpowiednim komunikatem. Aby wyjść z aktualnej rozgrywki,
należy wcisnąć przycisk „ESC”.
W opcji ‘Profile’ ustawiane są dane dotyczące konta – nazwa użytkownika i hasło.
Natomiast w ‘Statistics’ przechowywane są najlepsze wyniki – można użyć przycisku
‘Update’, żeby pobrać aktualne dane rankingowe z serwera. W ‘Settings’ możemy ustawić
level. Odpowiada on liczbie przesunięć mających na celu wymieszanie kostki. Przycisk ‘Exit’
zamyka aplikację.
Bibliografia
[1] Stephens Rod, WPF Programmer’s Reference, Wiley Publishing, 2010.
[2] Nathan A., Windows Presentation Foundation Unleashed, Sams Publishing, 2007.
[3] MacDonald M., Pro WPF in C# 2008 Windows Presentation Foundation with .NET 3.5,
APress, 2008.
16
[4] Witryna poświęcona technologiom Windows Presentation Foundation oraz Windows
Forms, http://windowsclient.net
[5] Strona poświęcona technologii WPF, http://www.wpftutorial.net
[6] GÓRSKI J., Inżynieria oprogramowania w projekcie informatycznym, Mikom, Warszawa,
1999.
[7] Rysowanie Kinectem za pomocą biblioteki OpenNI,
http://www.studentguru.gr/blogs/vangos/archive/2011/02/09/kinect-and-wpf-
painting-with-kinect-using-openni.aspx
[8] Tutorial 3D dla Windows Presentation Foundation,
http://kindohm.com/technical/WPF3DTutorial.htm
[9] Dokumentacja biblioteki OpenNI, http://openni.org/documentation