Aller au menu - Aller au contenu

Icône Scene nodes et caméras

Avatar
Mise à jour : 23/03/2011
Difficulté : Facile Facile Creative Commons BY-SA
1 621 visites depuis 7 jours, dont 94 sur ce chapitre classé 80/786
Dans ce chapitre nous allons voir les derniers éléments manquant avant de pouvoir réaliser notre première véritable scène 3D. Humble certes, mais véritable quand même. :D

Lisez le attentivement dans la mesure où la plupart de ce qui s'y trouve est fondamental et nous servira pour toute la suite du tutoriel dans la compréhension de ce qui se passe.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Ce qu'est un scene node

Comme nous l'avons vu lors du précédent chapitre, le scene manager va gérer le contenu de la scène. Pour ce faire il stocke chaque élément dans un nœud (node) et organise ceux-ci de manière arborescente. Un scene node (littéralement "nœud de scène") est donc un élément du scene manager qui va contenir un objet de la scène.

Il existe beaucoup de types de scene node différents. Une caméra sera par exemple stockée dans un node de type ICameraSceneNode alors qu'une lumière sera stockée dans un node de type ILightSceneNode. Tous les scene nodes d'Irrlicht sont des dérivés d'une classe de base nommée ISceneNode, ce qui permet au scene manager de les gérer "simplement".

Ce qu'il faut retenir ici pour l'instant est que chaque élément de la scène est contenu dans un node et que c'est celui-ci qu'on manipulera la plupart du temps. Le node agit comme interface pour communiquer avec l'élément qu'il contient.



Organisation d'une scène


De par la nature arborescente du scene manager, chaque node doit posséder un node père et peut posséder un nombre potentiellement infini de node fils. Irrlicht met donc automatiquement en place un node servant de racine dans chaque scène. Celui-ci se nomme le root scene node, c'est le premier, tout en haut de la structure.

Lors de la création d'un scene node, le scene manager requiert un pointeur vers le père pour organiser les choses. Si on ne précise rien le node créé est automatiquement rattaché au root scene node. Certains types de node restent quasi-systématiquement au niveau de la racine. Les caméras par exemple ne dépendent que très rarement de quoi que soit.



Identification des nodes


Pour ajouter un node au scene manager, il faut systématiquement passer par une méthode de celui-ci, qui renverra un pointeur vers le node si tout se passe bien. La convention de nommage étant bien faite, le prototype d'une méthode d'ajout de node ressemblera toujours à ça :

Code : C++
1
2
3
I_X_SceneNode* ISceneManager::add_X_SceneNode (
    ISceneNode* parent, // pointeur vers le noeud pere
    s32 id)             // numero d'identification du noeud


Ces deux arguments sont systématiquement présents, quel que soit le node à ajouter. Si la valeur 0 est passée pour parent, le node est automatiquement rattaché au root scene node. Nous reviendrons plus loin sur le numéro d'ID. Dans ce prototype les _X_ représentent le type de scene node. Par exemple pour une caméra statique cela donne la déclaration que nous avons croisé dans le deuxième chapitre :

Code : C++
1
2
3
4
5
6
ICameraSceneNode* ISceneManager::addCameraSceneNode (
    ISceneNode* parent,        // pointeur vers le noeud pere
    core::vector3df& position, // position de la camera
    core::vector3df& lookat,   // point de mire de la camera
    s32 id,                    // numero d'identification du noeud
    bool makeActive)           // true = rend la camera active



Cela nous laisse donc deux grandes façons de retrouver et d'accéder aux scènes nodes :
  • Par pointeurs : il s'agit de la méthode "classique". On crée une variable qui va contenir le pointeur renvoyé par la méthode ajoutant le node au scene manager. Ensuite il suffit de passer par ce pointeur pour appeler n'importe quelle méthode du node.
  • Par ID : il est possible de récupérer un node via le numéro d'ID qui est spécifié lors de sa création. Attention toutefois à ne pas déclarer deux nodes avec le même ID. De plus, l'ID de n'importe quel node est -1 par défaut lors de sa création.

