Irrlicht

From Fontys VR-Wiki
Jump to: navigation, search

Inleiding

Irrlicht is een Open Source 3D engine met een actieve community. Omdat de engine redelijk goed te doorgronden is hebben we deze engine als 1e gekozen om te koppelen aan de Cave. In de volgende paragrafen staat beschreven hoe een groepje studenten dit hebben aangepakt en wat de resultaten hiervan zijn.

Voor ons project hebben we een interface gemaakt om het gemakkelijker te maken voor programmeurs om Irrlicht te gebruiken samen met VRJuggler.

Als eerste bekijken we een UML diagram om een snel overzicht van het interface te krijgen. Vervolgens beschrijven we wat het interface doet en ook wat je zelf moet doen. Daarnaast is er ook nog een paragraaf over de project instellingen in Visual Studio. Om gebruikers van het interface wegwijs te maken is er een voorbeeld gegeven van hoe een simpele Irrlicht applicatie gemaakt kan worden. Ook is er een apispecificate waarin de doelen van methodes, pre- en postcondities beschreven staan.

Tijdens ons project zijn we tegen een aantal punten aangelopen die problemen opleverden. Deze problemen zullen bij het omzetten van een andere engine mogelijk ook problemen opleveren. Eerst zal in dit document beschreven worden welke wijzigingen wij hebben moeten aanbrengen in de Irrlicht engine om deze werkend te krijgen. Daarna zullen we een aantal punten aangeven waar we denken dat er problemen kunnen optreden als er een andere engine omgezet zal worden.


Interface Irrlicht/VRjuggler

Iirlicht1.jpg Op het moment heeft het interface maar één specifieke taak: Het interface zorgt er voor dat de gebruiker de applicatie niet zelf van een frustum hoeft te voorzien. Kort gezegd kan de gebruiker dus een applicatie schrijven voor Irrlicht zonder ook maar iets af te hoeven weten van frustums. Het doorgeven van het frustum kan eenvoudig gedaan worden door de applicatie over te laten erven van IrrlichtApp. Ook zorgt het interface er voor dat er een goeie IrrlichtDevice wordt aangemaakt met behulp van de methode createIrrlichtDevice. De gebruiker kan vervolgens gebruik maken van het m_device attribuut net als in Irrlicht.

Het interface beschikt niet over de mogelijkheid om VRJuggler input automatisch naar Irrlicht door te sturen. Hiervoor is gekozen omdat het de mogelijkheden die de input-apparaten bieden enorm zou beperken. Daarom moet de gebruiker zelf kiezen op wat voor manier de input afgehandeld wordt. Irrlicht heeft zelf een inputsysteem dat gebruikt kan worden. De gebruiker kan bijvoorbeeld een event aan de camera versturen. Dit kan door een irr::SEvent aan te maken en vervolgens de functie OnEvent van de camera aan te roepen waaraan het event meegegeven kan worden. Events kunnen in de preFrame verstuurd worden.

Let wel op: Muis input kan niet meer verstuurd worden, we gebruiken immers geen klassieke muis. Een manier om de camera te laten draaien is door het roteren van de camera scenenode. Berekeningen voor het roteren van de camera mogen in de preFrame gemaakt worden, maar het roteren zelf moet in de draw functie uitgevoerd worden.

De functie bufferPreDraw is geschikt om de methoden run van IrrlichtDevice en de methode beginScene van IVideoDriver aan te roepen

Project instellingen Visual Studio

Als eerste moet je er voor zorgen dat je Irrlicht voor VRJuggler hebt en dat VRJuggler staat geïnstalleerd. Bij je VisualStudio project moet je op de volgende dingen letten: Additional Include Directories, Addition Library Directories en Addition Dependencies.

Additional Include Directories

  • <irrlicht dir>\include
  • <vrjuggler dir>\include
  • <vrjugglerdeps dir>\include

Addition Library Directories

  • <irrlicht dir>\lib\Win32-visualstudio
  • <vrjugglerdeps dir>\lib
  • <vrjuggler >\lib

