28th February 2009, 02:19 am
Właśnie mija tydzień od zakończenia dwutygodniowego pobytu w domu w czasie końcówki sesji i przerwy egzaminacyjnej. W tym czasie rozpocząłem przepisywania mojego “silnika” od nowa, tym razem w oparciu o interfejsy. Są one bardzo poręczne, gdy chce się ukryć implementację klas wewnątrz statycznych, bądź dynamicznych bibliotek. Głównym założeniem przy projektowaniu drugiej wersji NIne było właśnie ukrycie implementacji wewnątrz statycznej biblioteki .lib. Dzięki takiemu postępowaniu biblioteka wygląda ładnie i pozwala ładnie rozszerzać własną funkcjonalność.
Drugim zagadnieniem poruszanym w tej notce są uchwyty, które same zarządzają licznikiem danego zasobu.
Z początku implementację uchwytów opierałem na artykule znajdującym się w książce “Game Programming Gems”, jednak problemem okazało się zmuszenie tychże uchwytów do sprzątania po sobie zasobów. Na szczęście rozwiązanie okazało się proste, ponieważ wystarczyło umieścić w klasie uchwytu wskaźnik do menadżera zasobów.
Poniżej znajduje się moja implementacja takiego uchwytu:
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(Dword handle, MHandleMgr* manager) : m_handle(handle), m_manager(manager)
{}
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle)
{ m_manager->Copy(src.m_handle); }
// Destruktor
~AutomaticHandle()
{ m_manager->Release(m_handle); }
// Operator przypisania
const AutomaticHandle& operator =(const ManagedHandle& src)
{ m_manager->Release(m_handle);
m_handle = src.m_handle;
m_manager->Copy(src.m_handle);
return src; }
// Operator Dword
operator Dword() const
{ return m_handle; }
/*
...
*/
private:
// Uchwyt
Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
}; |
template< typename TAG>
class AutomaticHandle
{
public:
// Podstawowy konstruktor
AutomaticHandle(Dword handle, MHandleMgr* manager) : m_handle(handle), m_manager(manager)
{}
// Konstruktor kopiujący
AutomaticHandle(const AutomaticHandle& src) : m_manager(src.m_manager), m_handle(src.m_handle)
{ m_manager->Copy(src.m_handle); }
// Destruktor
~AutomaticHandle()
{ m_manager->Release(m_handle); }
// Operator przypisania
const AutomaticHandle& operator =(const ManagedHandle& src)
{ m_manager->Release(m_handle);
m_handle = src.m_handle;
m_manager->Copy(src.m_handle);
return src; }
// Operator Dword
operator Dword() const
{ return m_handle; }
/*
...
*/
private:
// Uchwyt
Dword m_handle;
// Wskaźnik do managera
MHandleMgr* m_manager;
};
Jak widać klasa zawiera dodatkową zmienną – wskaźnik na klasę menadżera ustawiany przy konstrukcji. Klasa menadżera musi zawierać używane metody, które wewnątrz zarządzają licznikiem odwołań na dany zasób, a w momencie jego spadku do 0, likwidować dany zasób.
12th February 2009, 01:00 am
Nawiązując do poprzedniej notki chcę tylko zamieścić jak wyglądają klasy, które napisałem i czemu są takie fajne:
// Interfejs
class IMesh
{
public:
/*
...
*/
// Funkcja aktualizująca mesha
virtual void SetMesh(LPD3DXMESH mesh) = 0;
// Funkcja dodająca materiał do wektora
virtual void AddMaterial(const Material& mat) = 0;
// Funkcja zwraca materiał o podanym indeksie
virtual Material GetMaterial(Dword index) = 0;
/*
...
*/
};
// Główna klasa Mesh
class Mesh : public IMesh
{
public:
// Konstruktor
Mesh();
// Destruktor
~Mesh();
public:
/* Funkcje używane do rysowania modelu
...
*/
protected:
/*
...
*/
// Funkcja aktualizująca mesha
virtual void SetMesh(LPD3DXMESH mesh) = 0;
// Funkcja dodająca materiał do wektora
virtual void AddMaterial(const Material& mat) = 0;
// Funkcja zwraca materiał o podanym indeksie
virtual Material GetMaterial(Dword index) = 0;
/*
...
*/
};
// Przykład loadera
class X_file
{
public:
// Konstruktor
X_file(IMesh* mesh) { m_mesh = mesh; }
// Funkcja ładująca
RESULT LoadMesh(String filename)
{
LPD3DXMESH mesh;
/*
... cuda-niewidy...
*/
m_mesh->AddMesh(mesh); // Wywołanie funkcji interfejsu
}
private:
// Obiekt na którym działam
IMesh* m_mesh;
}; |
// Interfejs
class IMesh
{
public:
/*
...
*/
// Funkcja aktualizująca mesha
virtual void SetMesh(LPD3DXMESH mesh) = 0;
// Funkcja dodająca materiał do wektora
virtual void AddMaterial(const Material& mat) = 0;
// Funkcja zwraca materiał o podanym indeksie
virtual Material GetMaterial(Dword index) = 0;
/*
...
*/
};
// Główna klasa Mesh
class Mesh : public IMesh
{
public:
// Konstruktor
Mesh();
// Destruktor
~Mesh();
public:
/* Funkcje używane do rysowania modelu
...
*/
protected:
/*
...
*/
// Funkcja aktualizująca mesha
virtual void SetMesh(LPD3DXMESH mesh) = 0;
// Funkcja dodająca materiał do wektora
virtual void AddMaterial(const Material& mat) = 0;
// Funkcja zwraca materiał o podanym indeksie
virtual Material GetMaterial(Dword index) = 0;
/*
...
*/
};
// Przykład loadera
class X_file
{
public:
// Konstruktor
X_file(IMesh* mesh) { m_mesh = mesh; }
// Funkcja ładująca
RESULT LoadMesh(String filename)
{
LPD3DXMESH mesh;
/*
... cuda-niewidy...
*/
m_mesh->AddMesh(mesh); // Wywołanie funkcji interfejsu
}
private:
// Obiekt na którym działam
IMesh* m_mesh;
};
Nie lubię się rozpisywać. Jak można zauważyć dzięki interfejsowi IMesh można załadować dowolny model do klasy Mesh, ponieważ są publiczne wszystkie funkcje umożliwiające to zadanie, natomiast klasa Mesh posiada publiczne funkcje służące tylko i wyłącznie do rysowania modelu. Dzięki takiemu rozwiązaniu kod jest przejrzysty.
8th February 2009, 04:49 am
Minęło trochę czasu od mojej ostatniej notki, ale co poradzić, studia są trochę wymagające, więc trzeba się czasem pouczyć, zwłaszcza, że jest to styczeń czyli ostatni miesiąc przed sesją. W momencie, gdy piszę tę notkę, upływa właśnie pierwszy tydzień tej najbardziej nielubianej przez studentów pory roku :). Wracając jednak do tematyki tego devBloga należałoby powiedzieć czym ciekawym zajmowałem się przez ten czas, przecież nie samą nauką człowiek żyje.
Postanowiłem dodać do mojego silnika możliwość wczytywania różnego rodzaju plików z meshami. Na początek napisałem prostą klasę opakowującą interfejs ID3DXMesh, w której umieściłem strukturę przechowującą materiały danego mesha. Następnie napisałem funkcje automatyzujące odczyt i zapis plików w formacie .x. Schody zaczęły się, gdy chciałem dodać obsługę dodatkowego formatu – zdecydowałem, że będą to pliki .obj (Object files). OBJ są to pliki tekstowe, przechowujące informacje o całym meshu (fajna specyfikacja tego formatu znajduje się tutaj), z którymi w parze są jeszcze pliki .mtl, gdzie trzymane są informacje o materiałach dla danego subsetu (więcej info tutaj). Najważniejszą sprawą w całym tym przedsięwzięciu było napisanie parsera dla tego formatu. Na szczęście z pomocą przychodzi Microsoft DirectX SDK, w którym znajduje się przykładowy program (“MeshFromOBJ”) wczytujący pliki OBJ.
Pisząc klasy obsługujące różne modele chciałem, aby istniała możliwość konwersji między tymi formatami. Powstała więc koncepcja dwóch rodzajów klas – klasa Mesh oraz klasy obsługujące formaty. Klasy te mają być od siebie w jak największym stopniu niezależne, czyli ma istnieć możliwość dodawania funkcjonalności do klasy Mesh bez konfliktu z klasami wczytującym oraz musi istnieć możliwość pisania dodatkowych klas obsługujących różne formaty. Szczerze mówiąc, pierwszy raz zetknąłem się z takim problemem, ale na szczęście udało mi się znaleźć proste i eleganckie rozwiązanie. Mianowicie zastosowałem prosty interfejs, po którym dziedziczy klasa Mesh, z tym wyjątkiem, że klasa interfejsu posiada funkcje abstrakcyjne public, a klasa Mesh definiuje te funkcje jako protected, bądź private. Dzięki takiemu podejściu klasy korzystające z interfejsu, mogą operować na tych funkcjach, a klasa Mesh ma te funkcje ukryte :).