Tutoriel Irrlicht 17

Hello World pour mobiles

| Nikolaus Gebhart | Laurent Alexandre |

Traducteur

Neckara

Ce tutoriel est une traduction de l'anglais des tutoriels officiels d'Irrlicht.

Il montre comment faire un Hello World pour un mobile Windows. Il compile aussi sur d'autres plateformes. Les seules différences avec l'original sont que vous avez besoin d'une interface utilisateur sinon vous ne pourrez pas quitter l'application ainsi qu'un système de fichier relatif qui repose sur l'exécutable.

Commentez

- Titre : Tutoriel Irrlicht 17

- Auteurs : Nikolaus Gebhart, Laurent Alexandre

- Traducteur : Neckara

- Parution : 11 juin 2013

- Mise à jour : 11 juin 2013

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Neckara. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Prérequis

- II. Création du moteur Windows Mobile

- III. Boucle principale

- IV. Exemple nœud de scène personnalisé

- V. Exemple rendu de terrain

- VI. Fonction main

- VII. Conclusion

- VIII. Remerciements

I - Prérequis

#include <irrlicht.h>

#if defined ( _IRR_WINDOWS_ )
#include <windows.h>
#endif

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

#pragma comment(lib, "Irrlicht.lib")

class EventReceiver_basic : public IEventReceiver
{
private:
IrrlichtDevice *Device;
public:
EventReceiver_basic ( IrrlichtDevice *device ): Device ( device ) {}

virtual bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();

switch(event.GUIEvent.EventType)
{
case EGET_BUTTON_CLICKED:
if (id == 2)
{
Device->closeDevice();
return true;
} break;
}
}

return false;
}
};

class CSampleSceneNode : public ISceneNode
{
aabbox3d<f32> Box;
S3DVertex Vertices[4];
SMaterial Material;
public:

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

Vertices[0] = S3DVertex(0,0,10, 1,1,0, SColor(255,0,255,255), 0, 1);
Vertices[1] = S3DVertex(10,0,-10, 1,0,0, SColor(255,255,0,255), 1, 1);
Vertices[2] = S3DVertex(0,20,0, 0,1,1, SColor(255,255,255,0), 1, 0);
Vertices[3] = S3DVertex(-10,0,-10, 0,0,1, SColor(255,0,255,0), 0, 0);
Box.reset(Vertices[0].Pos);
for (s32 i=1; i<4; ++i)
Box.addInternalPoint(Vertices[i].Pos);
}
virtual void OnRegisterSceneNode()
{
if (IsVisible)
SceneManager->registerNodeForRendering(this);

ISceneNode::OnRegisterSceneNode();
}

