[Tutorial] Izrada 3D/scenegraph enginea

poruka: 66
|
čitano: 19.969
|
moderatori: Lazarus Long, XXX-Man, vincimus
+/- sve poruke
ravni prikaz
starije poruke gore
16 godina
neaktivan
offline
RE: [Tutorial] Izrada 3D/scenegraph enginea
woodgamesfx kaže...

Da siguran sam da koristim sintaksu C# za skriptiranje :)

Da ne zagađujemo temu, ovdje sam ti odgovorio na ovo...

Pravi znak inteligencije nije znanje, već mašta - Albert Einstein
14 godina
neaktivan
offline
[Tutorial] Izrada 3D/scenegraph enginea

Onaj kod template od jučer :

 

/**************************

 * Includes

 *

 **************************/

 

#include <windows.h>

#include <gl/gl.h>

 

 

/**************************

 * Function Declarations

 *

 **************************/

 

LRESULT CALLBACK WndProc (HWND hWnd, UINT message,

WPARAM wParam, LPARAM lParam);

void EnableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC);

void DisableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC);

 

 

/**************************

 * WinMain

 *

 **************************/

 

int WINAPI WinMain (HINSTANCE hInstance,

           HINSTANCE hPrevInstance,

           LPSTR lpCmdLine,

           int iCmdShow)

{

   WNDCLASS wc;

   HWND hWnd;

   HDC hDC;

   HGLRC hRC;     

   MSG msg;

   BOOL bQuit = FALSE;

   float theta = 0.0f;

 

   /* register window class */

   wc.style = CS_OWNDC;

   wc.lpfnWndProc = WndProc;

   wc.cbClsExtra = 0;

   wc.cbWndExtra = 0;

   wc.hInstance = hInstance;

   wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);

   wc.hCursor = LoadCursor (NULL, IDC_ARROW);

   wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);

   wc.lpszMenuName = NULL;

   wc.lpszClassName = "GLSample";

   RegisterClass (&wc);

 

   /* create main window */

   hWnd = CreateWindow (

    "GLSample", "OpenGL Sample", 

    WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE,

    0, 0, 256, 256,

    NULL, NULL, hInstance, NULL);

 

   /* enable OpenGL for the window */

   EnableOpenGL (hWnd, &hDC, &hRC);

 

   /* program main loop */

   while (!bQuit)

   {

     /* check for messages */

     if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

     {

       /* handle or dispatch messages */

       if (msg.message == WM_QUIT)

       {

         bQuit = TRUE;

       }

       else

       {

         TranslateMessage (&msg);

         DispatchMessage (&msg);

       }

     }

     else

     {

       /* OpenGL animation code goes here */

 

       glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

       glClear (GL_COLOR_BUFFER_BIT);

 

       glPushMatrix ();

       glRotatef (theta, 0.0f, 0.0f, 1.0f);

       glBegin (GL_TRIANGLES);

       glColor3f (1.0f, 0.0f, 0.0f);   glVertex2f (0.0f, 1.0f);

       glColor3f (0.0f, 1.0f, 0.0f);   glVertex2f (0.87f, -0.5f);

       glColor3f (0.0f, 0.0f, 1.0f);   glVertex2f (-0.87f, -0.5f);

       glEnd ();

       glPopMatrix ();

 

       SwapBuffers (hDC);

 

       theta += 1.0f;

       Sleep (1);

     }

   }

 

   /* shutdown OpenGL */

   DisableOpenGL (hWnd, hDC, hRC);

 

   /* destroy the window explicitly */

   DestroyWindow (hWnd);

 

   return msg.wParam;

}

 

 

/********************

 * Window Procedure

 *

 ********************/

 

LRESULT CALLBACK WndProc (HWND hWnd, UINT message,

              WPARAM wParam, LPARAM lParam)

{

 

   switch (message)

   {

   case WM_CREATE:

     return 0;

   case WM_CLOSE:

     PostQuitMessage (0);

     return 0;

 

   case WM_DESTROY:

     return 0;

 

   case WM_KEYDOWN:

     switch (wParam)

     {

     case VK_ESCAPE:

       PostQuitMessage(0);

       return 0;

     }

     return 0;

 

   default:

     return DefWindowProc (hWnd, message, wParam, lParam);

   }

}

 

 

/*******************

 * Enable OpenGL

 *

 *******************/

 

void EnableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC)

{

   PIXELFORMATDESCRIPTOR pfd;

   int iFormat;

 

   /* get the device context (DC) */

   *hDC = GetDC (hWnd);

 

   /* set the pixel format for the DC */

   ZeroMemory (&pfd, sizeof (pfd));

   pfd.nSize = sizeof (pfd);

   pfd.nVersion = 1;

   pfd.dwFlags = PFD_DRAW_TO_WINDOW | 

    PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;

   pfd.iPixelType = PFD_TYPE_RGBA;

   pfd.cColorBits = 24;

   pfd.cDepthBits = 16;

   pfd.iLayerType = PFD_MAIN_PLANE;

   iFormat = ChoosePixelFormat (*hDC, &pfd);

   SetPixelFormat (*hDC, iFormat, &pfd);

 

   /* create and enable the render context (RC) */

   *hRC = wglCreateContext( *hDC );

   wglMakeCurrent( *hDC, *hRC );

 

}

 

 

/******************

 * Disable OpenGL

 *

 ******************/

 

void DisableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC)

{

   wglMakeCurrent (NULL, NULL);

   wglDeleteContext (hRC);

   ReleaseDC (hWnd, hDC);

}

 

http://graffiti-jam.blogspot.com/
Poruka je uređivana zadnji put sri 15.9.2010 8:40 (naxeem).
 
1 0 hvala 2
15 godina
neaktivan
offline
Iscrtavanje trokuta

Hullo.

Opet petak! Kako to vrijeme leti!
Mi smo u nekom laganom crunchu, nije da bas umirem od posla, ali ima gomila malih taskova koje moram porijesavati tako da nisam stigao nista konkretno razraditi, tako da cemo danas (kao za petak :-D) opet samo jednostavno iscrtavanje trokuta na ekran :-)

Prvo, jos jedna zahvala woodgamesfx na Win templateu, koji ce vjerujem vecina koristiti. Ja cu sastaviti kao medju-korak XWindows template, pa cemo u jednoj od narednih lekcija 'odlijepiti' prvo display system u abstrakciju, pa onda pomalo nakon toga renderer. Kao sto je spomenuto, primarni fokus je sada vidjeti da sav ovaj kod nesto i radi, osim testova :-)

