Tutoriel Irrlicht 9

Visualiseur de mesh

| Nikolaus Gebhart | Laurent Alexandre |

Traducteur

Neckara

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

Il montre comment créer des applications plus complexes avec le moteur. Nous construisons un simple visualiseur d'objets en utilisant l'API d'interface utilisateur et le gestionnaire de scène d'Irrlicht. Ce tutoriel montre comment créer et utiliser des boutons, des fenêtres, des barres d'outils, des menus, des listes déroulantes, des onglets, des zones de texte, des images, des pop-up de messages, des « skybox » et comment analyser les fichiers XML avec le lecteur XML du moteur.

Commentez

- Titre : Tutoriel Irrlicht 9

- Auteurs : Nikolaus Gebhart, Laurent Alexandre

- Traducteur : Neckara

- Parution : 14 mai 2013

- Mise à jour : 14 mai 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 de l'interface

- III. Gestion des événements

- IV. Création du moteur

- V. Lecture d'un fichier XML

- VI. Création des menus

- VII. Boucle de rendu

- VIII. Conclusion

- IX. Remerciements

I - Prérequis

Nous commençons comme dans la majorité des tutoriels : incluons tous les fichiers d'en-tête nécessaires, ajoutons un commentaire pour que le moteur soit lié avec le bon fichier .lib dans Visual Studio et déclarons quelques variables globales. Nous ajoutons aussi deux déclarations using namespace, nous n'aurons donc pas à écrire le nom complet de toutes les classes. Dans ce tutoriel, nous allons utiliser beaucoup d'objets de l'espace de nom gui.

#include <irrlicht.h>
#include "driverChoice.h"

using namespace irr;
using namespace gui;

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

Quelques variables globales seront utilisées plus tard.

IrrlichtDevice *Device = 0;
core::stringc StartUpModelFile;
core::stringw MessageText;
core::stringw Caption;
scene::ISceneNode* Model = 0;
scene::ISceneNode* SkyBox = 0;
bool Octree=false;
bool UseLight=false;

scene::ICameraSceneNode* Camera[2] = {0, 0};

// Valeurs utilisées pour identifier les éléments de l'interface utilisateur.
enum
{
GUI_ID_DIALOG_ROOT_WINDOW  = 0x10000,

GUI_ID_X_SCALE,
GUI_ID_Y_SCALE,
GUI_ID_Z_SCALE,

GUI_ID_OPEN_MODEL,
GUI_ID_SET_MODEL_ARCHIVE,
GUI_ID_LOAD_AS_OCTREE,

GUI_ID_SKY_BOX_VISIBLE,
GUI_ID_TOGGLE_DEBUG_INFO,

GUI_ID_DEBUG_OFF,
GUI_ID_DEBUG_BOUNDING_BOX,
GUI_ID_DEBUG_NORMALS,
GUI_ID_DEBUG_SKELETON,
GUI_ID_DEBUG_WIRE_OVERLAY,
GUI_ID_DEBUG_HALF_TRANSPARENT,
GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES,
GUI_ID_DEBUG_ALL,

GUI_ID_MODEL_MATERIAL_SOLID,
GUI_ID_MODEL_MATERIAL_TRANSPARENT,
GUI_ID_MODEL_MATERIAL_REFLECTION,

GUI_ID_CAMERA_MAYA,
GUI_ID_CAMERA_FIRST_PERSON,

GUI_ID_POSITION_TEXT,

GUI_ID_ABOUT,
GUI_ID_QUIT,

GUI_ID_TEXTUREFILTER,
GUI_ID_SKIN_TRANSPARENCY,
GUI_ID_SKIN_ANIMATION_FPS,

GUI_ID_BUTTON_SET_SCALE,
GUI_ID_BUTTON_SCALE_MUL10,
GUI_ID_BUTTON_SCALE_DIV10,
GUI_ID_BUTTON_OPEN_MODEL,
GUI_ID_BUTTON_SHOW_ABOUT,
GUI_ID_BUTTON_SHOW_TOOLBOX,
GUI_ID_BUTTON_SELECT_ARCHIVE,

GUI_ID_ANIMATION_INFO,

// Et quelques nombres magiques.
MAX_FRAMERATE = 80,
DEFAULT_FRAMERATE = 30
};

Alternons entre plusieurs caméras.

void setActiveCamera(scene::ICameraSceneNode* newActive)
{
if (0 == Device)
return;

scene::ICameraSceneNode * active = Device->getSceneManager()->getActiveCamera();
active->setInputReceiverEnabled(false);

newActive->setInputReceiverEnabled(true);
Device->getSceneManager()->setActiveCamera(newActive);
}

