11 listopada 2009 Tutorial 3. Custom SceneNode



Ten tutorial jest bardziej zaawansowany niż poprzednie. Jeżeli obecnie tylko bawisz się silnikiem Irrlicht, możesz chcieć przejrzeć najpierw inne przykłady. Ten tutorial pokazuje jak utworzyć 'custom scene node' i jak tego użyć w silniku. 'Custom scene mode' jest niezbędny jeśli chcesz implementować, realizować technikę renderowania, której w tej chwili silnik Irrlicht nie wspiera. Na przykład możesz z tym napisać 'indoor portal based renderer' albo zaawansowany 'terrain scene node'. Przez tworzenie 'custom scene nodes', możesz z łatwością rozszerzać silnik Irrlicht i dostosowywać go do swoich potrzeb.

Postaram się dobrze wytłumaczyć ten tutorial, żeby nie wydawał się trudny: wszystko krótko, wszystko w jednym pliku .cpp, i użyje tutaj silnika tak jak we wszystkich innych tutorialach.

Żeby zacząć, załączam plik nagłówkowy, używam 'irr namespace', i mówie 'linkerowi' żeby połączył z plikiem .lib.

#include <irrlicht.h>
#include <iostream>

using namespace irr;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

I tutaj bardziej zaawansowana część tutorialu: 'class' (klasa) każdego naszego 'custom scene node'. Mówiąc krótko, nasza 'scene node' nie będzie 'indoor portal renderer' ani 'terrain scene node', tylko prostym tetraedrem (czworościanem), obiektem 3d składającym się z 4 połączonych wierzchołków, który tylko rysuje siebie i nie robi nic więcej. Zauważ, że ten scenariusz nie wymaga 'custom scene node' w Irrlicht. Zamiast tego jeden utworzy mesh z geometrii i przekaże to do 'irr::scene::IMeshSceneNode'. Ten przykład tylko ilustruje utworzenie 'custom scene node' w bardzo prostym ustawieniu.

Żeby umożliwić naszej 'scene node' bycie wstawionym do sceny silnika Irrlicht, klasa którą tworzymy musi pochodzić z klasy 'irr::scene::ISceneNode' i musi unieważniać niektóre metody.
class CSampleSceneNode : public scene::ISceneNode
{ 

Najpierw deklarujemy pewne 'member' zmienne: 'bounding box', 4 wierzchołki i materiał tetraedru.
        core::aabbox3d Box;
        video::S3DVertex Vertices[4];
        video::SMaterial Material;

Parametry konstruktora określa rodzic 'scene node', wskaźnik do 'scene manager' i id tego 'scene node'. W konstruktorze określimy konstruktor jako 'parent class', ustawimy kilka właściwości materiału i utworzymy 4 wierzchołki tetraedru który narysujemy później.
public:

        CSampleSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id)
                : scene::ISceneNode(parent, mgr, id)
        {
                Material.Wireframe = false;
                Material.Lighting = false;

                Vertices[0] = video::S3DVertex(0,0,10, 1,1,0,
                                video::SColor(255,0,255,255), 0, 1);
                Vertices[1] = video::S3DVertex(10,0,-10, 1,0,0,
                                video::SColor(255,255,0,255), 1, 1);
                Vertices[2] = video::S3DVertex(0,20,0, 0,1,1,
                                video::SColor(255,255,255,0), 1, 0);
                Vertices[3] = video::S3DVertex(-10,0,-10, 0,0,1,
                                video::SColor(255,0,255,0), 0, 0);

Silnik Irrlicht musi znać 'bounding box' tego 'scene node'. Będzie tego używał do automatycznego wybierania informacji i innych rzeczy. Stąd, musimy utworzyć 'bounding box' z 4 wierchołków jakich używamy. Jeśli nie chcesz by silnik używał 'boxu' do automatycznego wybierania informacji i/lub nie chcesz tworzyć 'boxu', możesz także wezwać 'irr::scene::ISceneNode::setAutomaticCulling()' z 'irr::scene::EAC_OFF'.
                Box.reset(Vertices[0].Pos);
                for (s32 i=1; i<4; ++i)
                        Box.addInternalPoint(Vertices[i].Pos);
        } 

Przed narysowaniem, metoda 'irr::scene::ISceneNode::OnRegisterSceneNode()' każdej 'scene node' jest wzywana przez 'scene manager'. Jeśli 'scene node' chce się rysować, może się ona rejestrować w 'scene manager' do bycia rysowaną. Niezbędnym jest powiedzenie 'scene managerowi' kiedy powinien wezwać 'irr::scene::ISceneNode::render()'. Na przykład normalne 'scene nodes' renderują swoją zawartość jedna po drugiej, podczas gdy 'stencil buffer shadows' chciałby być rysowany po wszystkich innych 'scene nodes'. I kamera lub światło tych 'scene nodes' powinny być renderowane przed wszystkimi innymi 'scene nodes' (jeśli wogóle). Więc tutaj tylko rejestrujemy 'scene node' żeby renderowała się normalnie. Jeśli chcielibyśmy pozwolić im renderować się jak kamerze i światłu, będziemy musieli wezwać 'SceneManager->registerNodeForRendering(this, SNRT_LIGHT_AND_CAMERA)'. Potem wzywamy aktualną metodę klasy bazowej 'irr::scene::ISceneNode::OnRegisterSceneNode()', która pozwoli też 'child scene nodes' tego 'node' żeby sie same zarejestrowały.
        virtual void OnRegisterSceneNode()
        {
                if (IsVisible)
                        SceneManager->registerNodeForRendering(this);

                ISceneNode::OnRegisterSceneNode();
        } 