Il existe bien d'autres méthodes pour récupérer un accès à un ou plusieurs nodes du scene manager, mais elles sont pour la plupart imprécises et difficiles à mettre en œuvre. Le plus simple reste généralement de récupérer le pointeur donné lors de la création et de le stocker quelque part où il est simple d'y accéder. En faire un attribut de classe par exemple.

Types de données propres à Irrlicht

Irrlicht déclare bien entendu énormément de classes, de structure, d'énumérations, etc... qui lui sont propres. Mais il redéfinit aussi une bonne partie des types de données natifs du langage C++ (int, long, etc...). De plus, il propose d'autres types basiques qui sont indispensables à l'utilisation du moteur. Regardons-y de plus près :



Types natifs


Pour des raisons de compatibilité maximale, Irrlicht encapsule les types natifs du C++. Ainsi quelle que soit la plateforme sur laquelle on compile et les APIs bas niveau utilisées, le code source sera compatible sans devoir changer quoi que ce soit. Voici une liste des types de variables de base les plus courants utilisés par Irrlicht :
  • c8 : caractère sur 8 bits
  • s8 : entier signé sur 8 bits
  • u8 : entier non-signé sur 8 bits
  • s16 : entier signé sur 16 bits
  • u16 : entier non-signé sur 16 bits
  • f32 : flottant sur 32 bits
  • s32 : entier signé sur 32 bits
  • u32 : entier non-signé sur 32 bits
  • f64 : flottant sur 64 bits

Il est donc important de se rappeler qu'il faut éviter d'utiliser les classiques float, int, etc... et se servir à la place des types listés ci-dessus. Ceux-ci sont contenus dans le namespace irr.



Couleur


Je rappelle brièvement pour ceux qui ne le savent pas déjà que la couleur est la plupart du temps définie en informatique par la notation RGB [31-4]. C'est à dire que n'importe quelle couleur est définie par la combinaison des trois couleurs de base : rouge, vert et bleu. Plus une quatrième composante alpha correspondant à la transparence.

Irrlicht propose donc une classe permettant d'exprimer une couleur dans une seule variable au lieu de 4. Il s'agit de irr::video::SColor [doc], que nous avons déjà croisé dans la boucle de rendu. Cette classe stocke les composantes de manière ARGB, c'est à dire que la transparence se trouve avant le rouge, puis le vert, puis le bleu. Le code suivant permet de créer une variable contenant une couleur :

Code : C++
1
irr::video::SColor color(0, 255, 255, 255);

Ici nous avons une variable color contenant un blanc totalement transparent. Pour avoir un bleu pur totalement opaque, il faut donner les valeurs suivantes :

Code : C++
1
irr::video::SColor color(255, 0, 0, 255);



Vecteurs 2D

vecteur_2D

Un vecteur représente une direction, un déplacement [52-1]. Graphiquement un vecteur 2D ressemble à ce qui est visible sur le graphique de droite. Cet exemple montre un vecteur ayant pour valeurs 1 en X (l'axe des abscisses) et 1 en Y (l'axe des ordonnées).

La classe définie par Irrlicht pour contenir les deux valeurs d'un vecteur en deux dimensions est irr::core::vector2d<T> [doc]. Vous aurez reconnu le template qui nous permet d'utiliser quasiment n'importe quel type de base vu plus haut pour les valeurs. Pour créer le vecteur du graphique de droite par exemple, il suffit d'utiliser le code suivant :

Code : C++
1
irr::core::vector2d<irr::u32> vec(1, 1);


Et pour l'avoir en nombres flottants, le code suivant :

Code : C++
1
irr::core::vector2d<irr::f32> vec(1.0f, 1.0f);


Étant donné qu'un vecteur est défini par deux valeurs, une sur chaque axe, la classe vector2d est parfaite pour stocker des coordonnées. Les opérations entre vecteurs et coordonnées sont ainsi simplifiées, et l'utilisation de la classe est sensiblement la même quel qu'en soit la nture du contenu. À chaque fois qu'on a besoin de spécifier la position d'un node par exemple, on utilise un vecteur (3D).






Vecteurs 3D


Le principe est exactement le même que pour les vecteurs 2D sauf qu'on rajoute une dimension, donc un axe, donc une valeur. Visuellement le vecteur (1, 1, 1) donne ce qui est visible sur les graphiques ci-dessous :