Pa krenimo.

Prije svega, treba nam jos jedan konstruktor u meshu, koji ce nam olaksati izradu ovakvih testova. Zelimo biti sposobni primiti obican array vertexa koji cemo iskopirati u internu kolekciju.
Dekaracija:
Mesh(const math::Vector3f* verticesBuffer, const int size);

Implementacija je jednostavno kopiranje:
Mesh::Mesh(const math::Vector3f* verticesBuffer, const int size) :
       name("UNNAMED_OBJECT"),
       localTranslation(0.0F, 0.0F, 0.0F),
       rotationAxis(0.0F, 1.0F, 0.0F),
       rotationAngle(0.0F),
       scale(1.0F, 1.0F, 1.0F),
       defaultColor(1.0F, 0.0F, 0.0F, 1.0F)
{
    for (int i = 0; i < size; ++i)
       vertices.push_back(verticesBuffer[i]);
}

U praksi, predavanje cistih buffera je los potez, jer je tesko odnosno nemoguce provjeriti da je broj elemenata u bufferu tocno toliko velik koliko tvrdimo kroz drugi parametar 'size'. Zbog toga cemo obecati sami sebi da u produkcijskom kodu _necemo_ koristiti ovaj konstruktor, vec samo za testove.
Kod u testovima donekle mozemo napraviti citljivijim i manje 'hackish' ukoliko uskoristimo boost array - pokazat cu kako kasnije.

Sad idemo srediti nasu XWindow main klasu.
Pritom ne zaboravite da iako se ovaj file trenutno nalazi u produkcijskom kodu, on nije NISTA DRUGO doli obican interaktivni test. S vremenom, prepravit cemo build skriptu da nas produkcijski kod (src/main) builda u dinamicki library, a sve testove drzat cemo u src/test.
Trenutno, nas Tutorial1.cpp sadrzi obicni hello world, zato da build skripta ne puca. Sad, linuksasi, zamijenite to slijedecim kodom, a windowsasi preuzmite template od woodgamesfx iz prethodnog posta. Necu previse objasnjavati pojedinosti izrade XWindow prozora jer je to jedna druga tema, no cisto zlurado primjetite koliko je XWin API intuitivniji od Windows API-ja :-D :-D :-D
Toliko o 'genijalnosti' M$ programera.


S vremenom cemo pokusati zamjeniti ova dva filea sa jednim cross-platform libraryem, primjerice wxwidgets ili GTK, tako da svi imamo sve jednako.
Sad, kod:
#include <X11/X.h>
#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include <boost/shared_ptr.hpp>
#include <boost/array.hpp>
#include <math/Vector.hpp>
#include <model/Mesh.hpp>
#include <iostream>

using namespace model;
using namespace boost;
using namespace std;
using namespace math;

Display *dpy;
Window root;
GLint att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };
XVisualInfo *vi;
Colormap cmap;
XSetWindowAttributes swa;
Window win;
GLXContext glc;
XWindowAttributes gwa;
XEvent xev;

shared_ptr<Mesh> object;
const ColorRGBA clearColor(0.0F, 0.0F, 0.0F, 1.0F);

void Clear(const ColorRGBA& color) {
    glClearColor(color[0], color[1], color[2], color[3]);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void DrawGLScene() {
    Clear(clearColor);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0F, 1.0F, -1.0F, 1.0F, 1.0F, 20.0F);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F);

    object->Render();
}

void EventLoop() {
    while (1) {
       XNextEvent(dpy, &xev);

       if (xev.type == Expose) {
          XGetWindowAttributes(dpy, win, &gwa);
          glViewport(0, 0, gwa.width, gwa.height);
          // ********* The Render Call
          DrawGLScene();
          // ********* The Render Call End
          glXSwapBuffers(dpy, win);

       } else if (xev.type == KeyPress) {
          glXMakeCurrent(dpy, None, NULL);
          glXDestroyContext(dpy, glc);
          XDestroyWindow(dpy, win);
          XCloseDisplay(dpy);
       }
    }
}

void InitializeDisplaySystem() {
    dpy = XOpenDisplay(NULL);
    if (dpy == NULL) {
       cerr << "\t cannot connect to X server" << endl;
       exit(1);
    }

    root = DefaultRootWindow(dpy);
    vi = glXChooseVisual(dpy, 0, att);
    if (vi == NULL) {
       cerr << "\t no appropriate visual found" << endl;
       exit(2);
    } else {
       cerr << "\t visual " << ((void*) vi->visualid) << " selected" << endl; // %p creates hex output like glxinfo
    }

    cmap = XCreateColormap(dpy, root, vi->visual, AllocNone);

    swa.colormap = cmap;
    swa.event_mask = ExposureMask | KeyPressMask;

    win = XCreateWindow(dpy, root, 0, 0, 600, 600, 0, vi->depth, InputOutput,
          vi->visual, CWColormap | CWEventMask, &swa);

    XMapWindow(dpy, win);
    XStoreName(dpy, win, "Scenegraph tutorial");
}

void InitializeOpenGL() {
    glc = glXCreateContext(dpy, vi, NULL, GL_TRUE);
    glXMakeCurrent(dpy, win, glc);
    glEnable(GL_DEPTH_TEST);
}

void CreateMesh() {
    array<Vector3f, 3> triangleVerts = {
          Vector3f(-0.5F, -0.5F, 0.0F),
          Vector3f(0.5F, -0.5F, 0.0F),
          Vector3f(0.0F, 0.5F, 0.0F)
    };

    object = shared_ptr<Mesh>(new Mesh(triangleVerts.c_array(), triangleVerts.size()));
    object->SetDefaultColor(ColorRGBA(1.0F, 0.0F, 0.0F, 1.0F));
}

int main(int argc, char** argv) {
//    cout << argv[0] << " Version " << TUTORIAL_VERSION_MAJOR << "."
//          << TUTORIAL_VERSION_MINOR << "." << TUTORIAL_VERSION_PATCH << endl;

    InitializeDisplaySystem();
    InitializeOpenGL();
    CreateMesh();
    EventLoop();

    return 0;
}

