Posts from 2009

Płaska wizualizacja funkcji 3D

2009-06-02 18:43

Funkcje, których dziedziną jest podzbiór R2 (czyli płaszczyzna) ciężko jest przedstawić na płaskim ekranie w sposób poglądowy, a jednocześnie pozwalający na odczytanie jakichś wartości. O ile bowiem izometryczny rzut 3D wygląda efektownie, to poza ogólnym kształtem powierzchni nie przedstawia wielkiej ilości informacji. Sprawa wygląda trochę lepiej, jeśli taką figurę możemy jeszcze obracać… co aczkolwiek może być z kolei trudne do zrealizowania np. na wydruku :]

Jednym z wyjść może być tutaj zastosowanie sposobu używanego do map wysokości w grach. Nietrudno zauważyć, że są one niczym innym jak właśnie funkcjami typu R2->R, a reprezentowane są najczęściej jako bitmapy w odcieniach szarości: jasność piksela x,y odpowiada w nich wysokości odpowiedniego punktu terenu.
Takie heightmapy wprawdzie łatwo się rysuje, ale niespecjalnie nadają się do pokazania jako ilustracja czegokolwiek – odcienie szarości są przecież z definicji szare i nieciekawe :) Dlatego bardzo spodobał mi się prosty pomysł na uczynienie tej reprezentacji znacznie ładniejszą, o którym to usłyszałem niedawno.

Idea jest prosta: po co używać samych szarości, skoro mamy do dyspozycji całą paletę kolorów?… Każdy kolor w reprezentacji RGB jest przecież tożsamy z liczbą, co widać najbardziej, jeśli weźmiemy jego typową reprezentację 32-bitową:

W niej “największym” kolorem jest kolor biały (0x00FFFFFF, czyli 224 – 1), zaś “najmniejszym” czarny (0x0), a pomiędzy nimi włącznie występuje każdy z 2563 możliwych kolorów. Jeśli teraz przeskalujemy wartości naszej funkcji do tak określonego przedziału (lub nieco mniejszego), a następnie przekonwertujemy każdą z tych 32-(a właściwie 24-)bitowych liczb na odpowiadający jej kolor RGB, to otrzymamy w ten sposób heightmapę o znacznie bogatszej palecie barw niż poprzednio.
Jeśli ponadto używamy dokładnie takiej reprezentacji liczbowej kolorów, jak powyżej (najmłodsze bity na kolor niebieskie, a najstarsze używane na czerwony), to zauważymy, że rozkład barw w wyniku jest całkiem znajomy. I tak obszary niebieskie odpowiadają wartościom najmniejszym, zielone średnim, żółte – wysokim, a czerwone – najwyższym. Łącznie przypomina mapę hipsometryczną – czyli, jakby nie patrzył, mapę wysokości właśnie :)

Do czego może przydać się takie cudo, oprócz wspomnianej już wizualizacji funkcji R2->R na płaszczyźnie? Pewnie do niewielu rzeczy :) Ja wpadłem jednak na jeszcze jedno potencjalne zastosowanie.
Ponieważ wynikowy obrazek używa tak “geograficznych” barw, może on całkiem dobrze wyglądać jako… tło minimapy pokazywanej w grze, która rozgrywa się na terenie opisanym mapą wysokości. Oczywiście rzecz wymagałaby dopracowania: przede wszystkim bardziej starannego dobrania kolorów brzegowych (niekoniecznie białego i czarnego) oraz być może kolorów pośrednich (np. kolor poziomu morza), a i pewnie zastosowania jakichś filtrów na wynikowym obrazku (np. wygładzania). Efekty mogłyby być jednak interesujące, co widać obok.

Funkcje jako dane wejściowe

2009-05-29 15:56

Sporo algorytmów jako swoje parametry przyjmuje różnego typu funkcje, które potem są wykorzystywane w trakcie ich działania. Prostym przykładem są tu wszelkiego rodzaju sortowania czy wyszukiwania, umożliwiające często podanie własnego predykatu (funkcji zwracającej wartość logiczną). W bardziej skomplikowanej wersji może chodzić chociażby o algorytm genetyczny lub przeszukujący drzewo gry, który wykorzystuje do działania jakąś funkcję oceniającą (np. osobników w populacji).

