I. Sélectionneur de triangles

Image non disponible

Pour commencer, nous prenons le code du deuxième tutoriel qui charge et affiche un niveau de Quake 3. Nous utiliserons la carte pour marcher dedans et choisir des triangles. De plus, nous y placerons trois modèles animés pour le choix des triangles. Tout comme le deuxième tutoriel, le code suivant, démarre le moteur et charge le niveau.

 
Sélectionnez
#include <irrlicht.h>
#include "driverChoice.h"

using namespace irr;

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

enum
{
    // J'utilise ce IsceneNode ID pour indiquer un nœud de scène qui
    // ne pourra pas être choisi par le getSceneNodeAndCollisionPointFromRay().
    ID_IsNotPickable = 0,

    // J'utilise ce fanion dans les ISceneNode ID pour indiquer que le nœud de scène
    // peut être choisi par un rayon de sélection.
    IDFlag_IsPickable = 1 << 0,

    // J'utilise ce fanion dans ISceneNode ID pour indiquer que le
    // nœud de scène peut être mis en surbrillance. Dans cet exemple, les
    // hominidés peuvent être mis en surbrillance, mais le mesh de la carte ne le peut pas.
    IDFlag_IsHighlightable = 1 << 1
};

int main()
{
    // Demande à l'utilisateur un pilote.
    video::E_DRIVER_TYPE driverType=driverChoiceConsole();
    if (driverType==video::EDT_COUNT)
        return 1;

    // Crée le moteur.

    IrrlichtDevice *device =
        createDevice(driverType, core::dimension2d<u32>(640, 480), 16, false);

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

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

    device->getFileSystem()->addFileArchive("../../media/map-20kdm2.pk3");

    scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");
    scene::IMeshSceneNode* q3node = 0;

    // Le mesh de Quake peut être choisi, mais ne peux pas être mis en surbrillance.
    if (q3levelmesh)
        q3node = smgr->addOctreeSceneNode(q3levelmesh->getMesh(0), 0, IDFlag_IsPickable);

Jusqu'ici, tout va bien, nous avons chargé le niveau de Quake 3 comme dans le deuxième tutoriel. Maintenant, quelque chose de différent : nous créons un sélectionneur de triangles. Un sélectionneur de triangles est une classe pouvant parcourir les triangles d'un nœud de scène pour faire différentes choses avec, par exemple de la détection de collisions. Il y a différents sélectionneurs de triangles et tous peuvent être créés avec le IsceneManager. Dans cet exemple, nous créons un OctreeTriangleSelector, qui optimise un petit peu les sorties de triangles en les réduisant comme un octree. Ceci est très utile pour les énormes mesh comme les cartes Quake 3. Après nous créons le sélectionneur de triangles, nous l'attachons au q3node. Ceci n'est pas nécessaire, mais dans ce cas, nous avons besoin de nous occuper du sélectionneur, par exemple pour le supprimer une fois que nous n'en aurons plus besoin.

 
Sélectionnez
    scene::ITriangleSelector* selector = 0;

    if (q3node)
    {
        q3node->setPosition(core::vector3df(-1350,-130,-1400));

        selector = smgr->createOctreeTriangleSelector(
                q3node->getMesh(), q3node, 128);
        q3node->setTriangleSelector(selector);
        // Nous n'en avons pas encore fini avec ce sélecteur donc ne le supprimons pas.
    }

II. Animateurs de réponse à des collisions

Nous ajoutons une caméra FPS à la scène, ainsi nous pourrons voir et nous déplacer dans le niveau de Quake 3 comme dans le deuxième tutoriel. Mais cette fois-ci, nous ajoutons un animateur spécial à la caméra : un animateur réagissant aux collisions. Cet animateur modifie le nœud de scène avec lequel il est attaché, pour éviter de se déplacer à travers les murs et lui ajoute une gravité. La seule chose que nous avons à dire à l'animateur, c'est à quoi ressemble le monde, quelle taille fait le nœud de scène, quelle gravité appliquer, etc. Une fois que l'animateur répondant aux collisions a été attaché à la caméra, nous n'avons plus rien à faire pour la détection de collisions, tout sera géré automatiquement. Le reste du code de détection de collision qui suit sert pour la sélection du triangle. Notez aussi une autre caractéristique sympa : l'animateur répondant aux collisions peut aussi être attaché à n'importe quel autre nœud de scène et pas uniquement aux caméras. Il peut aussi être mélangé avec d'autres animateurs de nœuds de scène. Ainsi détecter la collision et réagir à celle-ci est une chose vraiment très facile à mettre en place dans le moteur Irrlicht.

Maintenant, nous allons regarder de plus près les paramètres de createCollisionResponseAnimator(). Le premier paramètre est le TriangleSelector qui indique comment les collisions sont organisées dans le monde. Le second paramètre est le nœud de scène, qui sera affecté par la détection de collisions. Dans notre cas, il s'agit de la caméra. Le troisième définit la grosseur de l'objet, c'est le radius d'un ellipsoïde. Testez et essayez de changer le radius avec des valeurs plus petites, la caméra sera alors capable de se déplacer plus près des murs. Le paramètre suivant est la direction et la vitesse de la gravité. Nous allons le mettre à (0,-10,0) qui est proche de la réalité en supposant que notre unité soit le mètre. Vous pouvez le mettre à (0,0,0) pour désactiver la gravité. La dernière valeur est juste une translation : sans cela, l'ellipsoïde avec lequel la détection de collisions est faite sera autour de la caméra et celle-ci serait en son centre. Mais comme tout être humain, nous sommes habitués à avoir nos yeux en haut et non au milieu du corps avec lequel nous entrons en collision avec notre monde. Donc nous plaçons le nœud de scène 50 unités au-dessus du centre de l'ellipsoïde avec ce paramètre. Voilà, maintenant la détection de collision fonctionne.

 
Sélectionnez
    // Met une vitesse de saut à trois unités par seconde, ce qui donne un saut plutôt réaliste
    // quand utilisé avec la gravité (0, -10, 0) dans l'animateur répondant aux collisions.
    scene::ICameraSceneNode* camera =
        smgr->addCameraSceneNodeFPS(0, 100.0f, .3f, ID_IsNotPickable, 0, 0, true, 3.f);
    camera->setPosition(core::vector3df(50,50,-60));
    camera->setTarget(core::vector3df(-70,30,-60));

    if (selector)
    {
        scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(
            selector, camera, core::vector3df(30,50,30),
            core::vector3df(0,-10,0), core::vector3df(0,30,0));
        selector->drop(); // Aussitôt qu'on a fini avec le sélecteur, jetons-le.
        camera->addAnimator(anim);
        anim->drop();  // De même, jetons l'animateur quand nous avons fini de le référencer.
    }

    // Maintenant, nous créons trois personnages animés que nous pourrons choisir, ainsi qu'une lumière dynamique pour les éclairer,
    // et un panneau pour dessiner  nous trouvons une intersection.

    // Tout d'abord, débarrassons-nous du curseur de la souris. Nous utiliserons un panneau pour montrer ce à quoi nous regardons.
    device->getCursorControl()->setVisible(false);

    // On ajoute le panneau.
    scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();
    bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );
    bill->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));
    bill->setMaterialFlag(video::EMF_LIGHTING, false);
    bill->setMaterialFlag(video::EMF_ZBUFFER, false);
    bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));
    bill->setID(ID_IsNotPickable); // Ceci s'assure que nous n'effectuons pas le choix avec le rayon accidentellement.

