Raytracer

Jak zwykle po długiej nieobecności w końcu pojawia się kolejna notka. Tym razem chciałbym zaprezentować Raytracer, który napisałem w ramach laboratorium z Zaawansowanej grafiki komputerowej. Program oprócz standardowego śledzenia promieni potrafi również generować teksturę proceduralną cegły dla podanego atrybutu (tę część napisałem z kolegą Adamem Jordankiem) oraz prowadzić obliczenia na wielu komputerach wykorzystując model klient-serwer.

Aplikacja do renderowania grafiki wykorzystuje między innymi moją własną biblioteczkę matematyczną, proste kontenery, a oprócz tego – do obsługi klienta i serwera –  sockety i wątki (w zależności od systemu ich odpowiednie implementacje).  Implementacja Raytracera wykorzystuje algorytm przecięcia promienia z trójkątem oraz implementację kd-tree znalezioną na tej stronie, które według mojego przekonania są na tyle wydajne i stabilne, że nie było sensu tego modyfikować. Wykorzystywany model oświetlenia jest efektem prób i błędów, ale myślę że wygląda całkiem znośnie. Zaimplementowane są wszystkie podstawowe elementy takie jak cienie, odbicia czy refrakcja.

Z ciekawszych rzeczy, które można znaleźć w kodzie to:

  • obsługa linii poleceń, która opiera się przede wszystkim na liście poleceń, gdzie każde polecenie zawiera nazwę i opis parametru oraz wskaźnik do funkcji, która implementuje jego obsługę.
  • implementacja klienta i serwera w oparciu o stany oraz przydział zadań do poszczególnych klientów
  • wczytywanie plików oparte o stany (łatwość dodawania nowych elementów)

Więcej informacji i paczkę można znaleźć tutaj.

Operacje na systemie plików w systemach Windows i Linux w C++

Tym razem notka o tym, w jaki sposób można wykonywać różne operacje w systemie plików w wymienionych w temacie systemach. Oczywiście każdy programista C/C++ zna funkcje wykonujące podstawowe operacje na plikach, takie jak:

  • odczyt, modyfikacja, zapis (funkcja fopen i poboczne oraz klasy fstream)
  • zmiana nazwy pliku lub katalogu (funkcja rename())
  • usunięcie pliku (funkcja remove())

Nieco więcej problemów sprawiają pozostałe operacje, takie jak np. pobranie pełnej ścieżki do pliku czy przeglądanie listy plików w katalogu, ponieważ w bibliotece standardowej nie ma do tego celu odpowiednich funkcji, zatem należy ich szukać w API systemu. Niestety co API to różne funkcje, ale zdarzają się i takie, które istnieją w obu systemach.

Na początek pobieranie pełnej ścieżki do bieżącego katalogu, czyli funkcja getcwd. Jej prototyp w systemie Windows jest następujący:

#include <direct.h>
char* _getcwd(char* _DstBuf, int _SizeInBytes);

a w systemie Linux:

#include <unistd.h>
char* getcwd(char* _DstBuf, int _SizeInBytes);

Jak widać prototyp obu funkcji jest identyczny, więc nic nie stoi na przeszkodzie utworzenia sobie odpowiedniego aliasu nazwy za pomocą typedef.

Trochę lepiej jest z funkcją stat() i jej strukturą o tej samej nazwie (thx C…, typedef needed), która istnieje w obu systemach. Funkcja ta służy do pobierania pełnych informacji o pliku, czyli np. daty modyfikacji, typu czy rozmiaru. Jej przykładowe użycie wygląda następująco:

1
2
3
4
5
6
7
8
9
10
11
#include <sys/stat.h>
typedef struct stat FileStatus; // thx C
 
/*...*/
FileStatus fileStatus;
if(stat(szFullPath.c_str(), &fileStatus) < 0)
{
    return;
}
 
unsigned int uFileSize = fileStatus.st_size;	// Pobranie rozmiaru pliku

Nieco gorzej jest z wyciąganiem listy plików w katalogu, ponieważ w tym przypadku oba systemy mają do tego różne funkcje. W przypadku systemu Windows wygląda to tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <windows.h>
/*...*/
 
WIN32_FIND_DATA info;
HANDLE hFind = FindFirstFile(szDirPath, &info);
 
if(INVALID_HANDLE_VALUE == hFind)
{
    printf("Error");
    return;
}
 