Additional Dependencies

  • "irrlicht.lib vrj.lib vrj_ogl.lib gadget.lib jccl.lib vpr.lib libnspr4.lib libplc4.lib comctl32.lib ws2_32.lib opengl32.lib glu32.lib

Tip: Gebruik een bestaand VRIrrlicht project waarin de instellingen al goed staan.


Een simpele IrrlichtApp

In dit hoofdstuk wordt uitgelegd hoe een simpele Irrlicht applicatie gemaakt kan worden voor VRJuggler. Als voorbeeld applicatie laden we een Quake3 map in en implementeren we wat basis navigatie.

Als eerste moet je een klasse maken die van IrrlichtApp overerft. Dit is het basis h-bestand, we gaan hieraan straks dingen toevoegen.

  1. ifndef _SIMPLEIRRLICHT_APP
  2. define _SIMPLEIRRLICHT_APP
  1. include "IrrlichtApp.h"

using namespace irr; using namespace vrj;

class SimpleIrrlichtApp : public IrrlichtApp { public: SimpleIrrlichtApp( ) { ; }

virtual ~SimpleIrrlichtApp( ) { ; }

public: // ---- INITIALIZATION FUNCTIONS ---- // /** * Executes any initialization needed before the API is started. * * @post Device interfaces are initialized with the device names * we want to use. * @note This is called once before Irrlicht is initialized. */ virtual void init( );

public: // ----- Drawing Loop Functions ------ // // The drawing loop will look similar to this: // // while (drawing) // { // preFrame(); // draw(); // intraFrame(); // Drawing is happening while here // sync(); // postFrame(); // Drawing is now done // // UpdateDevices(); // } //------------------------------------

/**

  • Function that is called upon entry into a buffer of an OpenGL
  • context (window).

* @note This function is designed to be used when you want to do

  • something only once per buffer (ie.once for left buffer, once for

* right buffer). */ virtual void bufferPreDraw( );

/** * Called before start of frame. * * @note Function called after device updates but before start of * drawing. */ virtual void preFrame( );

public: // ----- OpenGL FUNCTIONS ---- // /** * Function that is called immediately after a new OGL context is * created. * Initialize GL state here. Also used to create context specific * information. * * This is called once for each context. */ virtual void contextInit( );

/** * Function to draw the scene. * * @pre OpenGL state has correct transformation and buffer selected. * @post The current scene has been drawn. * * @note Called 1 or more times per frame. */ virtual void drawIrrlicht( ); };

  1. endif

Het cpp-bestand dat hier bij hoort ziet er als volgt uit:

  1. include "SimpleIrrlichtApp.h"

void SimpleIrrlichtApp::init() { }

void SimpleIrrlichtApp::bufferPreDraw() {}

void SimpleIrrlichtApp::preFrame() { }

void SimpleIrrlichtApp::drawIrrlicht() { }

void SimpleIrrlichtApp::contextInit() { }

We gaan nu het h-bestand zo invullen dat we over alles beschikken wat we nodig hebben. Het h-bestand ziet er nu als volgt uit:

  1. ifndef _SIMPLEIRRLICHT_APP
  2. define _SIMPLEIRRLICHT_APP
  1. include <gadget/Type/PositionInterface.h>
  2. include <gadget/Type/DigitalInterface.h>
  1. include <math.h>
  2. include <gmtl/Matrix.h>
  3. include <gmtl/Generate.h>
  4. include <gmtl/Vec.h>
  1. include "IrrlichtApp.h"

// movement defines

  1. define ROTATION_INCREMENT_HORIZONTAL 0.005f
  2. define ROTATION_INCREMENT_VERTICAL 0.005f
  3. define MOVE_ANGLE_MIN 10.0f
  4. define MOVE_ANGLE_MAX 89.0f

using namespace gmtl; using namespace vrj; using namespace irr; using namespace std;