Mettons la transparence du « skin » en changeant la valeur alpha de toutes les couleurs du « skin ».

void setSkinTransparency(s32 alpha, irr::gui::IGUISkin * skin)
{
for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
{
video::SColor col = skin->getColor((EGUI_DEFAULT_COLOR)i);
col.setAlpha(alpha);
skin->setColor((EGUI_DEFAULT_COLOR)i, col);
}
}

II - Création de l'interface

Mettons à jour l'affichage du modèle redimensionné.

void updateScaleInfo(scene::ISceneNode* model)
{
IGUIElement* toolboxWnd = Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (!toolboxWnd)
return;
if (!model)
{
toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( L"-" );
toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( L"-" );
toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( L"-" );
}
else
{
core::vector3df scale = model->getScale();
toolboxWnd->getElementFromId(GUI_ID_X_SCALE, true)->setText( core::stringw(scale.X).c_str() );
toolboxWnd->getElementFromId(GUI_ID_Y_SCALE, true)->setText( core::stringw(scale.Y).c_str() );
toolboxWnd->getElementFromId(GUI_ID_Z_SCALE, true)->setText( core::stringw(scale.Z).c_str() );
}
}

La fonction showAboutText() affiche une pop-up avec un titre et un message sous la forme de texte. Le texte sera stocké dans les variables MessageText et Caption au démarrage.

void showAboutText()
{
// Crée une fenêtre modale avec le texte
// chargé à partir du fichier XML.
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), MessageText.c_str());
}

La fonction loadModel() charge un modèle et l'affiche en utilisant un addAnimatedMeshSceneNode() et le gestionnaire de scène. Rien de difficile. Il affiche aussi une petite pop-up d'information si le modèle ne peut pas être chargé.

void loadModel(const c8* fn)
{
// Modifie le nom si c'est un fichier .pk3.

io::path filename(fn);

io::path extension;
core::getFileNameExtension(extension, filename);
extension.make_lower();

// Si une texture est chargée, l'applique au modèle courant&#8230;
if (extension == ".jpg" || extension == ".pcx" ||
extension == ".png" || extension == ".ppm" ||
extension == ".pgm" || extension == ".pbm" ||
extension == ".psd" || extension == ".tga" ||
extension == ".bmp" || extension == ".wal" ||
extension == ".rgb" || extension == ".rgba")
{
video::ITexture * texture =
Device->getVideoDriver()->getTexture( filename );
if ( texture && Model )
{
// Recharge toujours la texture.
Device->getVideoDriver()->removeTexture(texture);
texture = Device->getVideoDriver()->getTexture( filename );

Model->setMaterialTexture(0, texture);
}
return;
}
// Si une archive est chargée, l'ajoute à FileArchive.
else if (extension == ".pk3" || extension == ".zip" || extension == ".pak" || extension == ".npk")
{
Device->getFileSystem()->addFileArchive(filename.c_str());
return;
}

// Charge le modèle dans le moteur.

if (Model)
Model->remove();

Model = 0;

if (extension==".irr")
{
core::array<scene::ISceneNode*> outNodes;
Device->getSceneManager()->loadScene(filename);
Device->getSceneManager()->getSceneNodesFromType(scene::ESNT_ANIMATED_MESH, outNodes);
if (outNodes.size())
Model = outNodes[0];
return;
}

scene::IAnimatedMesh* m = Device->getSceneManager()->getMesh( filename.c_str() );

if (!m)
{
// Le modèle ne peut pas être chargé.

if (StartUpModelFile != filename)
Device->getGUIEnvironment()->addMessageBox(
Caption.c_str(), L"The model could not be loaded. " \
L"Maybe it is not a supported file format.");
return;
}

// Met les propriétés du matériel par défaut.

if (Octree)
Model = Device->getSceneManager()->addOctreeSceneNode(m->getMesh(0));
else
{
scene::IAnimatedMeshSceneNode* animModel = Device->getSceneManager()->addAnimatedMeshSceneNode(m);
animModel->setAnimationSpeed(30);
Model = animModel;
}
Model->setMaterialFlag(video::EMF_LIGHTING, UseLight);
Model->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, UseLight);
//  Model->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
Model->setDebugDataVisible(scene::EDS_OFF);

// Nous avons besoin de décocher les entrées du menuw. Ce serait bien de simuler un événement menu mais
// ce n'est pas simple. Donc nous le faisons à la manière brute.
gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_TOGGLE_DEBUG_INFO, true);
if (menu)
for(int item = 1; item < 6; ++item)
menu->setItemChecked(item, false);
updateScaleInfo(Model);
}