Kratko objasnjenje.
S obzirom da pocinjemo s dinamickom alokacijom objekata, zavlacimo rukav u boost i izvlacimo jos jedno njihovo genijalno rijesenje nazvano boost::shared_ptr. Shared pointer je u biti pametan reference counter na dinamicki alociran objekt, i vrlo jednostavan nacin za unistavanje memory leakova. Shared pointer se u praksi mora _ekskluzivno_ koristiti, a sam objekt je vrlo lightweight da jednostavno nema smisla razmisljati o tome da li napraviti shared_ptr ili obican pointer.
U svojoj glavi, obrisite keyworde 'delete' i 'delete[]'. Ne postoje. Alokacija dinamickih objekata u C++ se moze izvrsiti na samo jedan nacin:
shared_ptr<ExampleObject> exampleObject(new ExampleObject());

 

Sad to treba ponavljati sebi 100 puta na dan, te pritom svaki put lupiti petama o pod. I to je to. Ne treba brinuti o brisanju, jer ce shared_ptr automatski obrisati objekt kad zadnji pointer koji ga 'drzi' izadje iz scopea.
OK? Nema rucnih pointera, deal? Super.

Nadalje, radimo jednu ogromnu gresku koja se moze progutati oko teksta - globalne varijable. Nakon sto smo uspjeli sebe pogledati u ogledalo i nekako si oprostiti, primjetit cemo da je main metoda vrlo jednostavna i vrlo high-level. Tocno onako kako treba biti. Main metoda u praksi ne bi smjela sadrzavati vise od 5-6 metoda: init crash handler,init core, init logging, init display, event loop, destroy all -  to je primjerice naziv metoda u tocnom slijedu koje bi trebale biti pozvane u main-u.
Nemojte crammati kod u main.
Kad netko ucita vas kod, prvo gdje ce zaviriti je main. Ukoliko taj 'netko' trazi specificnu funkciju, najjednostavnije trazenje nije obicno linearno bruteforsiranje, nego tree search. Pa se onda potrudimo lijepo rastaviti nas kod u stablo. Kad covjek zaviri u nas trenutni main, jednostavno mu je potraziti gdje je sta: da li ga interesiraju inicijalizacije, ili nesto sto se desava dok se aplikacija vrti? Nakon toga, moze slijediti bilo koju od tih metoda jednostavnije.

Osnova dobrog programiranja, i vrlo bitan skill svakog programera nisu trikovi, nego pravilno imenovanje svojih konstrukta. Varijable, metode, klase, funkcije, namespaceovi. Idealan kod je onaj koji __NEMA__ komentara, jer su komentari bespotrebni. Jedini nacin za postici takvu citljivost koda je ako tezimo tome da se nas kod cita poput obicnog engleskog jezika.
Nemojte pasti u zamku premature optimizacije, u stilu "Pa, ovu metodu pozivam samo jednom, napravit cu rucni inline" - nema potrebe. Compiler jako dobro zna da se ta metoda poziva samo jednom, i inlineirat ce on to sam, ako smatra da je potrebno.

No, nakon kratkog detoura, dalje - u metodi CreateMesh vidimo kako je potrebno pravilno koristiti konstruktore i/ili funkcije koje traze plain buffere. Treba obavezno kreirati boost array koji trazi staticki definiranu velicinu, te koristiti c_array() i size() metode. Mi smo napravili obican trokut u centru koordinatnog sistema i potrudili se da bude obojan u crveno.

Unutar EventLoop metode, render call je oznacen komentarom. Taj call je potrebno uglaviti u Windows template tamo gdje je komentarima oznaceno:
/* OpenGL animation code goes here */

Potrebno je komentirati kod koji rotira trokut, te jednostavno dodati:
...
    else {
       DrawGLScene();
       SwapBuffers (hDC);
       Sleep(1);
    }
...

To je to!
Ajmo, compile, i pokretanje! Crveni trokut bi morao biti negdje na ekranu.

Cya do slijedeceg puta. Pitanja su uvijek dobrodosla.

I have got no money, I have got no power, I have got no fame... I have my strong beliefs...
Poruka je uređivana zadnji put pon 27.9.2010 19:43 (Deus ex machina).
 
4 0 hvala 3
15 godina
neaktivan
offline
Upotpunjavanje scenegrapha Node klasom

<insert generic hello statement here/>

    Nakon sto smo uspjesno prikazali trokut na ekranu, dokazali smo da nasa ideja Mesh koncepta funkcionira sasvim dobro kroz klasu. Mesh je trenutno singularna jedinica, odnosno nema kod koji bi je pretvorio u element nekakve kolekcije podataka - tj. stabla. Mesh bi mogao biti smjesten u template kolekcije STL-a ili custom kolekcije, no mi zelimo da Mesh bude punopravni clan naseg scenegrapha, odnosno, njegov 'leaf'.
    Zasto je to potrebno, vec je spomenuto: segmentiranje kalkulacija u lokalni prostor, zbog ocuvanja preciznosti.

    Prije svega, potrebno je koncipirati tipicni element stabla koji moze ali ne mora imati parenta, i moze ali ne mora imati neodredjen broj djece. Odlucili smo se na neodredjen broj zbog toga jer iako koncepti quadtree i octree sa sobom donose interesantne algoritme zbog svojeg konstantnog broja djece, ostavit cemo useru na izbor da organizira svoju scenu kako god pozeli.
    Nas koncept nazvat cemo, tipicno, Node, napraviti od njega klasu jer je to dobar konstrukt za ovakav koncept, te ga smjestiti u (naravno) dva filea unutar 'src/main/cpp/model'.

    Do sada smo vec, nadam se, prihvatili praksu da fileove nazivamo tocnim imenom klase (postivajuci velika i mala slova), te da ih smjestamo unutar direktorija koji su direktna preslika namespaceova, pa od sad nadalje necu vise to spominjati jer je iz koda jednostavno deducirati sto gdje ide.

    Takodjer, imajte na umu da u buducnosti, kad dodamo nove namespaceove, obavezno je potrebno pokrenuti 'cmake .' da se Makefileovi nanovo izgeneriraju za cijelo stablo.
    Pocetni kod za Node:
#ifndef MODEL_NODE_HPP_
#define MODEL_NODE_HPP_

#include <general/Object.hpp>
#include <vector>

namespace model {
class Mesh;

class Node : public general::Object {
private:
    boost::shared_ptr<Node> parent;
    std::vector<boost::shared_ptr<Mesh> > children;
    std::string name;

public:
    Node();
    Node(const std::string& name);
    virtual ~Node();

    void AddChild(const boost::shared_ptr<Mesh> spatial);
    void RemoveChild(const boost::shared_ptr<Mesh> spatial);
    boost::shared_ptr<std::vector<boost::shared_ptr<Mesh> > > GetChildren();