class SimpleIrrlichtApp : public IrrlichtApp { public: gadget::PositionInterface mWand; /**< Positional interface for Wand position */ gadget::PositionInterface mHead; /**< Positional interface for Head position */

gadget::DigitalInterface mButton0; /**< Digital interface for button 0 */ gadget::DigitalInterface mButton1; /**< Digital interface for button 1 */ scene::ICameraSceneNode* mCam; bool mButton0DownLastFrame; bool mButton1DownLastFrame; float mRotationX; float mRotationY;

video::IVideoDriver* driver; scene::ISceneManager* smgr; core::vector3df oldPos;

public: SimpleIrrlichtApp( ) { oldPos = core::vector3df(0.0f,0.0f,0.0f); }

virtual ~SimpleIrrlichtApp( ) { ; }

public: // ---- INITIALIZATION FUNCTIONS ---- // /** * Executes any initialization needed before the API is started. * * @post Device interfaces are initialized with the device names * we want to use. * @note This is called once before Irrlicht is initialized. */ virtual void init( );

public: // ----- Drawing Loop Functions ------ // // The drawing loop will look similar to this: // // while (drawing) // { // preFrame(); // draw(); // intraFrame(); // Drawing is happening while here // sync(); // postFrame(); // Drawing is now done // // UpdateDevices(); // } //------------------------------------

/** * Function that is called upon entry into a buffer of an OpenGL * context (window). * * @note This function is designed to be used when you want to do * something only once per buffer (ie.once for left buffer, once for * right buffer). */ virtual void bufferPreDraw( );

/** * Called before start of frame. * * @note Function called after device updates but before start of * drawing. */ virtual void preFrame( );

public: // ----- OpenGL FUNCTIONS ---- // /** * Function that is called immediately after a new OGL context is * created. * Initialize GL state here. Also used to create context specific * information. * * This is called once for each context. */ virtual void contextInit( );

/** * Function to draw the scene. * * @pre OpenGL state has correct transformation and buffer selected. * @post The current scene has been drawn. * * @note Called 1 or more times per frame. */ virtual void drawIrrlicht( );

};

  1. endif

Nu we een h-bestand hebben dat voorbereid is op input kunnen we het cpp-bestand gaan invullen. We beginnen bij het initialiseren van de invoerapparatuur en wat variabelen die we nodig hebben voor het manouvreren in de scene. Van de init maken we het volgende:

