Aller au menu - Aller au contenu

Icône Creation de scene nodes

Avatar
Mise à jour : 23/03/2011
Difficulté : Facile Facile Creative Commons BY-SA
1 621 visites depuis 7 jours, dont 35 sur ce chapitre classé 80/786
Nous y sommes amis lecteurs, le chapitre qui va bouleverser votre vie de codeur Irrlicht (oui encore Image utilisateur ) ! Nous allons maintenant voir comment créer nos propres types de scene nodes, nous ouvrant ainsi les portes aux plus folles expérimentations et créations de nos esprits malades fertiles.

Que ce soit pour apporter une modification mineure à un type de scene node existant ou pour en créer un de toutes pièces, la démarche reste sensiblement la même et nous allons bien entendu la détailler. Sans plus attendre.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Déclaration

Vous l'aurez deviné j'imagine, il faut pour créer un nouveau type de scene node dériver la classe abstraite ISceneNode. Le code suivant est la déclaration de notre nouvelle classe, que nous allons analyser juste après :

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
#include <IRR/irrlicht.h>

namespace irr
{
namespace scene
{


class CSceneNodePerso : public irr::scene::ISceneNode
{

public:

    CSceneNodePerso(irr::scene::ISceneNode* parent,
        irr::scene::ISceneManager* mgr, s32 id=0);

    virtual void OnRegisterSceneNode();            
    virtual void render();                         

    virtual const irr::core::aabbox3d<irr::f32>& getBoundingBox() const;
    virtual irr::u32 getMaterialCount() const;
    virtual irr::video::SMaterial& getMaterial(irr::u32 i);


private :

    irr::core::aabbox3d<irr::f32> m_box;           
    irr::video::SMaterial m_material;              
};


}  // end of scene namespace
}  // end of irr namespace


Notez que nous plaçons tout naturellement cette nouvelle classe dans le namespace scene, à l'instar de tout scene node du moteur. Ligne 9, la déclaration proprement dite, puis ligne 14 le constructeur, dont les arguments sont les arguments minimaux imposés par le constructeur de ISceneNode.

Lignes 17 et 18, rien de neuf si vous avez lu le chapitre précédent (et si non vous devriez), il s'agit de définir le type du scene node en vue du rendu, puis d'effectuer celui-ci. Les choses sérieuses commencent ligne 20 où nous tombons sur un accesseur mystérieux. Son nom indique qu'il renvoie la bounding box.


Qu'est-ce qu'une bounding box ?

Il s'agit d'une "boite" ayant une forme géométrique simple et englobant une forme géométrique plus complexe. L'idée est de se servir de cette boite pour effectuer divers tests. Par exemple en ce qui concerne les collisions, plutôt que de tester directement que 2 meshs ne sont pas en collision, on effectue le test sur les bouding box (BB) qui les entourent. Comme leurs formes sont beaucoup plus simples, les calculs sont beaucoup plus rapides. Bien évidemment si les 2 BB sont en collisions alors il faut effectuer un test complet sur les meshs eux mêmes. En ce qui nous concerne directement, la BB associée au node va servir à faire du culling, ce qui, si vous vous souvenez du chapitre précédent, est utile à réduire les temps de calcul en ne prenant pas en compte les nodes qui ne sont pas dans le champ de la caméra.



Les deux accesseurs suivants servent respectivement à récupérer le nombre de materials du node et les materials eux mêmes. Sachant que nous n'allons mettre qu'un seul material pour ce node, ces accesseurs peuvent paraitre redondants, mais il est néanmoins nécessaire de les surcharger car leurs implémentations dans ISceneNode ne conviennent pas pour ce qu'on veut faire.

Et enfin lignes 27 et 28, les deux attributs de notre scene node (on commence petit), qui sont comme l'indiquent les commentaires, la bouding box et le material. L'idée va être dans un premier temps de faire d'une pierre deux coups en se servant de la bouding box comme telle et de l'afficher en plus à l'écran. Puis nous verrons ensuite une technique un peu plus complexe permettant de créer manuellement n'importe quelle forme géométrique. :)

Implémentation

Voyons maintenant l'implémentation des fonctions déclarées plus haut, en commençant par le constructeur :

