Ładowanie
popup

C++11: Jak się pozbyć placeholderów?

2013-01-03 13:41:57

Bindując funkcje do obiektów std::function jesteśmy zmuszeni do podawania tzw. placeholderów. Choć niekiedy ich "mieszanie" może być przydatne, jednak zazwyczaj ustawia się je po kolei (_1, _2, _3, ...). Niestety nie ma prostego sposobu żeby pominąć tą czynność dla tych właśnie przypadków. Rozwiązaniem może być specjalna funkcja szablonowa, która po "wyliczeniu" ilości argumentów obiektu std::function (które są podawane jako argument szablonu) i dopasowaniu do niego odpowiedniej funkcji. Każda z tych funkcji różni się ilością placeholderów w argumentach funkcji std::bind.

template<typename S,typename I,typename FN>struct bind{ template<int N> static typename std::enable_if<N==0>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref);} template<int N> static typename std::enable_if<N==1>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1);} template<int N> static typename std::enable_if<N==2>::type get(S ptr,I *ref,FN &fp){fp=std::bind(ptr,ref,std::placeholders::_1,std::placeholders::_2);} ... };

Nie da się niestety napisać różnych wersji tej funkcji automatycznie, ale wątpię że ktokolwiek będzie miał potrzebę używania więcej niż kilku argumentów ;) Jak widać jest tu użyta klasa std::enable_if, (która też jest częścią nowego standardu). Jest to prosta klasa która pozwala wyeliminować funkcje których nie potrzebujemy w zależności od podanego warunku (kompilator na to pozwala, można poczytać o tym więcej tu SFINAE)

Dzięki takiej funkcji (i z pomocą C++11) można łatwo zbudować klasę eventów bardzo przypominającą tę z C# bez kombinowania z castowaniem wskaźników lub budowaniem jej od zera na template-ach. Dla uproszczenia podam prostą implementację klasy "delegate" korzystająca z tej funkcji:

