poniedziałek, 14 stycznia 2013

Projekt: Samochód Autonomiczny

Obecnie na blogu panuje cisza, więc przerwę ją na chwilę :)

Obecnie zbliża się ciężki okres - sesja, nawał projektów itd. Nie mniej jedną z rzeczy jakimi chciałbym się pochwalić, a zajmowałem się nimi ostatnio, to samochód autonomiczny "Jurek".

Dołączyłem do zespołu 2 tygodnie przed terminem pokazu i musiałem razem z Maćkiem (pzdr!) umożliwić autonomiczną jazdę - ze względu czasowych i technicznych (jedna kamerka internetowa na dachu i mój laptop + elektronika pojazdu) musieliśmy się ograniczyć do jazdy między dwoma zielonymi pasami (normalnie działa to na białych, ale spadł śnieg więc trzeba było zmienić oprogramowanie (: )

Z ciekawostek, soft jest oparty o C# (dev && dbg time!) i ku zaskoczeniu wszystkich zadziałało od razu na danych tzw. "z (_!_)", a co lepsze - okazał się działać lepiej niż przypuszczałem jeszcze kilka godzin wcześniej, w nocy - gdy kończyłem klepać i głośno przeklinałem :P
A tak to wyglądało:

Oczywiście telewizja swoje powiedziała, czym trochę nas ubawiła :)

Obecnie nie mam tymczasowo czasu rozwijać projekt, ale po sesji zamierzam ostro się zabrać za niego. Przyjmując model "progresywny" na liście TODO znajduje się zrobienie tego porządnie w C++ (i pewnie Qt?) oraz umożliwienie parkowania. O ile się uda - na razie jedyny sensor to jedna kamera internetowa, myślimy nad kupnem kilku ale nie wiem na ile realnie praktyczne to będzie.
Do tego szukam osób w miarę ogarniających temat CV, SLAM, pisania dokładnych symulacji oraz innych "ogarniaczy" do pomocy. Przy czym naprawdę zależy mi na konkretnych ludziach, którzy mogą pokazać, że coś faktycznie potrafią. Jeśli tak jest i jesteś we Wrocławiu, to skontaktuj się ze mną :)

I to tyle, jeszcze na koniec screen projektu który właśnie mam otwarty w IDE i kończę pisać - powierzchnie Coonsa I rodzaju:

piątek, 24 sierpnia 2012

Technika "Generic Macro"

Ostatnio w pracy pojawił się problem - mamy pewną funkcjonalność opartą na identyfikację zasobu poprzez c-string, co wygląda mniej więcej tak:

Resource->Get("Something_Foo");
Przy czym filozofia była taka, aby nigdy nie zwracać błędy tylko coś w miarę sensownego (wartości domyślne). Takie podejście do identyfikacji ma zasadnicze wady:
  • porównywanie stringów w run time
  • łatwo o błędy - literówki, niezgodność wielkości liter

Zaleto-wadą jest też fakt, że nie trzeba definiować wcześniej tych napisów ale nie ma żadnej jednej, długiej listy.

Dlatego też w ramach refaktoryzacji postanowiliśmy zamienić napisy na typ wyliczeniowy (oraz użyć ich do szablonowych wytycznych, ale o tym napiszę w następnym poście).
W ten sposób nie będzie dość kosztownego porównywania napisów, a także możliwa jest weryfikacja poprawności nazwy w czasie kompilacji jak i wygoda dla programisty w postaci podpowiedzi.

Ale tutaj jest pewien problem - potrzebna jest dalej reprezentacja tekstowa wartości typu wyliczeniowego - zarówno do zapisywania w logu ("Foo" mówi więcej niż 12) jak i API z którego korzystaliśmy również potrzebowało napisu... czyli chcemy mieć coś takiego:
enum res_id { Something_Foo, Something_Bar, Something_Foo_Foo };
const* char res_name[] = { "Something_Foo", "Something_Bar", "Something_Foo_Foo" };

Wypisanie tego w ten sposób i utrzymywanie w synchronizacji (biorąc pod uwagę tego, że osoby trzecie mogą zechcieć coś dopisać) nie wchodzi oczywiście w grę.