do
{
    if((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
    {
        printf("Directory: %s\n", info.cFileName);
    }
    else
    {
        printf("File: %s\n", info.cFileName);
    }
}
while(FindNextFile(hFind, &info) != 0);
 
FindClose(hFind);

A w systemie Linux tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <dirent.h>
#include <string.h> // c-string operations
typedef struct dirent DirEntry;
typedef struct stat DirEntryStat; // thx C again
 
DIR* pDir;
pDir = opendir(szDirPath);
if(pDir == null)
{
    printf("Error");
    return;
}
 
DirEntry* pDirent;
DirEntryStat dirEntryStat;
while((pDirent = readdir(pDir)) != null)
{
    char buffer[255];
    strcpy(buffer, szDirPath);
    strcat(buffer, pDirent->d_name);	// Do ponizszej funkcji potrzebna jest sciezka z biezacego katalogu
 
    if(stat((buffer, &dirEntryStat) < 0)
    {
        printf("Error");
        continue;	// file corrupted... NEXT!
    }
 
    if((dirEntryStat.st_mode & S_IFDIR) != 0)	// Katalog
    {
        printf("Directory: %s\n", pDirent->d_name);
    }
    else if((dirEntryStat.st_mode & S_IFREG) != 0)	// Plik
    {
        printf("File: %s\n", info.pDirent->d_name);
    }
}
 
closedir(pDir);

Można zauważyć, że funkcja z WinAPI jest nieco przyjaźniejsza, ponieważ operuje na uchwycie do żądanego katalogu, w przeciwieństwie do funkcji Linuksowej, która po prostu zwraca nazwę tego co siedzi w katalogu i nic więcej.

Struktura katalogów w projekcie

Czasami istnieje potrzeba pisania jednego projektu w różnych środowiskach programistycznych, tym bardziej jeśli korzystamy z systemu kontroli wersji (np. SVN,  GIT) i chcemy skompilować projekt z założenia multiplatformowy na innym systemie operacyjnym. W takim przypadku fajnie by było, aby projekt miał strukturę katalogów niezależną od IDE, ale jak powinna ona wyglądać? Według mnie tak:

  • include
  • src
  • workdir
  • projects
  • libs

Katalog include powinien zawierać tylko pliki nagłówkowe, które będą dołączane w innych projektach. Tak naprawdę jest on tutaj tylko ze względu na projekt biblioteki (np. lib, dll, itp.), więc w przypadku kompilacji do pliku wykonywalnego, można ten katalog pominąć, wtedy wszystkie pliki nagłówkowe powinny znaleźć się w katalogu src. Katalog src jest przeznaczony tylko dla plików źródłowych bieżącego projektu, które mogą być uporządkowane w różnych podkatalogach. W workdir powinna się znaleźć cała struktura katalogów docelowego programu, czyli wszystkie pliki i katalogi, które będą dostarczane ze skompilowanym plikiem wykonywalnym. Katalog libs służy do przechowywania wszystkich dołączanych bibliotek używanych w aplikacji. Dla każdej z nich powinien znaleźć się odpowiedni katalog a w nim jeszcze dwa – include i lib. Ostatnim katalogiem jest projects, który jest przeznaczony do przechowywania plików projektów dla każdego IDE (oczywiście każdy w osobnym katalogu).

Taka struktura jest przejrzysta i niezależna od stosowanego IDE. Zresztą nie jest to mój pomysł, ponieważ jest on od dawna stosowany w środowiskach linuksowych, ale w nieco innej postaci. Kluczowe jest po prostu trzymanie plików źródłowych w jednym katalogu niezależnym od stosowanego środowiska.

Ubuntu – nowa zabawka

Któż by mógł przypuszczać, że Linux jest taką fajną zabawką. Okazuje się jednak, że ten system operacyjny może sprawić dużo frajdy. Ostatnio dużo czasu spędzam na Ubuntu i muszę stwierdzić, że jestem z niego zadowolony. Wybrałem tę dystrybucję, ponieważ jest bardzo prosta w obsłudze, a konkurencyjna Fedora miała problemy z instalacją. Sam system jest 64-bitowy, gdyż chciałem zobaczyć różnicę między nim, a wersją 32-bitową, którą miałem wcześniej. Wrażenie jest czysto subiektywne (wydaje się lepiej). Ze sterownikami nie było żadnych problemów, ponieważ Ubuntu zawiera wszystkie, jakie są potrzebne.

Wygląd Ubuntu można bardzo prosto dostosować, wystarczy tylko aby w systemie znalazł się dekorator okien Emerald oraz Compiz-Fusion. Dzięki temu tandemowi można Linuksa przerobić z wyglądu na Windowsa, a nawet jeszcze lepiej. Mój system wygląda jak na zdjęciu :).Wyglad

Inną rzeczą, która mnie w Linuksie zainsteresowała, to możliwość uruchamiania aplikacji windowsowych, szczególnie że udało mi się załapać na darmową wersję aplikacji CrossOver. Osobiście korzystam z dwóch wersji: Linux i Games, dlatego mogę przyznać, że są to bardzo dobre aplikacje.