vecteur_3D_1vecteur_3D_2vecteur_3D_3

La classe en question est irr::core::vector3d<T> [doc]. Elle fonctionne de la même manière que vector2d. Ce qui veut dire que le code suivant permet d'obtenir le vecteur (1, 1, 1) :

Code : C++
1
irr::core::vector3d<irr::u32> vec(1, 1, 1);


La plupart du temps, Irrlicht requiert des valeurs en nombre flottant concernant les vecteurs. Cela permet plus de précision, notamment pour indiquer des coordonnées. On utilise les vecteurs 3d en flottant tellement souvent qu'un typedef a été mis en place pour éviter d'avoir à taper vector3d<irr::f32>. Il s'agit de vector3df. Pour indiquer le vecteur ci-dessus en nombre flottant, il suffit donc de faire :

Code : C++
1
irr::core::vector3df vec(1.0f, 1.0f, 1.0f);

Les caméras

Il ne nous manque plus grand chose avant de passer à la création de la scène proprement dite, si ce n'est l'indispensable objet : la caméra. Il en existe nativement trois types dans Irrlicht. Passons-les en revue sans plus attendre :



La caméra statique



C'est la caméra de base et comme son nom l'indique, il n'y a rien de prévu pour qu'elle bouge. Nous verrons plus tard lors du chapitre dédié aux caméras et leurs animators que tout est possible et que par un miracle de la technologie moderne une caméra statique peut devenir mobile. Mais d'ici là on va considérer qu'elle ne peut pas bouger. ;)



La caméra Maya



Elle est appelée ainsi car elle reprend le même système que la caméra utilisée dans le logiciel de modélisation 3D Maya. A savoir : l'orientation avec le clic gauche et les déplacements avec le droit.



La caméra FPS



Voici le type de caméra qui nous intéresse pour cet exemple. Son utilisation est très intuitive et est reprise par beaucoup de jeux vidéo. À savoir l'orientation via la souris et les déplacements via des touches du clavier. Comme les meshs, les caméras sont gérées par le scene manager et sont stockées dans un node. Voici la fonction en question :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ISceneManager::addCameraSceneNodeFPS (
 ISceneNode * parent,       // le noeud parent de la caméra
 f32 rotateSpeed,           // la vitesse de rotation de la caméra
 f32 moveSpeed,             // la vitesse de déplacement
 s32 id,                    // numéro d'identification du noeud
 SKeyMap * keyMapArray,     // une map permettant de re-affecter les touches
 s32 keyMapSize,            // taille de la keyMap
 bool noVerticalMovement,   // autorise ou non les mouvements sur l'axe vertical
 f32 jumpSpeed,             // vitesse de déplacement lors d'un saut
 bool invertMouse,          // inverse ou non la rotation de la caméra
 bool makeActive)           // indique si la caméra doit être active ou non


Le saut ne fonctionnera que si la camera gère les collisions.

Le seul argument qui devrait vous surprendre est la keymap. Une keymap est comme son nom l'indique une "carte" permettant d'assigner des touches à certaines actions. Par défaut la caméra propose les commandes suivantes :

ActionToucheDescription
EKA_MOVE_FORWARD KEY_UP flèche du haut pour déplacement en avant
EKA_MOVE_BACKWARD KEY_DOWN flèche du bas pour déplacement en arrière
EKA_STRAFE_LEFT KEY_LEFT flèche de gauche pour déplacement à gauche
EKA_STRAFE_RIGHT KEY_RIGHT flèche de droite pour déplacement à droite
EKA_JUMP_UP KEY_KEY_J touche j pour un saut


Utiliser les flèches directionnelles comme touches permettant de se déplacer n'est pas très agréable. En utilisant les paramètres 5 et 6 on peut ré-assigner les commandes aux touches qui nous intéressent. Pour associer une action à une touche il faut utiliser une structure nommée SKeyMap qui comporte 2 éléments :