Na takie okazje różne języki programowania oferują różne narzędzia, lecz w większości przypadków sprowadzają się one do jednego z poniższych:

  • Wskaźniki lub delegaty. Przekazywanie funkcji przez wskaźnik jest proste i naturalne, ale ma pewne ograniczenia. W C(++) na przykład nie da się zwykłym wskaźnikiem pokazać na metodę konkretnego obiektu; docelowo może to być tylko funkcja globalna lub statyczna. Ten problem nie występuje zwykle w przypadku delegatów, którymi często można pokazać właściwie na wszystko – również na anonimową funkcję zdefiniowaną ad hoc:
    1. // sortowanie listy w oparciu o własny predykat (C# 2.0 wzwyż)
    2. objects.Sort (delegate (object a, object b)
    3.     { return a.ToString().Length - b.ToString().Length; });

    Niektóre języki nie mają jednak ani wskaźników, ani delegatów – w nich zwykle stosuje się sposób następny.

  • Interfejsy i metody wirtualne. Ta technika polega na zdefiniowaniu interfejsu (lub klasy abstrakcyjnej) z metodą wirtualną, którą docelowo będzie wywoływać algorytm. Korzystający z niego programista implementuje po prostu wskazany interfejs (lub definiuje klasę pochodną) i nadpisuje wspomnianą metodę własną wersją. Ten sposób jest szczególnie popularny w Javie, gdzie wspomagają go inne mechanizmy językowe – jak choćby niestatyczność klas wewnętrznych lub możliwość ich definiowania inline – np. w wywołaniu funkcji.
    Zauważmy też, że implementacja naszej ‘funkcji’ w postaci klasy pozwala też na dodatkowe możliwości, jak na przykład posiadanie przez nią stanu (co aczkolwiek w wielu przypadkach, np. predykatów sortowania, jest wysoce niezalecane).
  • Funktory. To rozwiązanie jest specyficzne dla C++ i opiera się na dwóch występujących w tym języku feature‘ach: szablonach i przeciążaniu operatorów. Dzięki temu algorytm korzystający z “czegoś co przypomina funkcję” może wyglądać choćby tak:
    template < typename T, typename SearchFunc >
    const T find(const std::vector& v, SearchFunc pred)
    {
    for (int i = 0; i < v.size(); ++i) if (pred(v[i])) return v[i]; // znaleziono element spełniający predykat return T(); }[/cpp] Określenie "coś co przypomina funkcję" jest jak najbardziej na miejscu, gdyż tutaj predykatem może być cokolwiek, co da się jako funkcję potraktować - czyli wywołać operatorem (). Może więc to być zwykły wskaźnik, jakaś ładna zewnętrzna implementacja mechanizmu delegatów (np. FastDelegate) lub jakikolwiek obiekt z przeciążonym operatorem nawiasów (). Właśnie te ostatnie nazywa się zwykle funktorami, a jeśli ktoś nieco bardziej zagłębił się w bibliotekę STL, na pewno nie raz się z nimi spotkał.

Trzeba też powiedzieć, że właściwie istnieje też inny sposób: zamiast samej funkcji przekazywanie jej… nazwy. “I niby jak ją potem wywołać?”, można zapytać. Ano to już indywidualna kwestia każdego języka – w tym przypadku zwykle interpretowanego :)

Tags: , , , ,
Author: Xion, posted under Programming » 5 comments

Podwójne skróty klawiszowe w VS

2009-05-23 17:15

W takiej dużej aplikacji jak IDE ilość opcji jest na tyle spora, że część z nich pochowana jest głęboko w czeluściach wielopoziomowego menu. Często nie znaczy to jednak, że są one mniej przydatne; na pewno jednak są trudniej dostępne. Dodatkowo też ilość klawiszy na klawiaturze jest ograniczona, więc nie wszystkie opcje mogą mieć przypisane skróty klawiszowe… Chyba że zastosuje się jakieś sztuczkę :)