I tutaj pojawia się technika, którą nazywam "Generic Macro" (może jest na to jakaś inna nazwa?).

Otóż tworzymy plik o przykładowej nazwie resources.def w którym wpisujemy nasze dane wewnątrz jakiegoś w miarę sensownie nazwanego makra:

GENERIC_RESOURCE(Something_Foo)
GENERIC_RESOURCE(Something_Bar)
GENERIC_RESOURCE(SOmething_Foo_Foo)
następnie wystarczy stworzyć plik res_id.hpp w którym zdefiniujemy makro tak jak chcemy a potem dołączymy nasz plik .def :
#ifndef RES_ID_HPP__
#define RES_ID_HPP__

// this will be defined in cpp file
extern const char* res_name[];

// include resource.def by using macro to transform it to enum type
enum res_id {
    #define GENERIC_RESOURCE(X) X,
    #include "resources.def"
    #undef GENERIC_RESOURCE
    MaxResId
};

#endif //RES_ID_HPP__

I teraz w pliku res_names.cpp:
#include "res_id.hpp"

const char* res_names[] = {
    #define GENERIC_RESOURCE(X) (#X),
    #include "resources.def"
    #undef GENERIC_RESOURCE
    "\0"
};

I w ten sposób wystarczy modyfikować tylko plik resources.def a zarówno lista napisów jak i typ wyliczeniowy zostaną wygenerowane i będą zsynchronizowane.
Jak widać czasem sprytnie użyte makra mogą przełożyć się na lepszą jakość kodu dzięki zmniejszeniu ryzyka popełnienia błędu :)
Może znajdziesz jakieś inne ciekawe zastosowanie tej techniki?

środa, 1 sierpnia 2012

(Bezsensowne) Przeładowania operatorów w C++

Przy pomocy przeładowania operatorów w C++ można robić bezsensowne rzeczy, które jednych zachwycą a u innych wywołają wrogość :) Jedną z takich rzeczy jest stworzenie obsługi list (np: zakupów, TODO, aliasów) czy też listy wad i zalet w taki "naturalny" sposób:
Aliases aliasList;
aliasList.add_for("number")
    * "one" 
    * "two" 
    * "three";

aliasList.add_for("moo")
    - "foo"
    - "bar"
    - "jaj";

aliasList.add_for("no_alias"); // identity

std::cout << "three stands for " << aliasList["three"] << '\n';
List list;

list.prons_and_cons_for("headphones X")
    + "price"
    + "quality"
    + "bass"
    - "too short cable"
    - "no volume control";

list.print_info_about("headphones X"); // or list.get_cons() etc
Implementacja polega na rozbiciu na dwie klasy.
Pierwsza, najważniejsza klasa polega na tym, że akumuluje argumenty (albo w moim przypadku - posiada referencje do kontenera drugiej klasy) w zadany sposób poprzez przeładowanie operatorów zwracających referencję - tak jak przeładowuje się zwykle operatory ">>" oraz "<<", przy czym trzeba również pamiętać o priorytetach i kierunku łączenia.
Druga klasa (Aliases, List w przykładach) zajmuje się tworzeniem pierwszej klasy i zawiera kontenery i właściwą logikę.

Jeśli nie jest to jeszcze jasne, to wystarczy spojrzeć na kod (drafty):

poniedziałek, 16 lipca 2012

Analogowe literały w C++

Ostatnio w pracy troszkę się nudziło (bywa ;)), co spowodowało, że następujący kod zaczął się kompilować:
for (uint worm = I-------------I; worm >= II; worm -= I-I ) {
   cout << '*';
}

uint monsterHP =  I-----------------------I;       // 12 HP
uint monBossHP = (I-----------------------I) * 10; // 120 HP

cout <<( I-------I
         |       !
         !       !
         I-------I ).area(); 
To nie jest mój pomysł - kiedyś natrafiłem na blogu Paczesiowej ciekawy wpis gdzie znalazłem link opisujący w C++ literały analogowe - czyli zamiast podawać wartości naturalne można było narysować sobie linie, prostokąt albo sześcian w ASCII, co następnie było zamieniane na wartość liczbową.