La fonction createToolBox() crée une barre d'outils pour une fenêtre. Dans ce simple visualiseur d'objets, la barre d'outils contient uniquement des onglets avec trois zones de texte pour changer les dimensions du modèle affiché.

void createToolBox()
{
// Supprime la boîte d'outils s'il y en déjà une.
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* e = root->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (e)
e->remove();

// Crée la boîte d'outils de la fenêtre.
IGUIWindow* wnd = env->addWindow(core::rect<s32>(600,45,800,480),
false, L"Toolset", 0, GUI_ID_DIALOG_ROOT_WINDOW);

// Crée le système d'onglets et les onglets.
IGUITabControl* tab = env->addTabControl(
core::rect<s32>(2,20,800-602,480-7), wnd, true, true);

IGUITab* t1 = tab->addTab(L"Config");

// Ajoute quelques zones de texte ainsi qu'un bouton sur le premier onglet.
env->addStaticText(L"Scale:",
core::rect<s32>(10,20,60,45), false, false, t1);
env->addStaticText(L"X:", core::rect<s32>(22,48,40,66), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,46,130,66), true, t1, GUI_ID_X_SCALE);
env->addStaticText(L"Y:", core::rect<s32>(22,82,40,96), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,76,130,96), true, t1, GUI_ID_Y_SCALE);
env->addStaticText(L"Z:", core::rect<s32>(22,108,40,126), false, false, t1);
env->addEditBox(L"1.0", core::rect<s32>(40,106,130,126), true, t1, GUI_ID_Z_SCALE);

env->addButton(core::rect<s32>(10,134,85,165), t1, GUI_ID_BUTTON_SET_SCALE, L"Set");

// Boutons de redimensionnement rapide.
env->addButton(core::rect<s32>(65,20,95,40), t1, GUI_ID_BUTTON_SCALE_MUL10, L"* 10");
env->addButton(core::rect<s32>(100,20,130,40), t1, GUI_ID_BUTTON_SCALE_DIV10, L"* 0.1");

updateScaleInfo(Model);

// Ajoute le contrôle de la transparence.
env->addStaticText(L"GUI Transparency Control:",
core::rect<s32>(10,200,150,225), true, false, t1);
IGUIScrollBar* scrollbar = env->addScrollBar(true,
core::rect<s32>(10,225,150,240), t1, GUI_ID_SKIN_TRANSPARENCY);
scrollbar->setMax(255);
scrollbar->setPos(255);

// Ajoute le contrôle du nombre d'images par seconde.
env->addStaticText(L":", core::rect<s32>(10,240,150,265), true, false, t1);
env->addStaticText(L"Framerate:",
core::rect<s32>(12,240,75,265), false, false, t1);
// Informations sur l'image courante.
env->addStaticText(L"", core::rect<s32>(75,240,200,265), false, false, t1,
GUI_ID_ANIMATION_INFO);
scrollbar = env->addScrollBar(true,
core::rect<s32>(10,265,150,280), t1, GUI_ID_SKIN_ANIMATION_FPS);
scrollbar->setMax(MAX_FRAMERATE);
scrollbar->setMin(-MAX_FRAMERATE);
scrollbar->setPos(DEFAULT_FRAMERATE);
scrollbar->setSmallStep(1);
}

La fonction updateToolBox() est appelée à chaque image pour mettre à jour dynamiquement les informations dans la boîte d'outils.

void updateToolBox()
{
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement* root = env->getRootGUIElement();
IGUIElement* dlg = root->getElementFromId(GUI_ID_DIALOG_ROOT_WINDOW, true);
if (!dlg )
return;

// Met à jour les informations que nous avons sur l'animation du modèle.
IGUIStaticText *  aniInfo = (IGUIStaticText *)(dlg->getElementFromId(GUI_ID_ANIMATION_INFO, true));
if (aniInfo)
{
if ( Model && scene::ESNT_ANIMATED_MESH == Model->getType() )
{
scene::IAnimatedMeshSceneNode* animatedModel = (scene::IAnimatedMeshSceneNode*)Model;

core::stringw str( (s32)core::round_(animatedModel->getAnimationSpeed()) );
str += L" Frame: ";
str += core::stringw((s32)animatedModel->getFrameNr());
aniInfo->setText(str.c_str());
}
else
aniInfo->setText(L"");
}
}