void SimpleIrrlichtApp::init() { // Initialize devices mWand.init("VJWand"); mHead.init("VJHead"); mButton0.init("VJButton0"); mButton1.init("VJButton1");

// set some navigation variables mButton0DownLastFrame = false; mButton1DownLastFrame = false; mRotationX = 0.0f; mRotationX = 0.0f; } Nu gaan we de Quake map inladen in de methode contextInit: void SimpleIrrlichtApp::contextInit() { this->createIrrlichtDevice();

if (m_device == NULL) cout << "Error initialising m_device\n"; cout << "createIrrlichtDevice done\n";

driver = m_device->getVideoDriver();

if (driver == NULL) cout << "Error getting VideoDriver\n"; cout << "getVideoDriver done\n";

smgr = m_device->getSceneManager();

if( smgr == NULL ) cout << "Error getting SceneManager\n"; cout << "getSceneManager done\n";

// unzip pk3 m_device->getFileSystem()->addZipFileArchive("media/map-20kdm2.pk3");

// get bsp scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp"); scene::ISceneNode* node = 0;

// bsp linking if (mesh) node = smgr->addOctTreeSceneNode(mesh->getMesh(0));

if (node) node->setPosition(core::vector3df(-1300,-500,-1249));

smgr->addCameraSceneNodeFPS( );

// get camera for camera movement mCam = smgr->getActiveCamera(); }

Nu we de Quake map geladen hebben moeten we de voorbereidingen voor het tekenen doen, deze komen in de bufferPreDraw: void SimpleIrrlichtApp::bufferPreDraw() { m_device->run(); driver->beginScene(true, true, video::SColor(0,200,200,200)); } Om nou de navigatie door de Quake map mogelijk te maken gaan we nu de 3D muis uitlezen, deze informatie doorgeven aan Irrlicht en camera rotatie voorberkenen:

void SimpleIrrlichtApp::preFrame() { // Setup a key event SEvent e; e.EventType = EET_KEY_INPUT_EVENT;

e.KeyInput.Key = irr::KEY_UP; // forward

// check if button0 is down if (mButton0->getData()) e.KeyInput.PressedDown = true; else e.KeyInput.PressedDown = false;

// check if a same button0 event has ben sent last frame if (mButton0DownLastFrame != e.KeyInput.PressedDown) { mButton0DownLastFrame = e.KeyInput.PressedDown; smgr->getActiveCamera()->OnEvent(e); }

e.KeyInput.Key = irr::KEY_DOWN; // backward

// check if button1 is down if (mButton1->getData()) e.KeyInput.PressedDown = true; else e.KeyInput.PressedDown = false;

// check if a same button1 event has ben sent last frame if (mButton1DownLastFrame != e.KeyInput.PressedDown) { mButton1DownLastFrame = e.KeyInput.PressedDown; smgr->getActiveCamera()->OnEvent(e); }

// get 3D-mouse movement float wandRotY(0.0), wandRotX(0.0); gmtl::Matrix44f wandMat = mWand->getData();

gmtl::EulerAngleXYZf angles;

   	gmtl::setRot( angles, wandMat );
   	wandRotX = gmtl::Math::rad2Deg( angles[0] );

wandRotY = gmtl::Math::rad2Deg( angles[1] );

if ( wandRotY > MOVE_ANGLE_MIN && wandRotY < MOVE_ANGLE_MAX ) mRotationY += ROTATION_INCREMENT_HORIZONTAL * gmtl::Math::abs(wandRotY); else if ( wandRotY < -MOVE_ANGLE_MIN && wandRotY > -MOVE_ANGLE_MAX ) mRotationY -= ROTATION_INCREMENT_HORIZONTAL * gmtl::Math::abs(wandRotY);

if ( wandRotX > MOVE_ANGLE_MIN && wandRotX < MOVE_ANGLE_MAX ) mRotationX += ROTATION_INCREMENT_VERTICAL * gmtl::Math::abs(wandRotX); else if ( wandRotX < -MOVE_ANGLE_MIN && wandRotX > -MOVE_ANGLE_MAX ) mRotationX -= ROTATION_INCREMENT_VERTICAL * gmtl::Math::abs(wandRotX);

if ( mRotationX > MOVE_ANGLE_MAX ) mRotationX = MOVE_ANGLE_MAX; else if ( mRotationX < -MOVE_ANGLE_MAX ) mRotationX = -MOVE_ANGLE_MAX; }

Als laatste moeten we nog de camerarotatie toepassen. Dit kon niet in de methode preFrame, om onbekende reden is dit object niet beschikbaar in deze methode. Daarom doen we dit in de drawIrrlicht:

void SimpleIrrlichtApp::drawIrrlicht() { // rotate cam mCam->setRotation(core::vector3df(mRotationX, mRotationY, .0f)); }

Draai het programma, je kunt nu door de gespecificeerde Quake map rondbewegen.


Huidige status

Momenteel zijn de doelstelling van het project gerealiseerd. We hebben een engine omgezet zodat deze werkt in de CAVE met vrjuggler. Daarnaast hebben we in kaart gebracht waar de problemen liggen bij het omzetten van een engine naar een CAVE applicatie.


Grafische Artefacts

Iirlicht2.jpg

Er komen zo nu en dan wat artefacts voor. Deze zijn herhaalbaar en gebeuren dan ook altijd op dezelfde plaats onder dezelfde condities. Deze komen voor zover getest alleen op het onderste scherm voor in de QuakeMap applicatie. In gewone simpele applicaties gebeurt het ook dat alles wat recht voor de cave staat weggeclipt wordt en het lijkt er op dat alles wat er achter staat niet weggeclipt wordt.


Problemen bij omzetting Irrlicht.

Voor de omzetting van irrlicht naar een VRJuggler gebruikende applicatie zijn we de volgende problemen tegengekomen:

  • Zowel Irrlicht als VRJuggler maakt een eigen OpenGL en Windows context aan. Wanneer deze twee gekoppeld worden mag er maar een context zijn.
  • De beide systemen erven over van abstracte klassen, voor de nieuwe applicatie moet er een abstracte klasse komen.
  • Het input systeem van Irrlicht is niet geschikt voor het werken met 3D input apparatuur.
  • Het laten bewegen van objecten in de VRJuggler vereist een ander transformatie systeem als in Irrlicht zit.
  • De verschillende schermen in de CAVE zullen een ander beeld moeten weergeven maar Irrlicht stuurt de data voor een scherm naar openGL. Op deze data wordt zogenaamde Frustum culling toegepast. Hierdoor wordt alleen de data gestuurd dat op dit scherm moet staan. Andere data wordt niet naar de rendering device gestuurd. Elk scherm zal z’n eigen frustum moeten krijgen.


4.2.7. Oplossingen voor omzetting Irrlicht

Voor het omzetten van Irrlicht naar een applicatie die kan samenwerken met Juggler hebben wij besloten dat we geen wijzingen aanbrengen in de Juggler code maar alleen in de Irrlicht engine wijzigingen aan te brengen. Het was ideaal geweest als er geen wijzigingen in de Irrlicht code ingevoerd zou hoeven te worden. Maar dit is niet mogelijk om te realiseren.

Om de twee contexten die worden aangemaakt terug te zetten naar een context zal er een verwijderd moeten worden. Daarom hebben we uit Irrlicht deze aanroep gehaald. Dit leverde niet veel problemen op. Het was vooral een kwestie van dingen verwijderen uit de engine.

We hebben voor de implementatie van nieuwe applicaties in de nieuwe VRIrrlicht omgeving een abstracte klasse geschreven. Deze klasse erft over van de juggler abstracte klasse en heeft een verwijzing naar het irrlictht systeem.

Voor het omschrijven van het input systeem hebben we ervoor gekozen om de input van juggler te gebruiken. Juggler biedt een vrij goed systeem om de invoer af te handelen. Dit systeem is in de demo applicatie zo aangepast dat de data die eruit vrijkomt in irrlicht verwerkt wordt. Maar verder is hier weinig aan veranderd.

De transformaties die in irrlicht worden gedaan zijn omgezet door “projection matrix” goed in te stellen.

Het probleem met meerdere vensters die een eigen frustum hebben waarop gerenderd wordt was veruit het grootste probleem. Voor een goed begrip van dit probleem is het nodig om te begrijpen hoe de frustum culling in een normaal systeem als irrlicht werkt.

In een normaal systeem met een monitor heeft het systeem maar een richting waar het naar toe kijkt. Aan de hand van deze kijkrichting wordt bepaald welke vertex’s in het beeldscherm vallen er welke erbuiten vallen. De vertex’s die buiten dit frustum vallen zullen dus niet gerenderd worden.

Dit is in het onderstaande plaatje weergegeven. De vierkanten met een rood kruis vallen buiten het viewing frustem en dus buiten het gezichtsveld van de gebruiker. Deze worden dus niet naar de weergave apparaten gestuurd waardoor er geen overbodige rekenkracht aan verspild wordt.

Iirlicht3.jpg

Maar in de juggler omgeving zijn er vier verschillende schermen die allemaal een eigen kijkrichting moeten hebben. Deze kijkrichting is alleen op te halen uit VRJuggler omdat irrlicht niet weet welke pc hij is.

Dit is in het volgende plaatje weergegeven. Het is duidelijk dat er verschillende viewing frustums zijn. Elk viewing frustum wordt door een eigen PC aangestuurd, en deze hebben dus allemaal een afzonderlijke richting. Iirlicht4.jpg


De moeilijkheid dit probleem is dat het model dat wordt gebruikt voor de omschrijving van deze richting niet overeenkomt met die van irrlicht.

Om dit om te zetten hebben we de omzetting die in OpenSG wordt gebruikt om hun stelsel naar juggler om te zetten gebruikt bekeken. Dit hebben we omgezet naar de irrlicht variant.

Materiaal

Op de website is een handleiding voor het installeren gezet. Bovendien zijn er enkele voorbeeldprojecten beschikbaar.