    std::vector<boost::shared_ptr<Mesh> >::const_iterator Begin() const;
    std::vector<boost::shared_ptr<Mesh> >::const_iterator End() const;

    void SetParent(boost::shared_ptr<Node> parent);
    boost::shared_ptr<Node> GetParent() const;

    virtual void RenderChildren() const;

    virtual std::string GetClassName() const;
    virtual std::string ToString() const;
};
}

#endif

    Uocavamo dvije nove zanimljivosti: prije spomenuti shared_ptr i forward deklaraciju. Shared pointer koristimo da bi spremili pokazivac na dinamicki alociran Mesh. Ovime postizemo vise efekata, onaj primarno uociljiv je taj da je Node poprilicno lightweight objekt koji samo opisuje veze izmedju objekata. Drugi, ne tako vidljiv efekt, je taj da smo isforsirali usere da sve Mesheve alociraju na heapu - sto je dobra praksa jer heap sluzi spremanju runtime alociranih objekata, a 99% tih objekata biti ce isparsirano iz nekog vanjskog resursa - dakle, dobra odluka.
    Usput smo ustanoviti da nigdje necemo koristiti obican pointer, vec samo shared_ptr<>. Stoga bi bilo u redu da taj pointer ukljucimo negdje generalno, da vrijedi za sve - npr. u Object.hpp. Dodajte:
#include <boost/shared_ptr.hpp>
pa da zaboravimo na taj include jednom za svagda.

    Zanimljivost br. 2 je 'forward deklaracija', u praksi najobicniji C++ hack za compiler. Naime C++ lekser boluje od nedostatka inteligencije, jer je nesposoban prvo provuci sve sourceove kroz lekser a tek onda sastaviti AST. Ne. C++ mora sve znati unaprijed.
    Sideeffect ove gluposti je cinjenica da ciklicki dependencyi, poprilicno uobicajeni kod bilo kojeg ne-bazicnog design patterna (npr. Visitor) jednostavno ne rade u C++u, jer se compiler pogubi. Forward deklaracija je hack s kojim rijesavamo taj problem, na nacin da napominjemo compileru da itekako postoji neka klasa Mesh, i da tupan trenutno o tome ne treba brinuti jer ce naci definiciju klase negdje kasnije. U praksi, forward deklaracije bi se UVIJEK trebale koristiti kod asocijativne veze izmedju dvije klase. Asocijativna veza je ono sto nas Node trentno radi s Mesh-em, tj. Node ne naslijedjuje mesh nego ga koristi unutar deklaracija svojih metoda. Zgodan side-efekt ovog pravila je taj da se vrijeme kompilacije slightly skracuje.

    Primjetite takodjer da je const-correctness u ovoj klasi lagano kompromitiran - metode koje vracaju iteratore (Begin i End) su const, dakle mogu se pozivati na const Nodeu (pravimo se da ne vidimo da je const Node trenutno beskoristan, jer ga je nemoguce inicijalizirati sa setom djece). Problem je da iteratori koji se vracaju, ne vracaju niti const shared pointere (dakle moguce je promjeniti objekt unutar pokazivaca), niti const Mesheve na koje pokazivaci pokazuju.
    Razlog tome je malo arhitektura, a malo lijenost. Lijenost u tome sto, da bi vratili sve kompletno konstantno, morali bi iskopirati const interni vector te ga napuniti const pokazivacima na kopije const objekata = glupo. Razlog arhitekture je u tome sto se const treba odrzavati na this objektu, a ne lazno propagirati na ostale objekte. Cinjenica je da mi kroz AddChild i RemoveChild zahtijevamo objekte koji nisu const, te je tupavo vracati const verzije istih objekata samo da bi kompletno odrzali const correctnes. Konstantnost Nodea se mora odrzati iskljucivo u slucaju da netko zeli modificirati this objekt, ali to se ne bi smjelo propagirati na silu kroz ostale objekte - osim ako to nije originalni dizajn klase (a nije, jer Node zahtijeva mogucnost da se objekti mutiraju).
   
    Iz perspektive arhitekture, vec sada uocavamo slicnosti sa Mesh klasom, a nismo dodali ni osnove za kalkulacije - Node ima ime i sposobnost iscrtavanje djece. Svaki Node mora biti sposoban opisati lokalni prostor kroz vektore lokalne translacije, rotacije i skaliranja. Takodjer, ova Node klasa je trenutno poprilicno lose osmisljena u smislu da predstavlja obicnu linearnu kolekciju, a ne stablo. Dakle, trebali bi biti sposobni spremiti i Mesh-eve i Node-ove, te iskopirati gro koda iz Mesh klase, ili ekstrahirati kalkulacijski kod u neke vanjske funkcije. Ili primjeniti OOP (:-D) i shvatiti da su i Node i Mesh dio scenegrapha te da predstavljaju scenu u prostoru, samo s razlikom da Node opisuje segmentaciju prostora, dok Mesh opisuje kako iscrtati nesto u tom segmentu.
    To znaci da idealno treba izvuci trecu klasu vani, jednu koja ce objedniti svojstva i Mesh-a i Node-a. Na taj nacin, rijesit cemo sve probleme Node klase.
    Nazovimo tu klasu model::Spatial:
#ifndef MODEL_SPATIAL_HPP_
#define MODEL_SPATIAL_HPP_

#include <general/Object.hpp>
#include <math/Vector.hpp>

namespace model {
class Spatial: public general::Object {
protected:
    std::string name;
    math::Vector3f localTranslation;
    math::Vector3f rotationAxis;
    float rotationAngle;
    math::Vector3f scale;
    math::ColorRGBA defaultColor;

public:
    Spatial();
    Spatial(std::string name);
    virtual ~Spatial();
    virtual std::string GetClassName() const;

    std::string GetName() const;
    math::Vector3f GetLocalTranslation() const;
    math::Vector3f GetRotationAxis() const;
    float GetRotationAngle() const;
    math::Vector3f GetScale() const;
    math::ColorRGBA GetDefaultColor() const;

    void SetName(const std::string& name);
    void SetLocalTranslation(const math::Vector3f& localTranslation);
    void SetRotation(const math::Vector3f& axis, const float angle);
    void SetRotationAxis(const math::Vector3f& axis);
    void SetRotationAngle(const float angle);
    void SetScale(const math::Vector3f& scale);
    void SetDefaultColor(const math::ColorRGBA& color);
   
    virtual void Render() const = 0;
};
}