Supposons qu'on utilise une bonne vieille configuration w pour avancer, s pour reculer, a pour aller à gauche, d pour aller à droite et barre espace pour sauter. J'ai un clavier qwerty ceci dit, utilisez ce qui vous convient le mieux. Le code de la keymap correspondante sera le suivant :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
irr::SKeyMap keyMap[5];
keyMap[0].Action = irr::EKA_MOVE_FORWARD;   // avancer
keyMap[0].KeyCode = irr::KEY_KEY_W;
keyMap[1].Action = irr::EKA_MOVE_BACKWARD;  // reculer
keyMap[1].KeyCode = irr::KEY_KEY_S;
keyMap[2].Action = irr::EKA_STRAFE_LEFT;    // a gauche
keyMap[2].KeyCode = irr::KEY_KEY_A;
keyMap[3].Action = irr::EKA_STRAFE_RIGHT;   // a droite
keyMap[3].KeyCode = irr::KEY_KEY_D;
keyMap[4].Action = irr::EKA_JUMP_UP;        // saut
keyMap[4].KeyCode = irr::KEY_SPACE;


Rien de compliqué si vous avez compris ce qui précède. On crée un tableau pour contenir les instances de SKeyMap. Il ne reste plus qu'à le passer en argument lors de la création de la caméra :

Code : C++
1
sceneManager->addCameraSceneNodeFPS (0, 100.0f, 0.1f, -1, keyMap, 5);

Il est important de préciser la taille exacte de la keymap, autrement il y a de fortes chances qu'elle ne fonctionne pas correctement. En revanche il est inutile de préciser les derniers arguments si leur valeur par défaut correspond à ce qui nous arrange. Ce qui est le cas en l’occurrence.

Création d'une scène

Maintenant que nous avons tout ce qu'il faut pour réaliser une scène, il ne reste plus qu'à trouver quoi mettre dedans. On va commencer petit avec un cube de test que nous allons ajouter via le scene manager. La méthode [doc] est la suivante :

Code : C++
1
2
3
4
5
6
7
IMeshSceneNode* ISceneManager::addCubeSceneNode (
    f32 size,                  // longueur d'une arrete
    ISceneNode* parent,        //
    s32 id,                    // 
    core::vector3df& position, // position du cube
    core::vector3df& rotation, // orientation du cube
    core::vector3df& scale))   // echelle du cube


Pour achever le tout il nous manque deux lignes de code qui vont faire office de touche finale. La première sert à rendre le curseur invisible, et la deuxième à afficher le cube en mode filaire (nous verrons dans le prochain chapitre ce que cela signifie) :

Code : C++
1
2
device->getCursorControl ()-> setVisible (false); // curseur invisible
cube->setMaterialFlag(irr::video::EMF_WIREFRAME, true);


Le code complet final donne donc :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <irr/irrlicht.h>


int main(void) {
 
    irr::IrrlichtDevice* device = irr::createDevice(  // creation du device
        irr::video::EDT_OPENGL,                       // API = OpenGL
        irr::core::dimension2d<irr::u32>(200,200),    // taille fenetre 640x480p
        32);                                          // 32 bits par pixel

    irr::video::IVideoDriver* driver =                // video driver
        device->getVideoDriver ();
    irr::scene::ISceneManager* sceneManager =         // scene manager
        device->getSceneManager ();
 
    device->getCursorControl ()-> setVisible (false); // curseur invisible
 
    
    /* CUBE */

    irr::scene::IMeshSceneNode* cube =         // pointeur vers le node
        sceneManager->addCubeSceneNode(        // la creation du cube
            10.0f,                             // cote de 10 unites
            0,                                 // parent = racine
            -1,                                // pas d'ID
            irr::core::vector3df(              // le vecteur de position
                0.0f,                          // origine en X
                0.0f,                          // origine en Y
                20.0f));                       // +20 unites en Z
    
    cube->setMaterialFlag(irr::video::EMF_WIREFRAME, true);
    

    /* CAMERA */
    
    irr::SKeyMap keyMap[5];                    // re-assigne les commandes
    keyMap[0].Action = irr::EKA_MOVE_FORWARD;  // avancer
    keyMap[0].KeyCode = irr::KEY_KEY_W;        // w
    keyMap[1].Action = irr::EKA_MOVE_BACKWARD; // reculer
    keyMap[1].KeyCode = irr::KEY_KEY_S;        // s
    keyMap[2].Action = irr::EKA_STRAFE_LEFT;   // a gauche
    keyMap[2].KeyCode = irr::KEY_KEY_A;        // a
    keyMap[3].Action = irr::EKA_STRAFE_RIGHT;  // a droite
    keyMap[3].KeyCode = irr::KEY_KEY_D;        // d
    keyMap[4].Action = irr::EKA_JUMP_UP;       // saut
    keyMap[4].KeyCode = irr::KEY_SPACE;        // barre espace

    sceneManager->addCameraSceneNodeFPS(       // ajout de la camera FPS
        0,                                     // pas de noeud parent
        100.0f,                                // vitesse de rotation
        0.1f,                                  // vitesse de deplacement
        -1,                                    // pas de numero d'ID
        keyMap,                                // on change la keymap
        5);                                    // avec une taille de 5
    
    
    /* RENDU */
    
    irr::video::SColor color(                  // contient la couleur blanc
        255,                                   // composante A alpha (transparence)
        255,                                   // composante R rouge
        255,                                   // composante G verte
        255);                                  // composante B bleue
    
    while (device->run()) {                    // la boucle de rendu
        driver->beginScene(true, true, color); // demarre le rendu
        sceneManager->drawAll ();              // calcule le rendu
        driver->endScene ();                   // affiche le rendu
    }
 
    device->drop ();                           // libere la memoire
    return 0;
}