virtual void render()
{
u16 indices[] = {   0,2,3, 2,1,3, 1,0,3, 2,0,1  };
IVideoDriver* driver = SceneManager->getVideoDriver();

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

virtual const aabbox3d<f32>& getBoundingBox() const
{
return Box;
}

virtual u32 getMaterialCount()
{
return 1;
}

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

II - Création du moteur Windows Mobile

Démarre un moteur Windows Mobile.

IrrlichtDevice *startup()
{
// EDT_SOFTWARE et EDT_BURNINGSVIDEO peuvent tout deux être utilisés
E_DRIVER_TYPE driverType = EDT_SOFTWARE; // EDT_BURNINGSVIDEO;

// Crée le moteur.
IrrlichtDevice *device = 0;

#if defined (_IRR_USE_WINDOWS_CE_DEVICE_)
// On se met en mode plein écran standard pour les mobiles : 240x320.
device = createDevice(driverType, dimension2d<u32>(240, 320), 16, true );
#else
// Sur un PC, utilise le mode fenêtré.
device = createDevice(driverType, dimension2d<u32>(240, 320), 16, false );
#endif
if ( 0 == device )
return 0;

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();

// Définit un système de fichier relatif reposant sur  l'exécutable.
#if defined (_IRR_WINDOWS_)
{
wchar_t buf[255];
GetModuleFileNameW ( 0, buf, 255 );

io::path base = buf;
base = base.subString ( 0, base.findLast ( '\\' ) + 1 );
device->getFileSystem()->addFileArchive ( base );
}
#endif

IGUIStaticText *text = guienv->addStaticText(L"FPS: 25",
rect<s32>(140,15,200,30), false, false, 0, 100 );

guienv->addButton(core::rect<int>(200,10,238,30), 0, 2, L"Quit");

// Ajoute le logo Irrlicht.
guienv->addImage(driver->getTexture("../../media/irrlichtlogo3.png"),
core::position2d<s32>(0,-2));
return device;
}

III - Boucle principale

int run ( IrrlichtDevice *device )
{
while(device->run())
if (device->isWindowActive())
{
device->getVideoDriver()->beginScene(true, true, SColor(0,100,100,100));
device->getSceneManager()->drawAll();
device->getGUIEnvironment()->drawAll();
device->getVideoDriver()->endScene ();

IGUIElement *stat = device->getGUIEnvironment()->
getRootGUIElement()->getElementFromId ( 100 );
if ( stat )
{
stringw str = L"FPS: ";
str += (s32)device->getVideoDriver()->getFPS();

stat->setText ( str.c_str() );
}
}

device->drop();
return 0;
}

IV - Exemple nœud de scène personnalisé

int example_customscenenode()
{
// Crée le moteur.
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // Ne peut pas créer le pilote sélectionné.

// Crée le moteur et la caméra.
EventReceiver_basic receiver(device);
device->setEventReceiver(&receiver);

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();

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

CSampleSceneNode *myNode =
new CSampleSceneNode(smgr->getRootSceneNode(), smgr, 666);

ISceneNodeAnimator* anim =
smgr->createRotationAnimator(vector3df(0.8f, 0, 0.8f));

if(anim)
{
myNode->addAnimator(anim);
anim->drop();
anim = 0; // Comme je ne devrais plus l'utiliser à nouveau, je m'assure que je ne le pourrai pas.
}

myNode->drop();
myNode = 0; // Comme je ne devrais plus l'utiliser à nouveau, je m'assure que je ne le pourrai pas.

return run ( device );
}

class EventReceiver_terrain : public IEventReceiver
{
public:

EventReceiver_terrain(IrrlichtDevice *device, scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :
Device ( device ), Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true)
{
Skybox->setVisible(true);
Skydome->setVisible(false);
}

bool OnEvent(const SEvent& event)
{
if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();

switch(event.GUIEvent.EventType)
{
case EGET_BUTTON_CLICKED:
if (id == 2)
{
Device->closeDevice();
return true;
} break;
}
}

// Vérifie si l'utilisateur presse les touches 'W' ou 'D'.
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown)
{
switch (event.KeyInput.Key)
{
case irr::KEY_KEY_W: // Passe en mode fil de fer.
Terrain->setMaterialFlag(video::EMF_WIREFRAME,
!Terrain->getMaterial(0).Wireframe);
Terrain->setMaterialFlag(video::EMF_POINTCLOUD, false);
return true;
case irr::KEY_KEY_P: // Passe en mode fil de fer.
Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
!Terrain->getMaterial(0).PointCloud);
Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
return true;
case irr::KEY_KEY_D: // Change le détail de la carte.
Terrain->setMaterialType(
Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
video::EMT_DETAIL_MAP : video::EMT_SOLID);
return true;
case irr::KEY_KEY_S: // Change le ciel.
showBox=!showBox;
Skybox->setVisible(showBox);
Skydome->setVisible(!showBox);
return true;
default:
break;
}
}

return false;
}

private:
IrrlichtDevice *Device;
scene::ISceneNode* Terrain;
scene::ISceneNode* Skybox;
scene::ISceneNode* Skydome;
bool showBox;
};