#endif

    Puno kalkulacijskog koda smo premjestili iz Mesh klase. Primjetite da je Spatial abstraktna klasa, odnosno nemoguce ju je instancirati jer ju je nemoguce iscrtati. Metoda Render je cista virtualna metoda, sto znaci da Spatial ostavlja svojim sub-klasama tu odgovornost.
    Implementacija je trivijalna. Obratite pozornost na koristenje inicijalizacijskih listi unutar _svih_ konstruktora. VRLO vrlo VRLO je bitno inicijalizirati SVE membere. U C++u i C-u NISTA nije toliko bitno, koliko inicijaliziran member. Moderni jezici to rade automatski pa se obicna deklaracija (npr. int broj;) ekspanzira u definiciju iza zavjesa(int broj = -1;). U C++u to radimo na ruke, i ako na to zaboravimo, osim cinjenice da ce debugiranje biti pakao jer necemo znati sto je pravilna vrijednost a sto neinicijalizirana, moguce je da dereferenciramo pointer koji je izvan naseg segmenta i skrsimo aplikaciju. Moguce je da dobijemo i pointer koji je unutar naseg segmenta, pritom necemo skrsiti aplikaciju ali dobit cemo interesantne vrijednosti.
    No, kod:
#include "Spatial.hpp"

using namespace std;
using namespace math;
using namespace model;

Spatial::Spatial() :
    name("UNNAMED_OBJECT"), localTranslation(0.0F, 0.0F, 0.0F), rotationAxis(0.0F, 1.0F,
          0.0F), rotationAngle(0.0F), scale(1.0F, 1.0F, 1.0F), defaultColor(
          1.0F, 0.0F, 0.0F, 1.0F) {
}

Spatial::Spatial(std::string name) :
    name(name), localTranslation(0.0F, 0.0F, 0.0F), rotationAxis(0.0F, 1.0F,
          0.0F), rotationAngle(0.0F), scale(1.0F, 1.0F, 1.0F), defaultColor(
          1.0F, 0.0F, 0.0F, 1.0F) {
}

Spatial::~Spatial() {
}

string Spatial::GetClassName() const {
    return "model::Spatial";
}

string Spatial::GetName() const {
    return name;
}

Vector3f Spatial::GetLocalTranslation() const {
    return localTranslation;
}

Vector3f Spatial::GetRotationAxis() const {
    return rotationAxis;
}

float Spatial::GetRotationAngle() const {
    return rotationAngle;
}

Vector3f Spatial::GetScale() const {
    return scale;
}

ColorRGBA Spatial::GetDefaultColor() const {
    return defaultColor;
}

void Spatial::SetName(const string& name) {
    this->name.assign(name);
}

void Spatial::SetLocalTranslation(const Vector3f& localTranslation) {
    this->localTranslation.Set(localTranslation);
}

void Spatial::SetRotation(const Vector3f& axis, const float angle) {
    this->rotationAxis.Set(axis);
    this->rotationAngle = angle;
}

void Spatial::SetRotationAxis(const Vector3f& rotation) {
    this->rotationAxis.Set(rotation);
}

void Spatial::SetRotationAngle(const float rads) {
    this->rotationAngle = rads;
}

void Spatial::SetScale(const Vector3f& scale) {
    this->scale.Set(scale);
}

void Spatial::SetDefaultColor(const ColorRGBA& color) {
    this->defaultColor.Set(color);
}

Vratimo se starom Mesh-u, koji sad izgleda malo okrljasteno :-D
#ifndef MODEL_MESH_HPP_
#define MODEL_MESH_HPP_

#include <model/Spatial.hpp>
#include <vector>

namespace model {
class Mesh: public Spatial {
private:
    std::vector<math::Vector3f> vertices;

    void ApplyTransforms() const;
    void ApplyDefaultColor() const;
public:
    Mesh();
    Mesh(const std::string& name, const std::vector<math::Vector3f>& vertices);
    Mesh(const math::Vector3f* verticesBuffer, const int size); // FOR TESTING PURPOSES ONLY
    virtual ~Mesh();

    std::vector<math::Vector3f> GetVertices() const;
    void SetVertices(const std::vector<math::Vector3f>& vertices);

    virtual void Render() const;

    virtual std::string GetClassName() const;
    virtual std::string ToString() const;
};
}
#endif

    Sav kalkulacijski kod je otisao u Spatial, te smo obogatili produkcijski konstruktor obaveznim imenom. Primjetite da metoda Render sada pristojno naslijedjuje i implementira pure virtual metodu iz Spatiala.
    Malo vise razlika je otislo u samu implementaciju, jer umjesto inicijalizacije direktnih membera, inicijalizacijska lista sad treba pozvati konstruktor super klase, ovako:
Mesh::Mesh() :
   Spatial::Spatial() {
}

Mesh::Mesh(const string& name, const vector<Vector3f>& vertices) :
   Spatial::Spatial(name), vertices(vertices) {
}

Mesh::Mesh(const Vector3f* verticesBuffer, const int size) : Spatial::Spatial() {
    for (int i = 0; i < size; ++i)
       vertices.push_back(verticesBuffer[i]);
}

Mesh::~Mesh() {
}

...


    Sad, idemo popraviti Node. Posla nema puno, jer smo brzo uocili los dizajn, pa u principu samo treba prepravit Node da naslijedjuje Spatial,  prepraviti member Children da drzi pointere na Spatiale (naravno, kad kazem 'pointer', mislim na 'shared pointer' jer onaj cisti pointer ne bi smio ni postojati), te renameati metodu RenderChildren na Render. S obzirom da Node naslijedjuje Spatial i sadrzi Spatial, forward deklaracija vise nije potrebna.
#ifndef MODEL_NODE_HPP_
#define MODEL_NODE_HPP_

#include <model/Spatial.hpp>
#include <vector>

namespace model {
class Node: public Spatial {
private:
    boost::shared_ptr<Node> parent;
    std::vector<boost::shared_ptr<Spatial> > spatials;

public:
    Node();
    Node(const std::string& name);
    virtual ~Node();

    void AddChild(const boost::shared_ptr<Spatial> spatial);
    void RemoveChild(const boost::shared_ptr<Spatial> spatial);
    boost::shared_ptr<std::vector<boost::shared_ptr<Spatial> > > GetChildren();

    std::vector<boost::shared_ptr<Spatial> >::const_iterator Begin() const;
    std::vector<boost::shared_ptr<Spatial> >::const_iterator End() const;

    void SetParent(boost::shared_ptr<Node> parent);
    boost::shared_ptr<Node> GetParent() const;

    virtual void Render() const;