Pour ceux qui n'admirent pas le résultat directement sur leur écran, voici un aperçu de ce que ça donne :

cube_1cube_2cube_3cube_4
Chapitre précédent Sommaire Chapitre suivant

Partager

4 commentaires pour "Scene nodes et caméras"
Note moyenne : 3.60 / 4 (122 votes)
Pseudo Commentaire
Hors ligne Petit Phil's # Posté le 19/07/2011 à 12:18:20
Avatar

Avis : Très bon

Ville : Montromant
Pays : France métropolitaine

Petite précision pour ceux qui comme moi ne savaient pas :D :

Si j'ai bien comprit, entier signé = peut être négatif, entier non signé = toujours positif. Ils ont la même taille dans la mémoire.

Exemple:
¤ int > Entier > plage (sur processeur 32 bits): -2 147 483 648 à 2 147 483 647
¤ unsigned int > Entier non signé > plage (sur processeur 32 bits): 0 à 4 294 967 295

L'or s'éprouve avec le feu ! ;)

OS: Ubuntu 11.10 / Windows7 - - Processeur Intel® Core™ i3 CPU M 380 @ 2.53GHz × 4 - - Carte graphique RADEON HD6370M 1GB
 
Hors ligne Petit Phil's # Posté le 19/07/2011 à 12:29:55
Avatar

Avis : Très bon

Ville : Montromant
Pays : France métropolitaine

A oui aussi, dans la partie des vecteurs 2D:

Citation
Un vecteur représente une direction, un déplacement [todo ref tuto yno].

Pas de lien pour "[todo ref tuto yno]" ?

Enfin bon tu l'explique bien avec un joli schéma de toute façon ;)

Et:

Citation
Étant donné qu'un vecteur est défini par deux valeurs, une sur chaque axe, la classe vector2d est parfaite pour stocker des coordonnées. Les opérations entre vecteurs et coordonnées sont ainsi simplifiées, et l'utilisation de la classe est sensiblement la même quel qu'en soit la nture du contenu.

^^

L'or s'éprouve avec le feu ! ;)

OS: Ubuntu 11.10 / Windows7 - - Processeur Intel® Core™ i3 CPU M 380 @ 2.53GHz × 4 - - Carte graphique RADEON HD6370M 1GB
 
Hors ligne -Dr3ck- # Posté le 22/01/2012 à 20:26:35
///Testostérone\\\
Avatar

Avis : Très bon

Très bon tuto, merci à toi, vraiment !

U MAD BRO ? Image utilisateur
 
Hors ligne yomanxb # Posté le 22/04/2012 à 17:39:30
Avatar

Études : Lycée Pierre-Paul Riquet - Saint-Orens-de-Gameville

super tuto mais j'ai un probleme j'ai tout fait mais des que je lance sa plante et sa me met que sa a cesse de fonctionné! alors j'ai tout copié/collé mais sa me fait pareil je comprend pas!!

Voir tous les commentaires