V - Exemple rendu de terrain

Le début de la fonction main commence comme dans la plupart des autres exemples. Nous demandons à l'utilisateur le moteur de rendu à utiliser et nous le démarrons. Cette fois-ci avec le gestionnaire de paramètres avancés.

int example_terrain()
{
// Crée le moteur.
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // Ne peut pas créer le pilote sélectionné.

En premier lieu, nous ajoutons les choses habituelles à la scène : un joli logo du moteur Irrlicht, un petit texte d'aide, une caméra contrôlée par l'utilisateur et nous cachons le curseur de la souris.

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

// Définit une autre police d'écriture.
//env->getSkin()->setFont(env->getFont("../../media/fontlucida.png"));

// Ajoute un texte d'aide.
env->addStaticText(
L"Press 'W' to change wireframe mode\nPress 'D' to toggle detail map\nPress 'S' to toggle skybox/skydome",
core::rect<s32>(5,250,235,320), true, true, 0, -1, true);

// Ajoute la caméra.
scene::ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1.2f);

camera->setPosition(core::vector3df(2700*2,255*2,2600*2));
camera->setTarget(core::vector3df(2397*2,343*2,2700*2));
camera->setFarValue(42000.0f);

// Cache le curseur de la souris.
device->getCursorControl()->setVisible(false);

Maintenant, voici venu le nœud de scène de rendu de terrain : nous l'ajoutons à la scène comme n'importe quel autre nœud de scène avec IsceneManager::addTerrainSceneNode().

Le seul paramètre que nous utilisons est le nom de fichier de la texture de relief que nous utilisons. Une texture de relief est une simple texture en nuances de gris. Le dessinateur de terrain charge et crée le terrain 3D à partir de ce fichier.

Pour rendre le terrain plus grand, nous changeons le facteur de redimensionnement à (40, 4.4, 40). Parce que nous n'avons aucune lumière dynamique dans la scène, nous désactivons l'éclairage et nous mettons le fichier terrain-texture.jpg comme texture pour le terrain et detailmap3.jpg comme seconde texture appelée carte de détails. Enfin, nous définissons les valeurs de redimensionnement pour les textures : la première texture sera répétée une seule fois dans tout le terrain et la seconde (carte de détails) vingt fois.

// Ajoute le nœud de scène de terrain.
scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(
"../../media/terrain-heightmap.bmp",
0,                  // nœud parent
-1,                 // identifiant du nœud
core::vector3df(0.f, 0.f, 0.f),     // position
core::vector3df(0.f, 0.f, 0.f),     // rotation
core::vector3df(40.f, 4.4f, 40.f),  // échelle
video::SColor ( 255, 255, 255, 255 ),   // couleur du vertex
5,                  // LOD maximum
scene::ETPS_17,             // taille de la pièce
4                   // facteur de lissage
);

terrain->setMaterialFlag(video::EMF_LIGHTING, false);

terrain->setMaterialTexture(0,
driver->getTexture("../../media/terrain-texture.jpg"));
terrain->setMaterialTexture(1,
driver->getTexture("../../media/detailmap3.jpg"));

terrain->setMaterialType(video::EMT_DETAIL_MAP);

terrain->scaleTexture(1.0f, 20.0f);

Pour être capable de faire des collisions avec le terrain, nous créons un sélectionneur de triangles. Si vous voulez savoir ce que fait le sélectionneur de triangles, jetez un coup d'œil au tutoriel Collision. Le sélectionneur de triangles travaille avec le terrain. Pour expérimenter, nous créons un animateur répondant aux collisions et l'attachons à la caméra, ainsi la caméra ne sera pas capable de voler à travers le terrain.

// Crée un sélectionneur de triangles pour le terrain.
scene::ITriangleSelector* selector
= smgr->createTerrainTriangleSelector(terrain, 0);
terrain->setTriangleSelector(selector);