void onKillFocus()
{
// Évite que la caméra FPS continue de bouger quand l'utilisateur presse le bouton alt-tab pendant le déplacement de la caméra.
const core::list<scene::ISceneNodeAnimator*>& animators = Camera[1]->getAnimators();
core::list<irr::scene::ISceneNodeAnimator*>::ConstIterator iter = animators.begin();
while ( iter != animators.end() )
{
if ( (*iter)->getType() == scene::ESNAT_CAMERA_FPS )
{
// Nous envoyons un événement de pression de touche de clavier pour chaque touche utilisée dans cet animateur.
scene::ISceneNodeAnimatorCameraFPS * fpsAnimator = static_cast<scene::ISceneNodeAnimatorCameraFPS*>(*iter);
const core::array<SKeyMap>& keyMap = fpsAnimator->getKeyMap();
for ( irr::u32 i=0; i< keyMap.size(); ++i )
{
irr::SEvent event;
event.EventType = EET_KEY_INPUT_EVENT;
event.KeyInput.Key = keyMap[i].KeyCode;
event.KeyInput.PressedDown = false;
fpsAnimator->OnEvent(event);
}
}
++iter;
}
}

La fonction hasModelDialog() vérifie si nous avons une fenêtre de dialogue modale ouverte.

bool hasModalDialog()
{
if ( !Device )
return false;
IGUIEnvironment* env = Device->getGUIEnvironment();
IGUIElement * focused = env->getFocus();
while ( focused )
{
if ( focused->isVisible() && focused->hasType(EGUIET_MODAL_SCREEN) )
return true;
focused = focused->getParent();
}
return false;
}

III - Gestion des événements

Pour obtenir tous les événements envoyés par les éléments d'interface utilisateur, nous avons besoin de créer un receveur d'événements. Celui-là est vraiment simple. Si un événement survient, il vérifie l'identifiant de l'émetteur et le type d'événement et commence une action déterminée par ces valeurs. Par exemple, si un élément de menu avec l'identifiant GUI_ID_OPEN_MODEL a été sélectionné, il ouvre une fenêtre d'ouverture de fichier.

class MyEventReceiver : public IEventReceiver
{
public:
virtual bool OnEvent(const SEvent& event)
{
// La touche Echap change la caméra utilisée.
if (event.EventType == EET_KEY_INPUT_EVENT &&
event.KeyInput.PressedDown == false)
{
if ( OnKeyUp(event.KeyInput.Key) )
return true;
}

if (event.EventType == EET_GUI_EVENT)
{
s32 id = event.GUIEvent.Caller->getID();
IGUIEnvironment* env = Device->getGUIEnvironment();

switch(event.GUIEvent.EventType)
{
case EGET_MENU_ITEM_SELECTED:
// Un élément de menu a été cliqué.
OnMenuItemSelected( (IGUIContextMenu*)event.GUIEvent.Caller );
break;

case EGET_FILE_SELECTED:
{
// Charge le modèle sélectionné dans la fenêtre d'ouverture de fichiers.
IGUIFileOpenDialog* dialog =
(IGUIFileOpenDialog*)event.GUIEvent.Caller;
loadModel(core::stringc(dialog->getFileName()).c_str());
}
break;

case EGET_SCROLL_BAR_CHANGED:

// Contrôle de la transparence du « skin ».
if (id == GUI_ID_SKIN_TRANSPARENCY)
{
const s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
setSkinTransparency(pos, env->getSkin());
}
// Contrôle la vitesse d'animation.
else if (id == GUI_ID_SKIN_ANIMATION_FPS)
{
const s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
if (scene::ESNT_ANIMATED_MESH == Model->getType())
((scene::IAnimatedMeshSceneNode*)Model)->setAnimationSpeed((f32)pos);
}
break;

case EGET_COMBO_BOX_CHANGED:

// Contrôle l'anti-aliasing.
if (id == GUI_ID_TEXTUREFILTER)
{
OnTextureFilterSelected( (IGUIComboBox*)event.GUIEvent.Caller );
}
break;

case EGET_BUTTON_CLICKED:

switch(id)
{
case GUI_ID_BUTTON_SET_SCALE:
{
// Fixe l'échelle.
gui::IGUIElement* root = env->getRootGUIElement();
core::vector3df scale;
core::stringc s;

s = root->getElementFromId(GUI_ID_X_SCALE, true)->getText();
scale.X = (f32)atof(s.c_str());
s = root->getElementFromId(GUI_ID_Y_SCALE, true)->getText();
scale.Y = (f32)atof(s.c_str());
s = root->getElementFromId(GUI_ID_Z_SCALE, true)->getText();
scale.Z = (f32)atof(s.c_str());

if (Model)
Model->setScale(scale);
updateScaleInfo(Model);
}
break;
case GUI_ID_BUTTON_SCALE_MUL10:
if (Model)
Model->setScale(Model->getScale()*10.f);
updateScaleInfo(Model);
break;
case GUI_ID_BUTTON_SCALE_DIV10:
if (Model)
Model->setScale(Model->getScale()*0.1f);
updateScaleInfo(Model);
break;
case GUI_ID_BUTTON_OPEN_MODEL:
env->addFileOpenDialog(L"Please select a model file to open");
break;
case GUI_ID_BUTTON_SHOW_ABOUT:
showAboutText();
break;
case GUI_ID_BUTTON_SHOW_TOOLBOX:
createToolBox();
break;
case GUI_ID_BUTTON_SELECT_ARCHIVE:
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
}

break;
default:
break;
}
}

return false;
}

Gère les événements de relâchement des touches du clavier.

bool OnKeyUp(irr::EKEY_CODE keyCode)
{
// Ne gère pas les touches si nous avons une fenêtre modale ouverte ce qui provoquerait un comportement inattendu pour utilisateur.
if ( hasModalDialog() )
return false;

if (keyCode == irr::KEY_ESCAPE)
{
if (Device)
{
scene::ICameraSceneNode * camera =
Device->getSceneManager()->getActiveCamera();
if (camera)
{
camera->setInputReceiverEnabled( !camera->isInputReceiverEnabled() );
}
return true;
}
}
else if (keyCode == irr::KEY_F1)
{
if (Device)
{
IGUIElement* elem = Device->getGUIEnvironment()->getRootGUIElement()->getElementFromId(GUI_ID_POSITION_TEXT);
if (elem)
elem->setVisible(!elem->isVisible());
}
}
else if (keyCode == irr::KEY_KEY_M)
{
if (Device)
Device->minimizeWindow();
}
else if (keyCode == irr::KEY_KEY_L)
{
UseLight=!UseLight;
if (Model)
{
Model->setMaterialFlag(video::EMF_LIGHTING, UseLight);
Model->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, UseLight);
}
}
return false;
}