W vimie i jego pochodnych taką sztuczką były polecenia wpisywane od dwukropka (na czele z najbardziej przydatnym, czyli :q! ;]). W Visual Studio mamy za to podwójne skróty klawiszowe, (chords) dzięki którym możemy z palca wywołać nie tylko, powiedzmy, polecenie otwarcia pliku, ale też pewne rzadziej używane narzędzia – które paradoksalnie bywają o wiele przydatniejsze.
Jak to działa? Otóż bardzo prosto: po wciśnięciu specjalnej kombinacji ‘aktywującej’ (np. Ctrl + K) nic się wprawdzie nie dzieje, ale aplikacji przełącza się w tryb pozwalający na wykrycie drugiej kombinacji (np. Ctrl + C), która odpowiada już konkretnej akcji. Całość można więc zapisać jako Ctrl + K,C; oznacza to po prostu: wciśnij i przytrzymaj Ctrl, a potem naciśnij kolejno K i C. Nic specjalnie trudnego, prawda? :)

A jakież to wspaniałe opcje IDE są dostępne w ten sposób? Ano jest ich całkiem sporo, spośród których wylistuję kilka:

  1. Komentowanie kodu. Tak tak, nie trzeba już ręcznie wstawiać /* */ czy #if 0/#endif, żeby wyłączyć jakiś kawałek kodu z kompilacji :) Mamy bowiem takie oto skróty:
    • Ctrl + K,C – komentuje (w stylu C++) każdą linijkę zaznaczonego fragmentu
    • Ctrl + K,U – odkomentowuje zaznaczony fragment (usuwa // z początku każdej linijki)
  2. Edycja kodu:
    • Ctrl + K,X – pozwala na wstawienie predefiniowanych wzorców (snippets) różnych konstrukcji językowych (np. pętli for), pozwalając przy tym na łatwą zmianę ich szczegółów (np. nazwy zmiennej będącej licznikiem pętli).
    • Ctrl + K,S – pozwala otoczyć zaznaczony fragment kodu jakimś nazwanym blokiem, np. trycatch, if, itd.
  3. (Ro)zwijanie bloków. W VS bloki kodu mogą być rozwijane lub ukrywane poprzez klikanie w mały plusik z lewej strony ich pierwszego wiersza. Opcja ta nazywa się outlining lub folding i można ją kontrolować m.in. przy pomocy poniższych skrótów:
    • Ctrl + M,O – zwija wszystkie bloki tak, że widoczne są tylko nagłówki definicji funkcji i metod
    • Ctrl + M,L – (ro)zwija wszystkie bloki
    • Ctrl + M,P – wyłącza automatyczny outlining całkowicie
  4. Zmiana sposobu przeglądania kodu:
    • Ctrl + R,W – w(y)łącza wyświetlanie białych znaków (spacji jako kropek, tabulatorów jako strzałek, itd.)
    • Ctrl + E,W – w(y)łącza zawijanie wierszy

Polecam przynajmniej jednokrotne spróbowanie każdej z powyższych opcji. Możliwe, że w ten sposób odkryjecie coś, czego brakowało wam przez cały czas :) Nie jest to też kompletna lista – więcej skrótów/opcji można znaleźć przeglądając menu Edit.

Tags: ,
Author: Xion, posted under Applications » 9 comments

Usuwanie z kontenerów STL

2009-05-15 14:30

Teoretycznie najlepszym sposobem na usuwanie elementów z pojemników STL jest posłużenie się idiomem eraseremove:

  1. v.erase (remove_if(v.begin(), v.end(), ToBeDeleted), v.end());

W praktyce bywa on dość kłopotliwy jeśli stosowany predykat (tutaj oznaczony jako funktor ToBeDeleted) musi być napisany specjalnie do tego celu. Zresztą gra często nie jest warta świeczki, bo implementacje algorytmów w rodzaju remove to w środku często nic innego, jak zwykłe pętle.

