I. Prérequis

Notez que le moteur de rendu de terrain d'Irrlicht est basé sur le GeoMipMapSceneNode de Spintz que je remercie de tout cœur. DuesXL fournit une nouvelle solution élégante pour construire des aires plus grandes sur des petites textures de relief → le lissage de terrain.

Image non disponible

Pour commencer, il n'y a rien de spécial. Nous incluons les fichiers d'en-têtes nécessaires et créons un détecteur d'événements pour détecter si l'utilisateur appuie sur une touche : la touche 'W' passe au mode fil de fer, la touche 'P' passe au mode nuage de points et la touche 'D' permet d'alterner entre les niveaux de détails.

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

using namespace irr;

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


class MyEventReceiver : public IEventReceiver
{
public:

    MyEventReceiver(scene::ISceneNode* terrain, scene::ISceneNode* skybox, scene::ISceneNode* skydome) :
        Terrain(terrain), Skybox(skybox), Skydome(skydome), showBox(true), showDebug(false)
    {
        Skybox->setVisible(showBox);
        Skydome->setVisible(!showBox);
    }

    bool OnEvent(const SEvent& event)
    {
        // Vérifie si l'utilisateur appuie sur 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 au 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 au mode nuage de points.
                Terrain->setMaterialFlag(video::EMF_POINTCLOUD,
                        !Terrain->getMaterial(0).PointCloud);
                Terrain->setMaterialFlag(video::EMF_WIREFRAME, false);
                return true;
            case irr::KEY_KEY_D: // Alterne entre les niveaux de détails.
                Terrain->setMaterialType(
                    Terrain->getMaterial(0).MaterialType == video::EMT_SOLID ?
                    video::EMT_DETAIL_MAP : video::EMT_SOLID);
                return true;
            case irr::KEY_KEY_S: // Alterne les cieux.
                showBox=!showBox;
                Skybox->setVisible(showBox);
                Skydome->setVisible(!showBox);
                return true;
            case irr::KEY_KEY_X: // Alterne les informations de débogage.
                showDebug=!showDebug;
                Terrain->setDebugDataVisible(showDebug?scene::EDS_BBOX_ALL:scene::EDS_OFF);
                return true;
            default:
                break;
            }
        }

        return false;
    }

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

La fonction main commence comme dans une grande partie des autres tutoriels. Nous demandons à l'utilisateur le rendu désiré et nous le démarrons. Cette fois avec le gestionnaire de paramètres avancés.

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

    // Crée le moteur avec tous les paramètres de création.
    // Vous pouvez ajouter plus de paramètres si vous le souhaitez, regardez irr::SIrrlichtCreationParameters.
    irr::SIrrlichtCreationParameters params;
    params.DriverType=driverType;
    params.WindowSize=core::dimension2d<u32>(640, 480);
    IrrlichtDevice* device = createDeviceEx(params);

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

Premièrement, nous ajoutons tous les objets standards à 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.

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

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

    // Ajoute le logo d'Irrlicht.
    env->addImage(driver->getTexture("../../media/irrlichtlogo2.png"),
        core::position2d<s32>(10,10));

    // Fixe les autres polices.
    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>(10,421,250,475), 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);

II. Ajout du terrain

Maintenant, voici venu le nœud de scène du 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 nuance 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 fixons les valeurs de redimensionnement pour la texture : la première texture sera répétée une seule fois dans tout le terrain et la seconde (la carte de détails) vingt fois.

 
Sélectionnez
    // 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 capables 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 sur les collisions. Le sélectionneur de triangles travaille avec le terrain. Pour l'expérimenter, nous créons un animateur de réponses aux collisions et l'attachons à la caméra ainsi la caméra ne sera pas capable de voler à travers le terrain.

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

    // Crée l'animateur de réponses 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 :

 
Sélectionnez
    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ées ou obtient un IndexBuffer avec un appel similaire.
    buffer->drop(); // Quand on a fini, jette le tampon une nouvelle 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'.

 
Sélectionnez
    // 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);

Voilà, dessinons le tout.

 
Sélectionnez
    int lastFPS = -1;

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

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

        driver->endScene();

        // Montre le nombre d'images par seconde dans le titre de la fenêtre.
        int fps = driver->getFPS();
        if (lastFPS != fps)
        {
            core::stringw str = L"Terrain Renderer - Irrlicht Engine [";
            str += driver->getName();
            str += "] FPS:";
            str += fps;
            // Affiche aussi la hauteur actuelle du terrain par rapport à la position de la caméra.
            // Nous pouvons utiliser la position de la camera, car le terrain est placé à l'origine du système de coordonnées.
            str += " Height: ";
            str += terrain->getHeight(camera->getAbsolutePosition().X,
                    camera->getAbsolutePosition().Z);

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

    device->drop();
    
    return 0;
}

Maintenant, vous savez utiliser les terrains dans Irrlicht.

III. Conclusion

Vous pouvez désormais afficher des terrains avec Irrlicht.

Dans le prochain tutoriel Rendu dans une texture, nous verrons comment dessiner sur des textures.

IV. Remerciements

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

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