Gère les événements « un élément de menu a été cliqué ».

void OnMenuItemSelected( IGUIContextMenu* menu )
{
s32 id = menu->getItemCommandId(menu->getSelectedItem());
IGUIEnvironment* env = Device->getGUIEnvironment();

switch(id)
{
case GUI_ID_OPEN_MODEL: // FilOnButtonSetScalinge -> Open Model
env->addFileOpenDialog(L"Please select a model file to open");
break;
case GUI_ID_SET_MODEL_ARCHIVE: // File -> Set Model Archive
env->addFileOpenDialog(L"Please select your game archive/directory");
break;
case GUI_ID_LOAD_AS_OCTREE: // File -> LoadAsOctree
Octree = !Octree;
menu->setItemChecked(menu->getSelectedItem(), Octree);
break;
case GUI_ID_QUIT: // File -> Quit
Device->closeDevice();
break;
case GUI_ID_SKY_BOX_VISIBLE: // View -> Skybox
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
SkyBox->setVisible(!SkyBox->isVisible());
break;
case GUI_ID_DEBUG_OFF: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem()+1, false);
menu->setItemChecked(menu->getSelectedItem()+2, false);
menu->setItemChecked(menu->getSelectedItem()+3, false);
menu->setItemChecked(menu->getSelectedItem()+4, false);
menu->setItemChecked(menu->getSelectedItem()+5, false);
menu->setItemChecked(menu->getSelectedItem()+6, false);
if (Model)
Model->setDebugDataVisible(scene::EDS_OFF);
break;
case GUI_ID_DEBUG_BOUNDING_BOX: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_BBOX));
break;
case GUI_ID_DEBUG_NORMALS: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_NORMALS));
break;
case GUI_ID_DEBUG_SKELETON: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_SKELETON));
break;
case GUI_ID_DEBUG_WIRE_OVERLAY: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_MESH_WIRE_OVERLAY));
break;
case GUI_ID_DEBUG_HALF_TRANSPARENT: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_HALF_TRANSPARENCY));
break;
case GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem(), !menu->isItemChecked(menu->getSelectedItem()));
if (Model)
Model->setDebugDataVisible((scene::E_DEBUG_SCENE_TYPE)(Model->isDebugDataVisible()^scene::EDS_BBOX_BUFFERS));
break;
case GUI_ID_DEBUG_ALL: // View -> Debug Information
menu->setItemChecked(menu->getSelectedItem()-1, true);
menu->setItemChecked(menu->getSelectedItem()-2, true);
menu->setItemChecked(menu->getSelectedItem()-3, true);
menu->setItemChecked(menu->getSelectedItem()-4, true);
menu->setItemChecked(menu->getSelectedItem()-5, true);
menu->setItemChecked(menu->getSelectedItem()-6, true);
if (Model)
Model->setDebugDataVisible(scene::EDS_FULL);
break;
case GUI_ID_ABOUT: // Help->About
showAboutText();
break;
case GUI_ID_MODEL_MATERIAL_SOLID: // View -> Material -> Solid
if (Model)
Model->setMaterialType(video::EMT_SOLID);
break;
case GUI_ID_MODEL_MATERIAL_TRANSPARENT: // View -> Material -> Transparent
if (Model)
Model->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
break;
case GUI_ID_MODEL_MATERIAL_REFLECTION: // View -> Material -> Reflection
if (Model)
Model->setMaterialType(video::EMT_SPHERE_MAP);
break;