// Crée l'animateur répondant aux collisions et l'attache à la caméra.
scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
selector, camera, core::vector3df(60,100,60),
core::vector3df(0,0,0),
core::vector3df(0,50,0));
selector->drop();
camera->addAnimator(anim);
anim->drop();

Si vous avez besoin d'accéder aux données du terrain, vous pouvez aussi le faire via le bout de code suivant :

scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);
terrain->getMeshBufferForLOD(*buffer, 0);
video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();
// Travaille sur les donnée ou obtient un IndexBuffer avec un appel similaire.
buffer->drop(); // Quand on a fini, jette le buffer encore une fois

Pour permettre à l'utilisateur de pouvoir passer du mode fil de fer au mode normal et vice-versa, nous créons une instance du receveur d'événements ci-dessus et nous en avertissons Irrlicht. De plus, nous ajoutons la « skybox » que nous avons déjà utilisée dans un bon nombre d'exemples d'Irrlicht ainsi qu'un « skydome » qui est alterné avec la « skybox » en pressant 'S'.

// Crée la « skybox » et le « skydome ».
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

scene::ISceneNode* skybox=smgr->addSkyBoxSceneNode(
driver->getTexture("../../media/irrlicht2_up.jpg"),
driver->getTexture("../../media/irrlicht2_dn.jpg"),
driver->getTexture("../../media/irrlicht2_lf.jpg"),
driver->getTexture("../../media/irrlicht2_rt.jpg"),
driver->getTexture("../../media/irrlicht2_ft.jpg"),
driver->getTexture("../../media/irrlicht2_bk.jpg"));
scene::ISceneNode* skydome=smgr->addSkyDomeSceneNode(driver->getTexture("../../media/skydome.jpg"),16,8,0.95f,2.0f);

driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);

// Crée le receveur d'événements.
MyEventReceiver receiver(terrain, skybox, skydome);
device->setEventReceiver(&receiver);

return run ( device );
}

int example_helloworld()
{
// Crée le moteur.
IrrlichtDevice *device = startup();
if (device == 0)
return 1; // Ne peut pas créer le pilote sélectionné.

IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();

IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
if (!mesh)
{
device->drop();
return 1;
}
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );

Pour rendre le mesh un peu plus joli, nous changeons son matériel. Nous désactivons l'éclairage parce que nous n'avons pas de lumière dynamique à l'intérieur et le mesh serait totalement noir sinon. Puis nous choisissons de boucler pour que l'animation prédéfinie STAND soit utilisée. Et enfin, nous appliquons une texture au mesh. Sans cela, le mesh serait dessiné avec une seule couleur.

if (node)
{
node->setMaterialFlag(EMF_LIGHTING, false);
node->setMD2Animation(scene::EMAT_STAND);
node->setMaterialTexture( 0, driver->getTexture("../../media/sydney.bmp") );
}

Pour regarder vers le mesh, nous plaçons une caméra dans l'espace 3D à la position (0,30,-40). La caméra regarde vers le point (0,5,0) qui est approximativement la position de notre modèle md2.

smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));

EventReceiver_basic receiver(device);
device->setEventReceiver(&receiver);

return run ( device );

}

#if defined (_IRR_USE_WINDOWS_CE_DEVICE_)
#pragma comment(linker, "/subsystem:WINDOWSCE /ENTRY:main")
#elif defined (_IRR_WINDOWS_)
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif

VI - Fonction main

int main()
{
example_helloworld ();
example_customscenenode();
//example_terrain();
}

VII - Conclusion

Vous pouvez désormais utiliser Irrlicht sur votre mobile.

Dans le prochain tutoriel Écran divisé, nous verrons comment diviser l'écran en 4 partie.

VIII - Remerciements

Merci à Nikolaus Gebhardt de nous permettre de traduire ce tutoriel.

Merci à LittleWhite pour sa relecture technique ainsi qu'à ced pour sa relecture orthographique.