No a pętle możemy przecież napisać sobie sami, prawda?… Otóż nieprawda – zwłaszcza jeśli wcześniej nie pomyślimy przez chwilę. Łatwo bowiem wyprodukować coś takiego:

  1. // źle!
  2. for (vector<Object>::iterator it = v.begin(); it != v.end(); ++it)
  3.     if (ToBeDeleted(*it)) v.erase(it);

Z poprawnością ma to niewiele wspólnego. Jeśli bowiem natrafimy na element do usunięcia, to po dokonaniu tego (w nagłówku pętli) zinkrementujemy iterator, który na ów usunięty element wcześniej pokazywał. W ten sposób możemy pominąć element następny, bo go zwyczajnie “przeskoczymy”. Odpowiednik powyższej pętli używający zwykłego licznika i operatora [] jest zresztą obarczony identycznym problemem.
Jak więc usuwać? Umiejętnie, rzecz jasna :) Skoro po skasowaniu jednego elementu pozostałe przesuną się o jedno miejsce do tyłu, nie powinniśmy zawsze bezmyślnie przechodzić dalej. Trzeba to robić tylko wtedy, gdy dany element zostawiliśmy w spokoju:

  1. for (vector<Object>::iterator it = v.begin(); it != v.end(); /* bez ++it! */ )
  2.     if (ToBeDeleted(*it)) it = v.erase(it);    else ++it;

To w sumie całkiem oczywiste, jeśli chwilę się nad tym zastanowić. Bowiem – w przeciwieństwie do dodawania – usuwanie elementów z kontenerów wymaga właśnie chwili zastanowienia :]

Tags: ,
Author: Xion, posted under Programming » 11 comments

Skąd wziąć matematyczne stałe

2009-05-09 12:24

Jeśli tylko liczymy coś bardziej skomplikowanego niż rozmieszczenie kontrolek w oknie, to niechybnie zaczniemy potrzebować którejś ze stałych matematycznych – na przykład π. Co wtedy robimy? Ano całkiem często skutkuje to włączeniem do programu deklaracji typu:

  1. const double PI = 3.14159265358979;

Nie jest to oczywiście wielkie zło (a już na pewno mniejsze niż bezpośrednie korzystanie z definicji typu π = 4atan(1)), ale w większości przypadków jest to też wyważanie otwartych drzwi. Potrzebne stałe mamy bowiem często już gdzieś zdefiniowane – trzeba tylko wiedzieć, gdzie ich poszukać:

  • W C/C++ mamy M_PI, M_E, a nawet M_LN10 czy M_SQRT2 zdefiniowane w math.h lub cmath. Definicje te nie są jednak częścią standardu, więc dbające o zgodność kompilatory (czyli większość kompilatorów w ogóle) wymagają pewnych #define‘ów przed dołączeniem ww. nagłówków. I tak dla Visual C++ jest to _USE_MATH_DEFINES, a dla GCC bodajże prawie dowolne z makr _SOURCE (jak _GNU_SOURCE czy _ALL_SOURCE).
  • W DirectX mamy tylko DX3DX_PI i D3DX1BYPI (1/π) – co nie dziwi, bo przecież w grafice więcej do szczęścia nie potrzeba ;) Obie te stałe są zdefiniowane w d3dx#math.h (gdzie # to numer wersji DirectX), który to nagłówek jest dołączany automatycznie jeśli korzystamy z D3DX.
  • W .NET mamy stałe PI i E zdefiniowane jako statyczne pola klasy System.Math. W Javie klasa java.lang.Math zawiera dwa identycznie zdefiniowane pola.

Jak zatem łatwo zauważyć, wynajdywanie koła w postaci definicji π czy e jest w wielu przypadkach nieuzasadnione :]

Tags: ,
Author: Xion, posted under Math, Programming » 13 comments

Nowsze solucje w starym VS

2009-05-03 15:17

Visual Studio 2008 zasadniczo służy do pracy z .NET Framework w wersji 3.5, ale ma tę przyjemną cechę, że pozwala określić używaną przez projekt wersję frameworka. A to oznacza, że można zdecydować się na wersję 2.0, co pozwala na współpracę także z edycją 2005.