template<typename... P> class delegate{ static const int argsCount=sizeof...(P); // tutaj wyliczamy ilość argumentów szablony przekazanych do klasy typedef std::function<void(P...)> tdSlot; // typ funkcji, dla uproszczenia zwraca void tdSlot m_slot; public: delegate(){} ~delegate(){} void connect(const tdSlot &f){ m_slot=f; } template<typename MP,typename TP>void connect(MP mp,TP *tp){ bind<MP,TP,tdSlot>::template get<argsCount>(mp,tp,m_slot); // po prostu wywołujemy odpowiednią funkcję w zależności od ilości argumentów } void fire(P... args){ m_slot(args...); // odpala delagata z podanymi argumentami (przekazuje je dalej) } void operator=(const tdSlot &f){return connect(f);} };

Mam w tej klasie 2 funkcje connect, pierwsza może służyć do bindowania lambdy/"zwykłej" funkcji lub przyjmowania rezultatu std::bind, druga służy do bindowania metod wewętrznych klas - pierwszy argument to wskaźnik do metody, druga to obiekt na którym wywołamy delegata. Prosty przykład użycia:

class foo{ public: void zzz(int &x){ std::cout<<"\nfoo::zzz="<<x; } void zzzff(float x,float y){ std::cout<<"\nfoo::zzzf="<<x<<", "<<y; } }; int main(int argc, char**argv){ foo obiekt; delegate<float,float> df; df.connect(&foo::zzzff,&obiekt); df.fire(5,10); df.connect(std::bind(&foo::zzzff,&obiekt,std::placeholders::_1,std::placeholders::_2)); // można też tak df.fire(10,15); int intArg=66; delegate<int&> di; di=[](int &o){std::cout<<"\ninside lambda: "<<o; ++o;}; di.fire(intArg); di.fire(intArg); std::cout<<"\nchanged value: "<<intArg; di.connect(&foo::zzz,&obiekt); di.fire(intArg); return 0; }

Tagi: Programowanie C++ Templates | Dodaj komentarz

Przyspieszanie Frustum Culling

2010-12-01 15:37:00

Dziś mały tip o optymalizacji Frustum Culling(zdefiniowany jest na 6 płaszczyznach) dla OBBox i AABBox (Prostopadłościan dowolnie zorientowany i 'wyrównany' do osi układu współrzędnych). Zacznę od struktury boxów:

class AABBox{ Vec3f min; // maksymalne wartości dla każdej z osi Vec3f max; // minimalne wartości dla każdej z osi }; class OBBox{ Vec3f center; // środek prostopadłościanu Vec3f axis[3]; // wektory opisujące układ współrzędnych Vec3f size; // długośći dla każdej z osi };

Najprostszym sposobem na sprawdzenie zawierania takich boxów w frustumie jest sprawdzenie zawierania każdego z jego wierzchołków dla każdej z 6 płaszczyzn frustuma. Żeby to troszkę przyspieszyć wyznaczam jeden wierzchołek (punkt p), który jest "najdalej" wysunięty względem normalnej danej płaszczyzny i sprawdzam relację tego punktu względem tej płaszczyzny. Jeśli dany wierzchołek znajduje się "nad" płaszczyzną - box znajduje się w zasięgu widzenia. Większość tego typu implementacji np. http://www.lighthouse3d.com/opengl/viewfrustum/index.php?gatest3 oblicza niepotrzebnie czy box przecina się z płaszczyzną - co narzuca niepotrzebny drugi test punkt-płaszczyzna i jest w większości przypadków niepotrzebne.

Wyliczanie punktu p dla AABBox:

// aabb - nasz box // normal - normalna płaszczyzny Point3f p=aabb.min; if(normal.x>=0) p.x=aabb.max.x; if(normal.y>=0) p.y=aabb.max.y; if(normal.z>=0) p.z=aabb.max.z;

Wyliczanie punktu dla OBBox:

// obb - nasz box // normal - normalna płaszczyzny // definiuje punkt poczatkowy (bedziemy go przesuwac wzgledem kazdej osi o odpowienie wartosci) Point3f p=obb.center; for(int i=0;i<3;++i){ float d=obb.axis[i]^normal; // projekcja normalnej wzdluz tej osi (operator ^ wykonuje iloczyn skalarny) // jesli d jest mniejsze od 0 - szukany punkt jest po negatywnej stronie danej osi (-axis[i]) if(d>0.f) p+=obb.axis[i]*obb.size[i]; else p-=obb.axis[i]*obb.size[i]; }

Dysponując takim punktem p, wystarczy sprawdzić czy znajduje się on "nad" płaszczyznami frustuma. Przykładowy kod sprawdzający:

// dla każdej płaszczyzny frustuma: for(int i=0;i<6;++i){ // m_planes - tablica z płaszczyznami if(!m_planes[i].Distance(p)>0) // sprawdzam czy punkt p znajduje się nad płaszczyzną return false; // jeśli nie - box napewno jest niewidoczny (można przerwać sprawdzanie) } return true;

Wykonałem prosty programik testowy, gdzie można sobie sprawdzić działanie tego kodu: frustm.culling.2.zip

Demo Frustum Culling...
Demo Frustum Culling...

Jak widać na tym screenie - przyrost wydajnosci jest, ale oczywiście nie ma większego sensu się bawić w takie optymalizacje - chyba że "dla sportu" ;)

Tagi: Programowanie Engine Optymalizacja | Komentarze (1)

Method Chaining

2010-10-04 10:56:39

Dziś napiszę o często stosowanej przeze mnie (i ogólnie w programowaniu wysokopoziomowym) technice programowania, mianowicie - "Method Chaining". Główną jej zaletą jest zwiększenie czytelności kodu, który staje się czytelniejszy i po prostu krótszy. Dobry przykład zastosowania tej techniki znajdziemy w bibliotece jQuery, albo w standardowej bibliotece C++ (choć przez przeładowanie operatorów << i >> można jej od razu nie dostrzec). Polega ona na wywoływaniu kilku funkcji na rzecz danego obiektu w jednej instrukcji. np.:

obiekt.funkcja1().funkcja2().funkcja3();

Powyższy przykład bez zastosowania Method Chaining wyglądał by tak:

obiekt.funkcja1(); obiekt.funkcja2(); obiekt.funkcja3();

Działanie kodu jest proste: każda wywołana funkcja w "łańcuchu" zwraca jako rezultat adres/referencję do obiektu dla którego jest wywoływana (wskaźnik this). Poniżej inny przykład z jakiegoś mojego kodu:

// z wykorzystaniem method chaining geometry.Scale(vec_scale).Translate(trans_vec).Rotate(rotate_vec,rotate_angle).CalculateNormals().Apply(); // bez wykorzystania method chaning geometry.SetScale(vec_scale); geometry.SetTranslate(trans_vec); geometry.SetRotate(rotate_vec,rotate_angle); geometry.SetCalculateNormals();

Kod ten kolejno wykonuje jakieś operacje na modelu o nazwie geometry, czyli kolejno: zmienia skalę modelu, dokonuje transformacji, następnie jest rotacja o zadany kąt według jakiegoś wektora, na koniec wyliczenie wektorów normalnych i wykonanie wcześniej ustalonych obliczeń funkcją apply. Jak widać kod jest bardziej skondensowany i czytelniejszy, a samo jego pisanie jest szybsze i przyjemniejsze (zwłaszcza z pomocą narzędzi typu InteliSense). Przykładowa implementacja klasy Geometry może wyglądać tak:

class Geometry{ private: bool todo_calc_normals,todo_rotate,todo_translate,todo_scale; public: Geometry():todo_calc_normals(false),todo_rotate(false),todo_translate(false),todo_scale(false){} Geometry &CalculateNormals(){ todo_calc_normals=true; return *this; } Geometry &Scale(const Vec3f &iScale){ todo_scale=true; // tu zapisujemy jeszcze gdzies w pamieci wektory 'iScale' return *this; } Geometry &Translate(const Vec3f &iTranslation){ todo_translate=true; // zapisujemy w pamieci 'iTranslation' return *this; } Geometry &Rotate(const Vec3f &iRotation,float iAngle){ todo_rotate=true; // zapisujemy w pamieci wektor 'iRotation' i kat 'iAngle' return *this; } void Apply(){ // na podstawie zebranych danych obliczamy dodane zadania } };

Jak widać kod jest bardzo prosty - całą robotę wykonuje instrukcja return *this - czyli jak wspomniałem - zwracanie referencji do obiektu na którym dalej chcemy wywoływać następną funkcję. Należy pamiętać w powyższym kodzie o odpowiednim ustawieniu flag 'todo_*' i zapamiętywaniu danych wejściowych niezbędnych do zakończenia obliczeń. Dzięki funkcji Apply bardzo łatwo można wprowadzić spore optymalizacje: zamiast osobno przechodzić dane modelu dla policzenia kolejno kilku transformacji na modelu, można na raz dokonać obliczeń raz przechodząc dane.

Technika ta oczywiście nie ogranicza się tylko do wywoływania funkcji jednego obiektu - spore możliwości pokazuje gdy wywołujemy łańcuch metod na kilku różnych obiektach, wspaniale upraszczając kod. Na koniec zamieszczam jeszcze kilka imponujących przykładów z wykorzystaniem biblioteki jQuery które zainspirowały mnie do napisania tej notki:

$('ul.first') .find('.foo').css('background-color', 'red').end() .find('.bar').css('background-color', 'green').end();

Powyższy przykład szuka wszystkich pod-elementów wypunktowania ul o klasie first i zmienia kolor tła na czerwony dla tych z klasą foo i na zielony dla pod-elementów z klasą bar.

$('form#login') .find('label.optional').hide().end() .find('input:password').css('border', '1px solid red').end() .submit(function(){ return confirm('Are you sure you want to submit?'); });

Kod ten wybiera jakiś formularz do logowania (o identyfikatorze login), później ukrywa wszystkie labele o klasie optional tego formularza. Ustawia czerwoną ramkę na pole tekstowe hasła formularza i ustawia mu jeszcze callback wywoływany przy wysłaniu formularza.

Dodatkowo funkcja end pozwala na przedłużenie łańcucha metod cofając wskaźnik elementu na którym pracujemy zwracając referencję do swojego rodzica. Nie trzeba zapamiętywać żadnych wyszukanych zmiennych, czy wyszukiwać elementów kilka razy - wszystko jest wykonane za pomocą jednego łańcucha metod.

przykłady pochodzą z:

Tagi: Programowanie | Komentarze (2)

Preloading obrazków w jQuery

2010-09-29 10:34:04

Dziś mały tip Javascript/jQuery. Kod ten pozwala na wykrycie momentu załadowania do pamięci pobranego obrazka na stronie internetowej. Dodaje go tutaj bo trochę sie nad tym męczyłem, a może się komuś przydać (w programowaniu 'webowym' jestem nowicjuszem ;) ). Od razu przejdę do kodu i krótkiego omówienia co się w tym kodzie dzieje:

function load_image(imgSrc){ // przed ladowaniem obrazka (np. pojawia sie gif z ladowaniem) var image=new Image(); // (1) $(image).load(function(){ // (2) // obrazek zaladowany do pamieci (np. mozna schowac juz gif z ladowaniem) // laduje obrazek z pamieci do naszego elementu img (zmiana atrybutu src) $('#popup_image').attr('src',imgSrc); // (4) // np. pokazuje wczesniej ukryty obrazek $('#popup_image').fadeIn('slow'); // zmiana pozycji rozmiaru obrazka itp. ... // reset callbacka image.onload=function(){}; }).attr('src',imgSrc); // (3) }

PS. Przy okazji polecam wszystkim web-developerom bibliotekę jQuery, zwłaszcza często używających Javascript. Jest to kompaktowa i bardzo dobrze zaprojektowana biblioteka, bardzo przyjemna w obsłudze.

Tagi: Programowanie jQuery Javascript | Komentarze (2)

Eksportowanie geometrii z Blender-a

2010-02-16 12:17:34

Ostatnio przerabiałem eksporter geometrii z blendera. aktualny skrypt był bardzo stary i potrzebował natychmiastowej przeróbki, wiec napisałem nowy. Przedstawię kilka porad na podstawie moich ostatnich doświadczeń na tym polu. Używam Blendera 2.49

Najwazniejsze to określić co będziemy eksportować. Czy to będzie jedna siatka, czy cały model (model składa się z x siatek (mesh))?, czy format będzie 'końcowy' czy przetwarzany?, jakie dane eksportować? jak i czy eksportować animacje? jak eksportować materiały? czy to będzie format binarny czy tekstowy? postaram się pomoc w odpowiedzi na niektóre z nich

to mniej - więcej tyle. resztę informacji można znaleźć w dokumentacji Blendera, czy w źródłach innych eksporterów dołączonych do programu.

a to test mojego eksportera porsche: 24000 wierzchołków
a to test mojego eksportera porsche: 24000 wierzchołków
palma 3500, a to sama palma (mojej produkcji :P)
palma 3500, a to sama palma (mojej produkcji :P)

//~

Tagi: Programowanie Engine Blender Python | Komentarze (2)

Tekstury NPOT w OpenGL

2009-12-01 12:07:35

Ostatnio przyszło mi skorzystać z tekstur nie mających wymiarów będących potęgami liczby 2 (NonPowerOfTwo). Dawniej radziłem sobie z takimi teksturami rozciągając mniejszą teksturę na większą mającą rozmiar będący potęgą 2 lub wklejając ją na fragment takiej tekstury. Dostępne są jeszcze dwa rozszerzenia wspomagające pracę z teksturami NPOT: texture_non_power_of_two i texture_rectangle. Oba są w standardzie OpenGL, texture_rectangle jest troche starsze. Przyjrzyjmy się im bliżej.

cechy texture_rectangle:

cechy texture_non_power_of_two:

Jak widać używanie texture_rectangle jest problematyczne, natomiast texture_non_power_of_two nie zmusza do wprowadzania zmian w programie: po prostu tworzymy teksturę podając 'dowolny' wymiar i używamy jak zwykłą teksturę. Wybieram ARB_texture_non_power_of_two.

W moim przypadku nie obyło się bez niespodzianek :) Oczywiście mój Radeon 9600 obsługuje oba rozszerzenia, ale nie do końca... Czasem działa, czasem nie - pełna losowość. A jak renderuję do tekstury NPOT to w ogóle nie działa. Szybka zmiana parametrów tekstury i wniosek: Po wyłączeniu filtrowania, ustawieniu zawijania na GL_CLAMP_TO_EDGE i nie generowaniu mipmap wszystko działa. Działa tak jak texture_rectangle, a ma ograniczenia jak texture_non_power_of_two, achh te 'ogl-owskie' sterowniki ATI...

Tagi: Programowanie OpenGL | Dodaj komentarz

GUI

2009-11-28 21:35:05

Niedawno skończyłem pisać system GUI do swojego silnika. Zawiera podstawowe kontrolki, obsługę skórek, rożych typów czcionek, prosty tryb 3d i takie tam :P Nie chcę sie rozpisywać, chyba wszystko widać na screenach i na filmikach. Teraz zabieram sie za ciekawsze rzeczy niż pisanie interfejsów :)

GUI#1
GUI#1
GUI#2
GUI#2
GUI#3
GUI#3
GUI#4
GUI#4
GUI#5
GUI#5

Tagi: Programowanie Engine | Komentarze (2)

<< Starsze