    virtual std::string GetClassName() const;
    virtual std::string ToString() const;
};
}
#endif

    Napokon, kad je dizajn fino uspostavljen, implementacija koja je poprilicno trivijalna (manipulacija internom kolekcijom). Prilikom renderiranja Nodea, sve sto zelimo je pozvati Render metodu svakog childa. Na taj nacin, ako je child tipa Node, 'rekurzivno' cemo pozvati novu iteraciju, a ako je child tipa Mesh, iscrtat ce sam sebe.
#include "Node.hpp"

#include <algorithm>
#include <sstream>
#include <string>

using namespace std;
using namespace model;
using namespace math;
using namespace boost;

Node::Node() :
    Spatial::Spatial() {
}

Node::Node(const string& name) :
    Spatial::Spatial(name) {
}

Node::~Node() {
}

void Node::AddChild(const shared_ptr<Spatial> mesh) {
    spatials.push_back(mesh);
}

void Node::RemoveChild(const shared_ptr<Spatial> mesh) {
    remove(spatials.begin(), spatials.end(), mesh);
}

vector<shared_ptr<Spatial> >::const_iterator Node::Begin() const {
    return spatials.begin();
}

vector<shared_ptr<Spatial> >::const_iterator Node::End() const {
    return spatials.end();
}

shared_ptr<vector<shared_ptr<Spatial> > > Node::GetChildren() {
    shared_ptr<vector<shared_ptr<Spatial> > > spatialsPtr(&spatials);
    return spatialsPtr;
}

void Node::SetParent(boost::shared_ptr<Node> parent) {
    this->parent = parent;
}

boost::shared_ptr<Node> Node::GetParent() const {
    return parent;
}

void Node::Render() const {
    for(vector<shared_ptr<Spatial> >::const_iterator it = Begin(); it != End(); ++it) {
       (*it)->Render();
    }
}

string Node::GetClassName() const {
    return "model::Node";
}

string Node::ToString() const {
    ostringstream os;
    os << GetClassName() << "{" << "filename='" << name << "', " << "meshes={";

    vector<shared_ptr<Spatial> >::const_iterator it = spatials.begin();
    vector<shared_ptr<Spatial> >::const_iterator nextToLast = spatials.end();
    nextToLast--;

    for (; it < nextToLast; ++it)
       os << (*it)->ToDebugString() << ", ";

    vector<shared_ptr<Spatial> >::const_iterator last = spatials.end();
    last--;

    os << (*last)->ToDebugString();
    os << "}" << "}";
    return os.str();
}

    Super. Ovaj tutorial se malo oduzio, pa cemo segmentirane kalkulacije ostaviti za drugi put, te ovako napola dovrsenu strukturu ostaviti kakva je. Za test, promjenit cemo Tutorial1.cpp na nacin da kreiramo root naseg scenegrapha, i trokut attachiramo kao child. Ako to profunkcionira, znaci da smo na dobrom putu.
    Negdje pri vrhu dodajmo:
#include <model/Node.hpp>

malo dalje gdje su globalne varijable (brrr) prepravimo
shared_ptr<Mesh> object;
u
const shared_ptr<Node> rootNode(new Node("root"));
    Const je tu da budemo sigurni da nitko nece 'slucajno' promjeniti root scene. CreateMesh treba prepraviti na:
void CreateMesh() {
    array<Vector3f, 3> triangleVerts = {
          Vector3f(-0.5F, -0.5F, 0.0F),
          Vector3f(0.5F, -0.5F, 0.0F),
          Vector3f(0.0F, 0.5F, 0.0F)
    };

    shared_ptr<Mesh> object = shared_ptr<Mesh>(new Mesh(triangleVerts.c_array(), triangleVerts.size()));
    object->SetDefaultColor(ColorRGBA(1.0F, 0.0F, 0.0F, 1.0F));
   
    rootNode->AddChild(object);

}
te unutar DrawGLScene() promjeniti render call da poziva rootNode:
void DrawGLScene() {
    Clear(clearColor);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0F, 1.0F, -1.0F, 1.0F, 1.0F, 20.0F);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F);

    rootNode->Render();
}

'make', 'scenegraph-tutorial', i crveni trokut bi i dalje trebao biti tamo negdje. Ako nije, pisite pa da popravljamo skupa.
Cya next time!

I have got no money, I have got no power, I have got no fame... I have my strong beliefs...
Poruka je uređivana zadnji put pon 27.9.2010 19:43 (Deus ex machina).
 
3 0 hvala 4
15 godina
neaktivan
offline
[Tutorial] Izrada 3D/scenegraph enginea

Razmisljao sam malo duze gdje da commitam cijeli projekt, i nisam bas pronasao neko dobro rijesenje. Prtljao sam po Dropboxu u nadi da cu uspjeti izrollati SVN root u Public direktoriju, pa poslati link na to, ali nazalost nije uspjelo. Razmisljao sam shareati svnroot direktorij, ali to znaci da bi svatko tko zeli pratiti tutorial, morao biti Dropbox clan, morao bi ga na ruke dodati kao collaboratora, i cijeli svnroot bi se sinkronizirao na svacijem hardu.

 

Tako da sam se odlucio za malo primitivnije rijesenje, koje ce zbog opsega ovog projekta biti sasvim dovoljno: zippani sourcevi.

Pa vi s njima radite sto vas volja.

 

Ovo je link za intro post:

http://dl.dropbox.com/u/80655/scenegraph_tutorial/intro.tar.bz2

 

Link za prvu lekciju:

http://dl.dropbox.com/u/80655/scenegraph_tutorial/lesson1.tar.bz2

 

Link za drugu lekciju:

http://dl.dropbox.com/u/80655/scenegraph_tutorial/lesson2.tar.bz2

 

 

Etc... zadnja lekcija je lekcija 7.

Pokusat cu na kraju svake lekcije ostaviti link za download.

 

Cheers.

I have got no money, I have got no power, I have got no fame... I have my strong beliefs...
 
1 0 hvala 3
15 godina
neaktivan
offline
[Tutorial] Izrada 3D/scenegraph enginea

Bok.
Proslo je malo vremena, razlog tome je da radim +9 sati dnevno u tiru i nemam vremena sjesti i posteno pisati, pa sam tu i tamo ostavljao sebi natuknice.