Ajoutons trois hominidés animés que nous pouvons choisir en utilisant une intersection rayon/triangle. Leur animation est plutôt lente pour rendre la sélection de triangles en cours plus facile à observer.

 
Sélectionnez
    scene::IAnimatedMeshSceneNode* node = 0;

    video::SMaterial material;

    // Ajoute un nœud MD2 qui utilise une animation basée sur des vertex.
    node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/faerie.md2"),
                        0, IDFlag_IsPickable | IDFlag_IsHighlightable);
    node->setPosition(core::vector3df(-90,-15,-140)); // Pose ses pieds au sol.
    node->setScale(core::vector3df(1.6f)); // Le réduit de manière réaliste
    node->setMD2Animation(scene::EMAT_POINT);
    node->setAnimationSpeed(20.f);
    material.setTexture(0, driver->getTexture("../../media/faerie2.bmp"));
    material.Lighting = true;
    material.NormalizeNormals = true;
    node->getMaterial(0) = material;

    // Maintenant, crée un sélectionneur de triangles. Le sélectionneur saura qu'il
    // est associé avec un nœud animé et se mettra à jour si nécessaire.
    selector = smgr->createTriangleSelector(node);
    node->setTriangleSelector(selector);
    selector->drop(); // Nous avons fini avec ce sélectionneur, jetons-le maintenant.

    // Et ce fichier B3D utilise une animation d'un squelette attaché au modèle.
    node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/ninja.b3d"),
                        0, IDFlag_IsPickable | IDFlag_IsHighlightable);
    node->setScale(core::vector3df(10));
    node->setPosition(core::vector3df(-75,-66,-80));
    node->setRotation(core::vector3df(0,90,0));
    node->setAnimationSpeed(8.f);
    node->getMaterial(0).NormalizeNormals = true;
    node->getMaterial(0).Lighting = true;
    // La même chose que nous avons déjà faite ci-dessus.
    selector = smgr->createTriangleSelector(node);
    node->setTriangleSelector(selector);
    selector->drop();

    // Ce fichier X utilise une animation d'un squelette non attaché au modèle.
    node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/dwarf.x"),
                        0, IDFlag_IsPickable | IDFlag_IsHighlightable);
    node->setPosition(core::vector3df(-70,-66,-30)); // Pose ses pieds au sol.
    node->setRotation(core::vector3df(0,-90,0)); // Et le tourne vers la caméra.
    node->setAnimationSpeed(20.f);
    node->getMaterial(0).Lighting = true;
    selector = smgr->createTriangleSelector(node);
    node->setTriangleSelector(selector);
    selector->drop();


    // Et ce fichier mdl utilise une animation d'un squelette attaché au modèle.
    node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/yodan.mdl"),
                        0, IDFlag_IsPickable | IDFlag_IsHighlightable);
    node->setPosition(core::vector3df(-90,-25,20));
    node->setScale(core::vector3df(0.8f));
    node->getMaterial(0).Lighting = true;
    node->setAnimationSpeed(20.f);

    // La même chose que nous avons déjà faite ci-dessus.
    selector = smgr->createTriangleSelector(node);
    node->setTriangleSelector(selector);
    selector->drop();

    material.setTexture(0, 0);
    material.Lighting = false;

    // Ajoute une lumière, ainsi les nœuds non sélectionnés ne seront pas complètement noir.
    scene::ILightSceneNode * light = smgr->addLightSceneNode(0, core::vector3df(-60,100,400),
        video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f);
    light->setID(ID_IsNotPickable); // Le rend invalide pour la sélection.

    // Se souvient quel nœud de scène est mis en surbrillance.
    scene::ISceneNode* highlightedSceneNode = 0;
    scene::ISceneCollisionManager* collMan = smgr->getSceneCollisionManager();
    int lastFPS = -1;

    // Dessine le triangle sélectionné seulement en fil de fer.
    material.Wireframe=true;

    while(device->run())
    if (device->isWindowActive())
    {
        driver->beginScene(true, true, 0);
        smgr->drawAll();

        // Éteint tous les nœuds de scène mis en surbrillance.
        if (highlightedSceneNode)
        {
            highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);
            highlightedSceneNode = 0;
        }

        // Toutes les intersections dans cet exemple sont effectuées avec un rayon sortant de la caméra avec une distance de 1000.
        // Vous pouvez facilement le modifier pour vérifier (p. ex.) une trajectoire de balle
        // ou la position d'une épée, ou créer un rayon à partir d'un clic de souris en utilisant
        // ISceneCollisionManager::getRayFromScreenCoordinates().
        core::line3d<f32> ray;
        ray.start = camera->getPosition();
        ray.end = ray.start + (camera->getTarget() - ray.start).normalize() * 1000.0f;

        // Suit le point de l'intersection courante avec une carte ou un mesh.
        core::vector3df intersection;
        // Utilisé pour montrer quel triangle a été atteint.
        core::triangle3df hitTriangle;

        // Cet appel est tout ce dont vous avez besoin pour faire une collision rayon/rectangle sur tous les nœuds de scène
        // qui ont un sélectionneur de triangles qui est inclus dans le mesh de la carte Quake 3. Il trouve la collision point/triangle la plus proche
        // et retourne le nœud de scène contenant ce point.
        // Irrlicht fournit d'autres types de sélection que le sélectionneur rayon/triangle comme les sélectionneurs
        // rayon/boîte et ellipse/triangle avec les fonctions d'aides associées.
        // Regardez les méthodes de ISceneCollisionManager.
        scene::ISceneNode * selectedSceneNode =
            collMan->getSceneNodeAndCollisionPointFromRay(
                    ray,
                    intersection, // Sera la position de la collision.
                    hitTriangle, // Sera le triangle atteint par la collision.
                    IDFlag_IsPickable, // S'assure que seuls les nœuds qu'on a pourront être choisis.
                    0); // Vérifie la scène entière (c'est actuellement la valeur par défaut).

        // Si le rayon atteint quelque chose, déplace le panneau à la position de la collision
        // et dessine le triangle qui a été atteint.
        if(selectedSceneNode)
        {
            bill->setPosition(intersection);

            // Nous avons besoin de réinitialiser la transformation avant de faire notre propre rendu.
            driver->setTransform(video::ETS_WORLD, core::matrix4());
            driver->setMaterial(material);
            driver->draw3DTriangle(hitTriangle, video::SColor(0,255,0,0));

            // Nous pouvons vérifier les fanions pour le nœud de scène qui a été atteint pour voir s'il devrait être
            // mis en surbrillance. Les nœuds de scène animés peuvent être sélectionnés, mais pas le mesh du niveau de Quake.
            if((selectedSceneNode->getID() & IDFlag_IsHighlightable) == IDFlag_IsHighlightable)
            {
                highlightedSceneNode = selectedSceneNode;

                // Mettre en surbrillance, dans ce cas, signifie désactiver l'éclairage pour le nœud.
                // Ce qui signifie qu'il sera dessiné avec une pleine luminosité.
                highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, false);
            }
        }

        // Nous avons tout fini, arrêtons donc la scène.
        driver->endScene();

        int fps = driver->getFPS();

        if (lastFPS != fps)
        {
            core::stringw str = L"Collision detection example - Irrlicht Engine [";
            str += driver->getName();
            str += "] FPS:";
            str += fps;

            device->setWindowCaption(str.c_str());
            lastFPS = fps;
        }
    }

    device->drop();

    return 0;
}

III. Conclusion

Vous pouvez désormais gérer des collisions avec Irrlicht.

Dans le prochain tutoriel Effets spéciaux, nous verrons comment créer des effets spéciaux comme un nœud de scène de surface d'eau.

IV. Remerciements

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

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