Pewnym problemem pozostaje jednak fakt, że próba załadowania w Visual Studio 2005 solucji stworzonej w edycji 2008 kończy się takim oto uroczym komunikatem:

Co z tym zrobić?… Można oczywiście utworzyć nową, pustą solucję, a potem dołączyć do niej projekty z tej oryginalnej. To dość kłopotliwe, a VS wymaga też, aby solucje tworzyć zawsze w pustym lub nieistniejącym katalogu. Ostatecznie musielibyśmy więc przekopiować ją wpierw w docelowe miejsce, a potem zająć się przyłączaniem doń naszych projektów. Warto też zauważyć, że stworzenie zupełnie nowej solucji oznacza utratę wszelkich niestandardowych konfiguracji budowania projektów; w gruncie rzeczy bowiem właśnie te konfiguracje to – poza samą listą projektów – jedyna istotna rzecz przechowywana w pliku .sln.
Na szczęście jednak ów plik jest zwykłym plikiem tekstowym, którego format tak naprawdę nie zmienił się od dawna (jak sądzę co najmniej od wersji 2001). Dlatego całkiem prawdopodobne jest to, że jedyną przeszkodą przy wczytywaniu nowej solucji w starszym VS jest… numer wersji zapisany w pierwszym wierszu pliku:

Microsoft Visual Studio Solution File, Format Version 10.00

Nie zaszkodzi więc zmienić owe 10.00 (VS 2008) na 9.00 (VS 2005) i ponownie spróbować otworzyć solucję. Jeśli próba się powiedzie, oszczędzimy sobie pracochłonnego kombinowania. A jeśli nie… to pewnie warto pomyśleć o upgrade ;]

Tags:
Author: Xion, posted under Applications » 10 comments

Uczę się pilnie…

2009-04-28 16:40

Dość powszechne na forum Warsztatu jest pytanie: z czego się uczyć? – programowania w ogóle i gamedevu w szczególności. Chodzi tu po prostu o źródła wiedzy. I chociaż (częściowa) odpowiedź znajduje się w warsztatowej Wiki, to pewnie podobne zapytania wciąż będą się pojawiać. Nie ma się tu specjalnie czemu dziwić.

Zupełnie inaczej jest, jeśli owo ‘uczenie się’ występuje w trochę innym kontekście. “Czy mogę nauczyć się programowania, jeśli …?”, “Czy powinienem wykonywać ćwiczenia z książki X?” albo najgorsze z tych pytań: “Ile czasu poświęcacie dziennie na naukę programowania?”. Natychmiast służę oczywiście odpowiedzią na nie: dokładnie trzy godziny, między 19 a 22, przez dwa dni w tygodniu, tj. we wtorki i piątki. I bardzo, bardzo przy tym pilnuję, aby tego czasu nauki nie skracać nawet o minutę!… Czy ta odpowiedź jest satysfakcjonująca? Przypuszczam, że niezbyt ;]
A mówiąc już trochę poważniej… Jeśli nie zajmujemy się programowaniem zawodowo, to jest to pewnie nasze hobby – czyli coś, co robimy dla przyjemności. Chodzi o tworzenie czegoś własnego, eksperymentowanie z różnymi technologiami, a także – nie ma co ukrywać – uczenie się czegoś nowego. Jednak mówimy tu o uczeniu się rzeczy, które chcemy, kiedy chcemy i jak długo chcemy – wedle naszego własnego uznania, a nie czegoś w rodzaju “planu treningowego'”, ułożonego na podstawie odpowiedzi ludzi z jakiegoś forum internetowego (z całym szacunkiem dla Warsztatu oczywiście). Trudno przecież oczekiwać, że powiedzą nam oni, co będzie nam bardziej odpowiadało; do tego trzeba dojść samodzielnie.

Czy więc właściwa odpowiedź na wspomniane pytanie istnieje? Oczywiście, że tak. Brzmi ona: na naukę programowania przeznaczamy tyle czasu, ile chcemy i/lub możemy. Tak po prostu.

Tags:
Author: Xion, posted under Internet, Programming, Thoughts » 10 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.