Niestety - strona przestała działać a w cache znalazłem jedynie podgląd jak to wyglądało... więc nie miałem innego wyjścia jak spróbować zaimplementować to samemu ;)

Na razie odpuściłem sobie implementację literałów 3D - zaimplementowałem tylko linie oraz prostokąty co okazało się zaskakująco proste, przy czym nie nakładałem zbyt wysokich wymagań co do poprawności i obsługi sytuacji brzegowych :)

Cała sztuczka polega na odpowiednim przeładowaniu operatorów. Zauważ, że tak naprawdę linię można ograniczyć dwoma obiektami (I), których wartość ulega zmianie poprzez użycie operatora "--" oraz połączenie ich przez binarny operator "-".

Analogicznie jest dla 2D - to po prostu 2 linie z tym, że jedna jest modyfikowana przez "!" (zwiększa wysokość) a potem linia + linia z wysokością są scalane przez operator binarny "|".

Myślę, że rozjaśni się wszystko jak pokażę kod (testowany na VC2k5 i ideone).

czwartek, 14 czerwca 2012

Pierwsze proceduralne gfx w < 2kB

Ostatnio na Warsztacie odbyło się compo procedural gfx 4kB w którym postanowiłem wziąć udział, mimo, że nie mogłem poświęcić dużo czasu.

W ~1,75kB zmieściłem renderowanie takiego oto obrazka zatytułowanego "Hi Hydra":

Niestety - pierwszy shader napisałem tydzień temu więc okazało się, że mój kod jest syfiasty i nie działa niektórym - a czasem potrafi nawet wywalić sterowniki! Nie mniej i tak miło, że udało mi się coś osiągnąć.

Nitro namawia mnie do dopracowania i wysłania jej na jakieś party... pewnie tak zrobię ;)

Paczka (uruchamiać na własne ryzyko! duże prawdopodobieństwo, że nie zadziała i może mieć straszne skutki uboczne!) do pobrania jest tutaj

Wrzucam też kilka ujęć WIP jak eksperymentowałem :)

niedziela, 27 maja 2012

Sesja ;)


Nic dodać, nic ująć - koniec semestru zawsze wiąże się z brakiem czasu. Nie mniej po zakończeniu tego najcięższego okresu mam zamiar napisać posty związane z programami nad którymi ostatnio pracuję:

1. Software'owy ray marcher w C++ - dorobiłem do niego GUI i muszę powiedzieć, że Qt jest naprawdę niezłe. Obecnie podpinam swój "język" opisu sceny - na zdjęciu, w programie po lewej jest kod, co 1,5 s jest parsowany i jest generowany mały podgląd. Można oczywiście wyrenderować w większym rozmiarze i zapisać. Obecnie podpinam światło i materiały a potem zamierzam dodać jeszcze kilka drobiazgów i program będzie się chyba nadawał na zaliczenie i będę mógł się zająć ciekawszymi rzeczami, czyli GPU :)




Tak program póki co wygląda :)
Drobny eksperyment z modyfikacją uzyskanych odległości











2. Ten program już oddałem - jest to program równoległy w C#, korzystający z algorytmu mrówkowego do rozwiązania jednomaszynowego problemu szeregowania zadań z wagami kar. Zamierzam poświęcić mu osobny wpis bo okazał się naprawdę ciekawy w implementacji.

Rozwiązanie optymalne: 917, program znalazł coś bliskiego dla problemu rozmiaru 40!

piątek, 13 kwietnia 2012

Koniec (?) projektu z blendera ;)

Oto i sam Pan Raptor ;)
W końcu skończyłem swój projekt na kurs Blendera (jeden z dwóch więksszych) :)

Tekstura sux i jeszcze mógłbym trochę rzeczy dorobić ale mi się już nie chce i czasu też nie mam za bardzo (hmm ale może jeszcze kiedyś mnie najdzie wena - chętnie bym w sumie urozmaicił środowisko.

Po lewej - sam dinozaur, a poniżej - w środowisku naturalnym z lekkim postprocessingiem w Photoshopie (Compositor z Blendera niestety nie dał rady bo nie mogłem pozbyć się białej otoczki z kanału alpha).

Oto i efekt końcowy - welociraptor w środowisku w którym go w ogóle nie powinno być :P