Code : C++
1
2
3
4
5
6
7
8
9
irr::scene::CSceneNodePerso::CSceneNodePerso(irr::scene::ISceneNode* parent,
    irr::scene::ISceneManager* mgr, irr::s32 id)
    : irr::scene::ISceneNode(parent, mgr, id)
{
    m_material.Wireframe = false;
    m_material.Lighting = false;

    m_box = irr::core::aabbox3d<irr::f32>(-20.0f, -20.0f, -20.0f, 20.0f, 20.0f, 20.0f);
}


Comme on pouvait s'en douter, rien de bien palpitant. Les arguments sont directement renvoyés à ISceneNode qui possède l'implémentation permettant de les gérer. Lignes 5 et 6 on configure quelques aspects du material qui ne possèdent pas ces valeurs par défaut. Le plus intéressant reste encore la ligne 8 où on définit notre cube, qui aura des cotés de 40 unités et se positionnera autour de l'origine du "monde".


Vient ensuite la fonction OnRegisterSceneNode sur laquelle nous nous sommes attardés dans le chapitre précédent. Vous pouvez voir ligne 4 que nous déclarons ce node comme ESNRP_SOLID, ce qui signifie qu'il est assimilé à un objet solide, devant être rendu après l'éclairage, mais avant les objets transparents, etc... (voir le chapitre précédent pour plus de détails)

Code : C++
1
2
3
4
5
6
7
virtual void CMeshSceneNode::OnRegisterSceneNode()
{
    if (IsVisible)
        SceneManager->registerNodeForRendering(this, irr::scene::ESNRP_SOLID);

    ISceneNode::OnRegisterSceneNode();
}


La ligne 6 permet d'appeler la version de la classe ISceneNode de la fonction qui est en train d'être surchargée. Ça peut paraitre paraitre un peu tordu mais c'est tout à fait logique et permet d'économiser pas mal de lignes de codes. Voici le contenu de ISceneNode::OnRegisterSceneNode() :

Code : C++
1
2
3
4
5
6
7
8
9
virtual void OnRegisterSceneNode()
{
    if (IsVisible)
    {
        core::list<ISceneNode*>::Iterator it = Children.begin();
        for (; it != Children.end(); ++it)
        (*it)->OnRegisterSceneNode();
    }
}


Comme vous pouvez le voir, cela permet simplement d'appeler la même fonction chez tous les nodes enfants de celui-ci. Mais vous l'aviez probablement deviné si vous avez lu le chapitre précédent avec attention. ;)


Passons à la fonction de rendu. On commence par récupérer un pointeur vers le driver ligne 3, puis on modifie le material actif. La ligne 6 fait bouger "le monde" selon la matrice contenant les changements du node en coordonnées absolues. Sans cette ligne vous aurez beau essayer de modifier la position du node, rien n'y fera.

Code : C++
1
2
3
4
5
6
7
8
9
void irr::scene::CSceneNodePerso::render()
{
    irr::video::IVideoDriver* driver = SceneManager->getVideoDriver();

    driver->setMaterial(m_material);
    driver->setTransform(irr::video::ETS_WORLD, AbsoluteTransformation);

    driver->draw3DBox(m_box, irr::video::SColor(255, 255, 255, 255));
}


Et enfin ligne 8, ce qui nous intéresse le plus, le rendu en lui même du cube à l'écran. Pour ce faire rien de plus simple, il existe une fonction intégrée au driver permettant de dessiner une boite. Nous allons donc y faire appel en passant notre m_box en paramètre et en spécifiant une couleur blanche.


Il ne reste plus qu'à implémenter les accesseurs. Je vous laisse jeter un oeil par vous même, il n'y a rien d'extraordinaire. ;)

Code : C++
1
2
3
4
5
6
7
8
9
const irr::core::aabbox3d<irr::f32>& irr::scene::CSceneNodePerso::getBoundingBox() const {
    return m_box;
}
irr::u32 irr::scene::CSceneNodePerso::getMaterialCount() const {
    return 1;
}
irr::video::SMaterial& irr::scene::CSceneNodePerso::getMaterial(u32 i) {
    return m_material;
}