U proslom tutorialu napravio sam gresku koju cemo jednostavno ispraviti. Naime, property Parent smjestili smo u Node, sto znaci da necemo moci upitati za parent iz Mesh klase, sto je nepotrebno ogranicenje. Ovo se vrlo jednostavno rijesava tako da premjestimo svojstvo parent u zajednicki Spatial.
Jedan sitan problem je taj da na ovaj nacin stvaramo ciklicki dependency izmedju Spatiala i Nodea - Spatial asocira Node, a Node naslijedjuje Spatial. No to smo vec naucili rijesiti uz pomoc forward deklaracija, pa cemo u Spatial dodati, unutar namespace deklaracije:
namespace model {
class Node;
...
private:
    ...
    boost::shared_ptr<Node> parent;

public:
    ...
    boost::shared_ptr<Node> GetParent() const;
    void SetParent(boost::shared_ptr<Node> parent);

Takodjer treba premjestiti implementaciju. Nije potrebno includati Node.hpp u implementacijski file, jer ne koristimo Node, nego samo spremamo pointer na njega.



OK... imamo trokut na ekranu, imamo bazicni scenegraph. Mislim da smo rijesili bullet br2.
Istina, sve sto imamo je vrlo vrlo bazicno, ali izdrzat ce - najbitnije je drzati se postavljenog plana, i napraviti najjednostavnije moguce rijesenje, te kasnije dodavati featureove kako nam je potrebno.
Nazalost, ja odstupam tu i tamo od toga, jer se zaboravim kad je u pitanju hobi, ali pokusavam se kontrolirati :-)

Dakle. Nas sistem je jos uvijek crossplatform, iako se poprilicno bijedno ponasa u pitanju aplikacija - to cemo ostaviti za malo kasnije.
Sad cemo se orijentirati na bullet3 - apstraktni renderer.

Sto je renderer?
U nasem libraryu, to je fasada izmedju usera, scenegrapha i komplicirane platform/API-dependent logike koja koristi podatke iz scenegrapha za iscrtavanje.
Ovaj odgovor krije nekoliko bitnih znacajki dizajna:
- scenegraph i math klase ne smiju nikako biti dependent na bilo koji rendering API. To znaci da scenegraph ne smije sadrzavati nikakvu logiku koja se tice iscrtavanja.
- renderer mora biti svjestan razlicitih platformi i razlicitih API-ja. Ovaj "i" je sumnjiv. Izgleda kao da odjednom Renderer obavlja malo vise posla nego sto bi trebao.

Koncentrirajuci se na tocku broj 1, uvidjamo da smo rastavili prikaz podataka od operacija koje ce se nad tim podacima izvoditi. Takodjer, nas OOP dizajn uvjetuje da model podataka i model operacija predstavljaju abstraktne pojmove, i niti jedan koncept ne bi smio znati interne stvari o drugom. Zbog cega?
Jer u buducnosti planiramo naravno dodavati nove stvari koje se mogu izrendati. Ne zelimo updateirati IRenderer kad god se to dogodi, sto znaci, zelimo asocirati iskljucivo top hijerarhije.
Takodjer, neke stvari koje ce se iscrtavati, nisu nuzno Spatial - primjerice, fullscreen efekti, GUI...
To znaci da root klasa nase hijerarhije vise ne moze biti Spatial, nego nesto generalnije. Nesto sto iskljucivo zna kako iscrtati sebe.
Drzimo se, kao i uvijek, jednostavnosti, te extractajmo metodu iz Spatiala jedan nivo iznad, i napravimo obican contract - interface.
#include <general/Object.hpp>