case GUI_ID_CAMERA_MAYA:
setActiveCamera(Camera[0]);
break;
case GUI_ID_CAMERA_FIRST_PERSON:
setActiveCamera(Camera[1]);
break;
}
}

Gère l'événement lorsqu'un des filtres de texture a été sélectionné dans la liste déroulante correspondante.

void OnTextureFilterSelected( IGUIComboBox* combo )
{
s32 pos = combo->getSelected();
switch (pos)
{
case 0:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
}
break;
case 1:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, true);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, false);
}
break;
case 2:
if (Model)
{
Model->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
Model->setMaterialFlag(video::EMF_TRILINEAR_FILTER, true);
}
break;
case 3:
if (Model)
{
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, true);
}
break;
case 4:
if (Model)
{
Model->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, false);
}
break;
}
}
};

IV - Création du moteur

La plus grande et difficile partie du travail a été faite. Nous avons juste besoin d'instancier le moteur Irrlicht ainsi que tous les boutons, menus et barres d'outils. Nous commençons par le moteur comme d'habitude en utilisant createDevice(). Pour que notre application puisse attraper des événements, nous passons notre receveur d'événements en paramètre. Comme vous pouvez le voir, il y a aussi un appel à IrrlichtDevice::setResizeable(). Ceci rend la fenêtre de rendu redimensionnable, ce qui est assez utile pour un visualiseur d'objets.