Mettez tout ça dans un fichier, mélangez bien fort et... et il nous manque encore un petit quelque chose. En effet, contrairement aux autres scene nodes existant nativement dans le moteur, celui là ne peut pas être ajouté au scene manager de manière classique. Il va falloir pour cela le créer manuellement, comme dans le code suivant :

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
int main(void)
{
    irr::IrrlichtDevice *device = irr::createDevice (
        irr::video::EDT_OPENGL,
        irr::core::dimension2d<irr::u32>(800,600),
        32,false,true,false,0);
    irr::video::IVideoDriver* driver = device->getVideoDriver ();
    irr::scene::ISceneManager *sceneManager = device->getSceneManager ();


    device->getCursorControl()->setVisible(false);
    sceneManager->addCameraSceneNodeFPS (0,80.0f,0.2f);

    irr::scene::CSceneNodePerso *myNode = new irr::scene::CSceneNodePerso(
        sceneManager->getRootSceneNode(), sceneManager);
    myNode->drop();


    while (device->run ())
    {
        driver->beginScene (true, true,
            irr::video::SColor (255,100,100,255));
        sceneManager->drawAll ();
        driver->endScene ();
    }

    device->drop ();
    return 0;
}

Image utilisateurImage utilisateur

Notez ligne 16 qu'on peut se débarrasser tout de suite du pointeur qu'on vient de créer puisque le scene manager a pris le node en charge et qu'il ne le supprimera que quand lui même sera supprimé. Bien, un coup de compilateur et vous devriez voir apparaitre la même chose que sur les screenshots de droite. :)

Du haut de cette pyramide, 14 chapitres vous contemplent

Histoire de complexifier un peu la chose, nous allons maintenant dessiner une pyramide à l'intérieur de cette boite. Et comme il n'existe évidement aucune fonction permettant de le faire simplement, il va falloir mettre les mains dans le cambouis (ô joie). Avant tout, un petit rappel (ou une découverte pour certains) sur la composition et la formation des meshs.

Vous le savez un mesh est un maillage, à savoir un assemblage de points (sommets, vertex en anglais) qui reliés d'une certaine manière forment des faces, qui forment elles mêmes au final une forme plus ou moins complexe. Pour faire simple, il faut deux choses pour créer un mesh :
  • Une liste de points
  • Une liste de valeurs expliquant comment les relier