namespace model {
class IRenderable: public general::Object {
public:
    virtual void Render() const = 0;
};

Popravimo Spatial naslijedjivanjem IRenderable, i uklanjanjem Render() metode.

Sad imamo dovoljno jednostavan root contract za rendanje. Da vidimo kako cemo to izgurati sa strane renderera.
Prije smo rekli da ne zelimo da niti jedan koncept (model podataka i operacije) zna jedan za drugi, dakle, potreban nam je interface IRenderer. No sto staviti u njega?
Pogledajmo prvo hijerarhiju:
IRenderable <----- IRenderer
    |                |
 Spatial       OpenGLWin, OpenGLLinux, DirectX
    |
Mesh, Node

Teoretski, u OOP-u se ovaj problem rijesava uz pomoc double-dispatch sistema. Sto je to?
Double-dispatch koristi overloadane metode i za vrijeme izvrsavanja programa, odredjuje koji tocno overload pozvati, ovisno o tipu podataka.
Primjerice, u IRenderer interfaceu imamo metodu:
render(IRenderable& spatial);
a primjerice neka implementacija renderera zna kako iscrtati mesheve:
render(IRenderable& spatial);
render(Mesh& mesh);

Double dispatch bi se pobrinuo da kad pozovemo IRenderer interface metodu render(Spatial) i u nju predamo objekt tipa Mesh (neovisno o tipu reference), pozvali bi tocan overload metode u implementaciji renderera.
Losa vijest, ovo ne radi. Staticki OOP jezici ne podrzavaju double-dispatch, koji bi se mogao jednostavno implementirati uz overhead jednak vtabelama (ie. prakticno nepostojeci overhead).
Dobra vijest, uz malo dodatnog posla, moguce je postici funkcionalnost double-dispatcha koristeci stock design pattern nazvan Visitor http://en.wikipedia.org/wiki/Visitor_pattern.

Kako cemo mi implementirati Visitor? Nas IRenderer ce u praksi postati visited element, a IRenderer ce postati visitor.
1. Visit ce se odvijati preko metode iz IRenderer koja ce primati referencu na renderer.
2. Svaka implementacija IRenderable ce pozvati IRenderer uz 'this' parametar, te na taj nacin ostvariti double-dispatch, dajuci rendereru do znanja koji tocno overload pozvati.

Kao sto vidimo, tu je u djiru jos jedan ciklicki dependency.
Napravimo prvo nas rendering namespace, i dodajmo osnovni IRenderer. Tree sada izgleda ovako:
project/
|-- CMakeLists.txt
`-- src
    |-- main
    |   `-- cpp
    |       |-- Tutorial1.cpp
    |       |-- general
    |       |   |-- Object.cpp
    |       |   `-- Object.hpp
    |       |-- math
    |       |   `-- Vector.hpp
    |       |-- model
    |       |   |-- IRenderable.hpp
    |       |   |-- Mesh.cpp
    |       |   |-- Mesh.hpp
    |       |   |-- Node.cpp
    |       |   |-- Node.hpp
    |       |   |-- Spatial.cpp
    |       |   `-- Spatial.hpp
    |       `-- renderer
    |           `-- IRenderer.hpp
    `-- test
        `-- cpp
            |-- TestSuite.cpp
            `-- math
                `-- VectorTest.hpp

Visitor kaze da IRenderer mora biti sposoban iscrtati svaki element, dakle, IRenderer mora znati za sve nase scenegraph elemente.
However, mi cemo uvesti jos jedno pravilo - klase ne mogu biti include-dependent na klase izvan svojih paketa, vec cemo koristiti iskljucivo forward deklaracije. Na ovaj nacin, nas include-tree ce biti puno cisci i izazvati manje glavobolja u buducnosti.
Dakle, napokon, startni code za IRenderer - trenutno poznajemo samo dva elementa u scenegraphu:
#include <general/Object.hpp>

namespace model {
class Node;
class Mesh;
}

namespace renderer {
class IRenderer: public general::Object {
protected:
    IRenderer() {}
    virtual ~IRenderer() {}
public:
    virtual void Render(const boost::shared_ptr<const model::Mesh> mesh) const = 0;
    virtual void Render(const boost::shared_ptr<const model::Node> node) const = 0;
};
}

Jos malo pa smo rijesili - treba popraviti IRenderable interface, tako da prima visitora. Zbog naseg maloprije uspostavljenog pravila, opet cemo se koristiti forward deklaracijama:
namespace renderer {
class IRenderer;
}

namespace model {
class IRenderable: public general::Object {
public:
    virtual void Render(const boost::shared_ptr<const renderer::IRenderer> renderer) const = 0;
};
}

Odlicno. E sada build puca :-) Jer nasa nova metoda nije nigdje implementirana - treba popraviti scenegraph klase tako da dodamo novi parametar renderer, te iskoristiti taj renderer za rendanje, tocno kako Visitor opisuje. U Node i Mesh treba popraviti header na isti nacin, tj. napraviti da metoda izgleda jednako kao u IRenderable, bez =0. U implementaciji, prvo popravimo Mesh.
Pobijte sve #includeove koji dodaju dependency na OpenGL u bilo kojoj scenegraph klasi. S obzirom da cemo koristiti metode iz IRenderer, potrebno je dodati #include - ali NE ZABORAVITE da ovaj include ide ISKLJUCIVO u cpp file - to je pravilo forward deklaracija.
Dakle, pri vrhu:
#include <renderer/IRenderer>
pa onda tamo gdje je Render metoda, treba predati shared_ptr na this:
void Mesh::Render(const shared_ptr<const IRenderer> renderer) const {
       renderer->Render(shared_ptr<const model::Mesh>(this));
}

Ovo izgleda sasvim OK?
Ma ni u ludilu!!!
Iako izgleda intuitivno, ovo je bug na kvadrat. Zasto?
C++ nije managed jezik, zapamtite da shared_ptr, koliko god magican, NIJE svemoguc. Ono sto smo ovdje napravili, je dodavanje NOVOG shared_ptr na STARU referencu. Ako je netko procitao pravila koristenja shared_ptr, vidio je da sigurno koristenje istog UVJETUJE konstrukcija zajedno u paru sa 'new'.
Sto bi se dogodilo s ovim konstruktom?
Cim bi nas novo stvoreni shared_ptr izasao van scopea, reference counter bi pao na nulu i shared_ptr bi pobrisao this. Svi ostali shared_ptr koji drze this, poput onoga koji ga je napravio, npr., bi postao invalid :-D
Spasite si segmentation fault glavobolje, i upoznajte
#include <boost/enable_shared_from_this.hpp>
koji normalno dodajemo ravno u Object.hpp.

Ovaj header nam daje na raspolaganje istoimenu klasu, koju je potrebno naslijediti u SVAKOJ klasi u kojoj 'this' treba postati shared_ptr.
Dakle, jos jedan popravak u Mesh header:
class Mesh: public Spatial, public boost::enable_shared_from_this<Mesh> {
...
i OBAVEZNO u Node:
class Node: public Spatial, public boost::enable_shared_from_this<Node> {
...

E fino, sad smo na miru. Sad, natrag na popravak Mesh::Render(...) metode:
void Mesh::Render(const shared_ptr<const IRenderer> renderer) const {
    renderer->Render(shared_from_this());
//    ApplyTransforms();
//    glBegin(GL_TRIANGLES);
//
//    for (unsigned int i = 0; i < vertices.size(); ++i) {
//       Vector3f vertex = vertices[i];
//       ApplyDefaultColor();
//       glVertex3f(vertex[0], vertex[1], vertex[2]);
//    }
//
//    glEnd();
}

Sad je PUNO BOLJE :-D
Popravimo Node:
void Node::Render(const shared_ptr<const IRenderer> renderer) const {
    renderer->Render(shared_from_this());
   
//    for(vector<shared_ptr<Spatial> >::const_iterator it = Begin(); it != End(); ++it) {
//       renderer->Render()
//       (*it)->Render();
//    }
}

Tu se negdje krije skrivena pouka: _nikad_ brisati kod. Komentari su puno bolji! Vidjet cemo zasto malo kasnije.

Super! To bi trebalo biti to sto se tice scenegrapha. Ali build jos uvijek puca. To je lose. Idemo napraviti najosnovniji, debug renderer, cisto da se uvjerimo da sve radimo kako spada.
Dodajmo DebugRenderer klasu:
#include <renderer/IRenderer.hpp>
#include <iostream>

namespace renderer {
class DebugRenderer : public IRenderer {
    virtual void Render(const boost::shared_ptr<const model::Mesh> mesh) const {
       std::cout << "Rendering Mesh!" << std::endl;
    }

    virtual void Render(const boost::shared_ptr<const model::Node> node) const {
       std::cout << "Rendering Node!" << std::endl;
    }

    virtual std::string GetClassName() const  {
       return "renderer::DebugRenderer";
    }

};
}

Ruzno je programirati u header fileu, ali ovo je debug klasa, koju cemo kasnije ubiti. Ovo je samo da proradi build, pa je OK.

Za danas je dosta! Poceo sam implementaciju pravog renderera, ali previse je to posla za jedan lesson. Slijedeci put, prebacit cemo sve u non-debug renderer implementaciju.
Cya!

 

 

Edit: pardon, zabio sam link za lijencine :-)

I have got no money, I have got no power, I have got no fame... I have my strong beliefs...
Poruka je uređivana zadnji put pet 8.10.2010 23:10 (Deus ex machina).
 
8 0 hvala 8
Nova poruka
E-mail:
Lozinka:
 
vrh stranice