W 'render() method' dzieje się większość ciekawych rzeczy: 'scene node' samo się renderuje. Unieważniamy tą metodę i rysujemy tetraedr.
        virtual void render()
        {
                u16 indices[] = {       0,2,3, 2,1,3, 1,0,3, 2,0,1      };
                video::IVideoDriver* driver = SceneManager->getVideoDriver();

                driver->setMaterial(Material);
                driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
                driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4);
        }

I wreszcie tworzymy trzy małe dodatkowe metody. 'irr::scene::ISceneNode::getBoundingBox()' wraca 'boundary box' tych 'scene node', 'irr::scene::ISceneNode::getMaterialCount()' zwraca ilość materiału w tej 'scene node' (nasz tetraedr ma tylko jeden materiał), i 'irr::scene::ISceneNode::getMaterial()' zwraca materiał w indexie. Ponieważ mamy tutaj tylko jeden materiał, mozemy zwrócić tylko jeden materiał, zakładając, że nikt nigdy nie wezwie 'getMaterial()' z indexem większym niż 0.
       virtual const core::aabbox3d& getBoundingBox() const
        {
                return Box;
        }

        virtual u32 getMaterialCount() const
        {
                return 1;
        }

        virtual video::SMaterial& getMaterial(u32 i)
        {
                return Material;
        }       
}; 

To wszystko. Ten 'scene node' jest gotowy. Teraz musimy tylko włączyć silnik, utworzyć 'scene node' i kamerę, i zobaczyć rezultaty.
int main()
{
        // let user select driver type

        video::E_DRIVER_TYPE driverType;

        printf("Please select the driver you want for this example:\n"\
                " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
                " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
                " (f) NullDevice\n (otherKey) exit\n\n");

        char i;
        std::cin >> i;

        switch(i)
        {
                case 'a': driverType = video::EDT_DIRECT3D9;break;
                case 'b': driverType = video::EDT_DIRECT3D8;break;
                case 'c': driverType = video::EDT_OPENGL;   break;
                case 'd': driverType = video::EDT_SOFTWARE; break;
                case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
                case 'f': driverType = video::EDT_NULL;     break;
                default: return 0;
        }

        // create device

        IrrlichtDevice *device = createDevice(driverType,
                        core::dimension2d(640, 480), 16, false);
                
        if (device == 0)
                return 1; // could not create selected driver.

        // create engine and camera

        device->setWindowCaption(L"Custom Scene Node - Irrlicht Engine Demo");

        video::IVideoDriver* driver = device->getVideoDriver();
        scene::ISceneManager* smgr = device->getSceneManager();

        smgr->addCameraSceneNode(0, core::vector3df(0,-40,0), core::vector3df(0,0,0));


Utwórz naszą 'scene node'. Ja nie sprawdzam teraz rezultatów wzywania w tej chwili, ponieważ to powinno być uważane jako wyjątek niż powrót 0 jako błąd. Ponieważ nowy 'node' utworzy sie sam z 'reference count' 1, i później będzie miał dodane inne odniesienie przez jego 'parent scene node' kiedy jest dodany do sceny, muszę opuscic moje odniesienie do tego. Najlepiej jest upuścić to tylko *po* tym jak skończyłem go używać, bez względu na to jaka jest 'reference count' obiektu po jego utworzeniu.
     CSampleSceneNode *myNode =
                new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666);

Żeby coś zanimować w tej nudnej scenie składającej się z jednego tetraedru i pokazać, że teraz możesz używać swojej 'scene node' jak każdej innej 'scene node' w silniku, dodamy animatora do 'scene node', który będzie obracał troszke 'node'. 'irr::scene::ISceneManager::createRotationAnimator()' może zwrócić 0 więc musi być sprawdzony.
        scene::ISceneNodeAnimator* anim =
                smgr->createRotationAnimator(core::vector3df(0.8f, 0, 0.8f));

        if(anim)
        {
                myNode->addAnimator(anim);

Skończyłem odnosic sie do 'anim', więc muszę 'irr::IReferenceCounted::drop()' ten odnośnik teraz, ponieważ był wyprodukowany przez funkcję 'createFoo()'. Jako, że nie powinienem odnosić się do niego więcej, upewniamy się, że nie możemy przez ustawienie do 0.
                anim->drop();
                anim = 0;
        } 

Skończyłem mój obiekt [CSampleSceneNode] i muszę opuścić moje odniesienie. To nie skasuje obiektu, ponieważ będzie on ciągle złączony ze 'scene graph', co uchroni przed skasowaniem dopóki 'graph' jest wykasowany lub gdy 'custom scene node' jest z niego usunięty.
       myNode->drop();
        myNode = 0; // As I shouldn't refer to it again, ensure that I can't

Teraz rysujemy wszystko i kończymy.
       u32 frames=0;
        while(device->run())
        {
                driver->beginScene(true, true, video::SColor(0,100,100,100));

                smgr->drawAll();

                driver->endScene();
                if (++frames==100)
                {
                        core::stringw str = L"Irrlicht Engine [";
                        str += driver->getName();
                        str += L"] FPS: ";
                        str += (s32)driver->getFPS();

                        device->setWindowCaption(str.c_str());
                        frames=0;
                }
        }

        device->drop();
        
        return 0;
} 

I to już wszystko. Skompiluj i baw się nadal z progrmem.



PiotrSOG