Pour mieux comprendre ce qui va suivre, je vous ai fait un petit schéma maison (le losange ne sert qu'à saisir plus facilement la perspective du cube) :
Image utilisateur

Les sommets, qui sont représentés par des lettres, ont donc des coordonnées en 3D. Maintenant imaginons que nous ayons un tableau de 8 cases contenant chacune une coordonnée en 3D, qu'on peut donc assimiler à un sommet. On aimerait bien pouvoir construire le cube ci-dessus à partir de ces 8 sommets. C'est faisable bien sûr, à condition d'indiquer quel sommet fait parti de quelle face. La face du haut par exemple contient les points A,B,C et D. La face de droite les points C,D,E et F, etc...

Le driver, qui va se charger de dessiner tout ça uniquement avec des triangles, a donc besoin d'une liste qui lui permette de comprendre dans quel ordre relier les points pour former les triangles. En fait, du point de vue de la syntaxe on se contente de lui passer une liste ininterrompue de valeurs correspondant aux sommets, qu'il va regrouper par paquets de 3 pour dessiner les triangles. Ce qui nous donne pour dessiner la face du haut :

Code : Autre
1
A,B,C, A,C,D,


Et la liste complète pour le cube entier :

Code : Autre
1
2
3
4
5
6
A,B,C, C,D,A, // face du haut
E,F,G, G,H,E, // face du bas
C,D,E, E,F,C, // face de droite
B,A,H, H,G,B, // face de gauche
A,D,E, E,H,A, // face dernier plan
C,F,B, B,G,C  // face premier plan



Maintenant que nous avons compris ça, je pense qu'on peut attaquer le code. Pour commencer, il faut rajouter un attribut à notre classe :

Code : C++
1
irr::video::S3DVertex m_vertices[5];   // contient les sommets du mesh


S3DVertex est une classe d'Irrlicht permettant comme son nom l'indique de stocker les coordonnées d'un sommet en 3D. On utilise donc un tableau contenant 5 sommets, 4 pour la base et 1 pour le sommet de la pyramide. Ensuite il faut modifier le constructeur pour ajouter l'initialisation de ces sommets :

Code : C++
1
2
3
4
5
m_vertices[0] = irr::video::S3DVertex(-20,-20,-20,0,0,0,  irr::video::SColor(255,0,255,255), 0, 0);
m_vertices[1] = irr::video::S3DVertex(-20,-20,20,0,0,0, irr::video::SColor(255,255,0,255), 0, 0);
m_vertices[2] = irr::video::S3DVertex(20,-20,20,0,0,0, irr::video::SColor(255,255,255,0), 0, 0);
m_vertices[3] = irr::video::S3DVertex(20,-20,-20,0,0,0, irr::video::SColor(255,0,0,255), 0, 0);
m_vertices[4] = irr::video::S3DVertex(0,20,0,0,0,0, irr::video::SColor(255,0,255,0), 0, 0);


Quelques explications sur les valeurs indiquées. Les 3 premières correspondent à la position du point respectivement sur les axes X, Y, et Z (Y représentant l'axe vertical par défaut dans Irrlicht). Les 3 suivantes permettent d'indiquer un vecteur normal au sommet. Si vous ne savez pas ce que c'est pas de panique, nous n'en avons pas besoin dans l'immédiat (d'ailleurs je les ai laissés à 0). Sachez simplement que c'est très utile pour l'éclairage par exemple. Les deux dernières valeurs (là aussi laissées à 0) servent à faire correspondre une texture avec le mesh. En gros ces deux valeurs indiquent un point sur une image 2D (texture) et qui correspond au sommet 3D.

Toujours dans le constructeur, il y a une autre ligne à ajouter :

Code : C++
1
m_material.BackfaceCulling = false;


Cela va désactiver le backface culling, c'est à dire que toutes les faces seront visibles des deux cotés. Nous n'auront ainsi pas à nous soucier de l'ordre dans lequel les sommets d'une même face sont reliés. Et nous arrivons d'ailleurs au moment de créer la liste dont nous parlions plus haut. Dans la fonction render :

Code : C++
1
u16 indices[] = { 0,1,4, 1,2,4, 2,3,4, 3,0,4, 0,1,2, 0,2,3 };


Si vous reprenez les coordonnées 3D des sommets que nous avons déclarés plus haut, vous vous rendrez compte que les 4 premiers groupes de 3 correspondent aux cotés de la pyramide, et les 2 derniers aux deux triangles composant la base. Ensuite il ne reste plus qu'à ajouter cette ligne, toujours dans la fonction render :

Code : C++
1
driver->drawIndexedTriangleList(&m_vertices[0], 5, &indices[0], 6);

Image utilisateurImage utilisateur

Le premier paramètre sert à indiquer le tableau contenant les sommets. Le deuxième indique sa taille, ou du moins le nombre de cases à prendre à compte. Le troisième indique la liste que nous venons de créer juste au dessus. Et enfin le dernier indique le nombre de paquets de 3 contenus dans la liste. Donc le nombre de faces que nous allons dessiner.

Vous savez ce qu'il reste à faire. Mélangez, compilez, et admirez le résultat sur le screen de droite si vous ne l'avez pas déjà sous les yeux. :)
Et voilà. Que rajouter ? Si ce n'est insister encore sur le fait que nous venons de franchir un palier dans la maîtrise du moteur.

Vous êtes maintenant capables de créer vos propres types de scene nodes, d'utiliser la plupart de ceux existants, et vous maitrisez une bonne partie du processus de rendu. Un peu d'auto-satisfaction n'a jamais fait de mal, alors je pense que nous pouvons nous féliciter un moment pour le travail accompli (surtout moi d'ailleurs Image utilisateur ).
Chapitre précédent Sommaire Chapitre suivant

Partager

1 commentaire pour "Creation de scene nodes"
Note moyenne : 3.60 / 4 (122 votes)
Pseudo Commentaire
Hors ligne VivienDenizart # Posté le 04/05/2010 à 23:32:50

Salut, très bon tuto pour commencer avec Irrlicht !

Juste une remarque : quand tu implémentes la méthode OnRegisterSceneNode(), tu écris :
Code : C++
1
virtual void CMeshSceneNode::OnRegisterSceneNode()


Or :
    On n'est pas dans la délclaration de la méthode donc pas de virtual
    Je n'ai pas trouvé pourquoi tu te servais de la classe CMeshSceneNode à la place de CSceneNodePerso vu qu'on fait une surcharge.


J'ai regardé si j'avais loupé son implémentation dans une de tes autres parties mais sans succès.
Donc pour l'implémentation j'ai mis : Code : C++
1
irr::scene::CSceneNodePerso ::OnRegisterSceneNode()


C'est plus logique et en plus ça marche :p

Voir tous les commentaires