int main(int argc, char* argv[])
{
// Demande un pilote à l'utilisateur.
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;

// Crée le moteur qui quitte si la création a échoué.
MyEventReceiver receiver;
Device = createDevice(driverType, core::dimension2d<u32>(800, 600),
16, false, false, false, &receiver);

if (Device == 0)
return 1; // Ne peut pas créer le pilote sélectionné.

Device->setResizable(true);

Device->setWindowCaption(L"Irrlicht Engine - Loading...");

video::IVideoDriver* driver = Device->getVideoDriver();
IGUIEnvironment* env = Device->getGUIEnvironment();
scene::ISceneManager* smgr = Device->getSceneManager();
smgr->getParameters()->setAttribute(scene::COLLADA_CREATE_SCENE_INSTANCES, true);

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

smgr->addLightSceneNode(0, core::vector3df(200,200,200),
video::SColorf(1.0f,1.0f,1.0f),2000);
smgr->setAmbientLight(video::SColorf(0.3f,0.3f,0.3f));
// Ajoute notre dossier « media » comme « répertoire de recherche ».
Device->getFileSystem()->addFileArchive("../../media/");

V - Lecture d'un fichier XML

La prochaine étape est de lire un fichier de configuration. Il est enregistré dans le format XML et ressemble un peu à ceci :

<?xml version="1.0"?>
<config>
<startUpModel file="some filename" />
<messageText caption="Irrlicht Engine Mesh Viewer">
Hello!
</messageText>
</config>

Nous avons besoin de stocker les données dedans pour initialiser les valeurs des variables globales StartUpModelFile, MessageText et Caption. Ceci est fait en utilisant l'analyseur XML du moteur Irrlicht :

// Lit la configuration à partir du fichier XML.

io::IXMLReader* xml = Device->getFileSystem()->createXMLReader( L"config.xml");

while(xml && xml->read())
{
switch(xml->getNodeType())
{
case io::EXN_TEXT:
// Dans le fichier XML, le seul texte que nous ayons est le messageText.
MessageText = xml->getNodeData();
break;
case io::EXN_ELEMENT:
{
if (core::stringw("startUpModel") == xml->getNodeName())
StartUpModelFile = xml->getAttributeValue(L"file");
else
if (core::stringw("messageText") == xml->getNodeName())
Caption = xml->getAttributeValue(L"caption");
}
break;
default:
break;
}
}

if (xml)
xml->drop(); // Ne pas oublier de supprimer le lecteur XML.

if (argc > 1)
StartUpModelFile = argv[1];

VI - Création des menus

Ce n'était pas difficile. Maintenant, mettons une police sympa et créons le menu. Il est possible de créer des sous-menus pour chaque élément de menu. Par exemple, l'appel à menu->addItem(L« File », -1, true, true); ajoute un nouvel élément de menu avec le nom « File » et l'identifiant -1. Le paramètre suivant dit que l'élément de menu devrait être activé et le dernier dit qu'il devrait être un sous-menu. Le sous-menu peut maintenant être accessible via menu->getSubMenu(0) parce que l'entrée « File » est l'élément de menu d'index 0.

// Met une jolie police.

IGUISkin* skin = env->getSkin();
IGUIFont* font = env->getFont("fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);

// Crée le menu.
gui::IGUIContextMenu* menu = env->addMenu();
menu->addItem(L"File", -1, true, true);
menu->addItem(L"View", -1, true, true);
menu->addItem(L"Camera", -1, true, true);
menu->addItem(L"Help", -1, true, true);

gui::IGUIContextMenu* submenu;
submenu = menu->getSubMenu(0);
submenu->addItem(L"Open Model File & Texture...", GUI_ID_OPEN_MODEL);
submenu->addItem(L"Set Model Archive...", GUI_ID_SET_MODEL_ARCHIVE);
submenu->addItem(L"Load as Octree", GUI_ID_LOAD_AS_OCTREE);
submenu->addSeparator();
submenu->addItem(L"Quit", GUI_ID_QUIT);

submenu = menu->getSubMenu(1);
submenu->addItem(L"sky box visible", GUI_ID_SKY_BOX_VISIBLE, true, false, true);
submenu->addItem(L"toggle model debug information", GUI_ID_TOGGLE_DEBUG_INFO, true, true);
submenu->addItem(L"model material", -1, true, true );

submenu = submenu->getSubMenu(1);
submenu->addItem(L"Off", GUI_ID_DEBUG_OFF);
submenu->addItem(L"Bounding Box", GUI_ID_DEBUG_BOUNDING_BOX);
submenu->addItem(L"Normals", GUI_ID_DEBUG_NORMALS);
submenu->addItem(L"Skeleton", GUI_ID_DEBUG_SKELETON);
submenu->addItem(L"Wire overlay", GUI_ID_DEBUG_WIRE_OVERLAY);
submenu->addItem(L"Half-Transparent", GUI_ID_DEBUG_HALF_TRANSPARENT);
submenu->addItem(L"Buffers bounding boxes", GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES);
submenu->addItem(L"All", GUI_ID_DEBUG_ALL);

submenu = menu->getSubMenu(1)->getSubMenu(2);
submenu->addItem(L"Solid", GUI_ID_MODEL_MATERIAL_SOLID);
submenu->addItem(L"Transparent", GUI_ID_MODEL_MATERIAL_TRANSPARENT);
submenu->addItem(L"Reflection", GUI_ID_MODEL_MATERIAL_REFLECTION);

submenu = menu->getSubMenu(2);
submenu->addItem(L"Maya Style", GUI_ID_CAMERA_MAYA);
submenu->addItem(L"First Person", GUI_ID_CAMERA_FIRST_PERSON);

submenu = menu->getSubMenu(3);
submenu->addItem(L"About", GUI_ID_ABOUT);

Sous le menu, nous voulons une barre d'outils, sur laquelle nous plaçons des boutons colorés et des éléments qui semblent très importants comme une « bête » liste déroulante.

// Crée la barre d'outils.

gui::IGUIToolBar* bar = env->addToolBar();

video::ITexture* image = driver->getTexture("open.png");
bar->addButton(GUI_ID_BUTTON_OPEN_MODEL, 0, L"Open a model",image, 0, false, true);

image = driver->getTexture("tools.png");
bar->addButton(GUI_ID_BUTTON_SHOW_TOOLBOX, 0, L"Open Toolset",image, 0, false, true);

image = driver->getTexture("zip.png");
bar->addButton(GUI_ID_BUTTON_SELECT_ARCHIVE, 0, L"Set Model Archive",image, 0, false, true);

image = driver->getTexture("help.png");
bar->addButton(GUI_ID_BUTTON_SHOW_ABOUT, 0, L"Open Help", image, 0, false, true);

// Crée la liste déroulante pour les filtres de textures.

gui::IGUIComboBox* box = env->addComboBox(core::rect<s32>(250,4,350,23), bar, GUI_ID_TEXTUREFILTER);
box->addItem(L"No filtering");
box->addItem(L"Bilinear");
box->addItem(L"Trilinear");
box->addItem(L"Anisotropic");
box->addItem(L"Isotropic");

VII - Boucle de rendu

Pour rendre l'éditeur un peu plus joli, nous désactivons la transparence des éléments d'interface utilisateur et nous ajoutons le logo du moteur Irrlicht. De plus, un texte montrant le nombre d'images par seconde est créé et le titre de la fenêtre est modifié.

// Désactive l'alpha.

for (s32 i=0; i<gui::EGDC_COUNT ; ++i)
{
video::SColor col = env->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
col.setAlpha(255);
env->getSkin()->setColor((gui::EGUI_DEFAULT_COLOR)i, col);
}

// Ajoute un système d'onglets.

createToolBox();

// Créé le texte pour le nombre d'images par seconde.

IGUIStaticText* fpstext = env->addStaticText(L"",
core::rect<s32>(400,4,570,23), true, false, bar);

IGUIStaticText* postext = env->addStaticText(L"",
core::rect<s32>(10,50,470,80),false, false, 0, GUI_ID_POSITION_TEXT);
postext->setVisible(false);

// Met le titre de la fenêtre.

Caption += " - [";
Caption += driver->getName();
Caption += "]";
Device->setWindowCaption(Caption.c_str());

Voilà presque toute l'application. Nous montrons simplement une pop-up « à propos » au démarrage et chargeons le premier modèle. Pour rendre le tout plus joli, une « skybox » est créée. Nous permettons aussi à l'utilisateur de contrôler la caméra pour rendre l'application un peu plus interactive. Finalement, tout est dessiné dans une boucle de rendu ordinaire.

// Montre une pop-up « à propos » et charge le modèle par défaut.
if (argc==1)
showAboutText();
loadModel(StartUpModelFile.c_str());

// Ajoute la « skybox ».

SkyBox = smgr->addSkyBoxSceneNode(
driver->getTexture("irrlicht2_up.jpg"),
driver->getTexture("irrlicht2_dn.jpg"),
driver->getTexture("irrlicht2_lf.jpg"),
driver->getTexture("irrlicht2_rt.jpg"),
driver->getTexture("irrlicht2_ft.jpg"),
driver->getTexture("irrlicht2_bk.jpg"));

// Ajoute le nœud de scène de caméra.
Camera[0] = smgr->addCameraSceneNodeMaya();
Camera[0]->setFarValue(20000.f);
// Les caméras Maya se repositionnent elles-mêmes relativement à leur cible, donc à la position de la cible où le nœud de scène de mesh est placé.
Camera[0]->setTarget(core::vector3df(0,30,0));

Camera[1] = smgr->addCameraSceneNodeFPS();
Camera[1]->setFarValue(20000.f);
Camera[1]->setPosition(core::vector3df(0,0,-70));
Camera[1]->setTarget(core::vector3df(0,30,0));

setActiveCamera(Camera[0]);

// Charge le logo du moteur Irrlicht.
IGUIImage *img =
env->addImage(driver->getTexture("irrlichtlogo2.png"),
core::position2d<s32>(10, driver->getScreenSize().Height - 128));

// Fixe les bords du logo dans le coin en bas à gauche de l'écran.
img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);

// Se rappelle de l'état donc nous pourrons remarquer quand le fenêtre perd le focus.
bool hasFocus = Device->isWindowFocused();

// Dessine tout.

while(Device->run() && driver)
{
// Capte les changements de focus (contournement en attendant qu'Irllicht intègre des événements pour cela).
bool focused = Device->isWindowFocused();
if ( hasFocus && !focused )
onKillFocus();
hasFocus = focused;

if (Device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(150,50,50,50));

smgr->drawAll();
env->drawAll();

driver->endScene();

// Met à jour les informations sur le nombre d'images par seconde.
core::stringw str(L"FPS: ");
str.append(core::stringw(driver->getFPS()));
str += L" Tris: ";
str.append(core::stringw(driver->getPrimitiveCountDrawn()));
fpstext->setText(str.c_str());

// Met à jour les informations sur la caméra courante.
scene::ICameraSceneNode* cam = Device->getSceneManager()->getActiveCamera();
str = L"Pos: ";
str.append(core::stringw(cam->getPosition().X));
str += L" ";
str.append(core::stringw(cam->getPosition().Y));
str += L" ";
str.append(core::stringw(cam->getPosition().Z));
str += L" Tgt: ";
str.append(core::stringw(cam->getTarget().X));
str += L" ";
str.append(core::stringw(cam->getTarget().Y));
str += L" ";
str.append(core::stringw(cam->getTarget().Z));
postext->setText(str.c_str());

// Met à jour la fenêtre d'outils.
updateToolBox();
}
else
Device->yield();
}

Device->drop();
return 0;
}

VIII - Conclusion

Vous pouvez désormais lire des fichiers XML avec Irrlicht et vous maîtrisez maintenant un peu mieux les éléments d'interface utilisateur.

Dans le prochain tutoriel Shaders, nous verrons comment utiliser les ombres et créer de nouveaux types de matériels avec.

IX - Remerciements

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

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