I. Prérequis

Image non disponible

Ok, commençons avec les en-têtes (je pense qu'il n'y a rien à en dire)..

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

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

// Espaces de noms pour le moteur.
using namespace irr;
using namespace video;
using namespace core;
using namespace scene;
using namespace io;
using namespace gui;

Ceci est le type des fonctions qui déterminent les couleurs.

 
Sélectionnez
typedef SColor colour_func(f32 x, f32 y, f32 z);

II. Fonctions de coloration

Et voici un ensemble de fonctions pouvant être utilisées pour colorer les nœuds lors de la création du mesh.

 
Sélectionnez
// Nuance de gris, basé sur la hauteur.
SColor grey(f32, f32, f32 z)
{
    u32 n = (u32)(255.f * z);
    return SColor(255, n, n, n);
}

// Interpolation entre le bleu et le blanc, avec du rouge ajouté dans une direction
// et du vert dans l'autre.
SColor yellow(f32 x, f32 y, f32)
{
    return SColor(255, 128 + (u32)(127.f * x), 128 + (u32)(127.f * y), 255);
}

// Blanc pur.
SColor white(f32, f32, f32) { return SColor(255, 255, 255, 255); }

Le type de la fonction qui génère la carte de hauteur. X et Y prennent des valeurs entre -0,5 et 0,5 et s est l'échelle de la carte de hauteur.

 
Sélectionnez
typedef f32 generate_func(s16 x, s16 y, f32 s);

// Une simple fonction intéressante. :-)
f32 eggbox(s16 x, s16 y, f32 s)
{
    const f32 r = 4.f*sqrtf((f32)(x*x + y*y))/s;
    const f32 z = expf(-r * 2) * (cosf(0.2f * x) + cosf(0.2f * y));
    return 0.25f+0.25f*z;
}

// Une fonction sinus plutôt bête. :-/
f32 moresine(s16 x, s16 y, f32 s)
{
    const f32 xx=0.3f*(f32)x/s;
    const f32 yy=12*y/s;
    const f32 z = sinf(xx*xx+yy)*sinf(xx+yy*yy);
    return 0.25f + 0.25f * z;
}

// Une fonction simple.
f32 justexp(s16 x, s16 y, f32 s)
{
    const f32 xx=6*x/s;
    const f32 yy=6*y/s;
    const f32 z = (xx*xx+yy*yy);
    return 0.3f*z*cosf(xx*yy);
}

III. Cartes de hauteurs

Une simple classe pour représenter les cartes de hauteurs. La majorité de ceci devrait être évident.

 
Sélectionnez
class HeightMap
{
private:
    const u16 Width;
    const u16 Height;
    f32 s;
    core::array<f32> data;
public:
    HeightMap(u16 _w, u16 _h) : Width(_w), Height(_h), s(0.f), data(0)
    {
        s = sqrtf((f32)(Width * Width + Height * Height));
        data.set_used(Width * Height);
    }

    // Remplit la carte de hauteur avec des valeurs générées à partir de f.
    void generate(generate_func f)
    {
        u32 i=0;
        for(u16 y = 0; y < Height; ++y)
            for(u16 x = 0; x < Width; ++x)
                set(i++, calc(f, x, y));
    }

    u16 height() const { return Height; }
    u16 width() const { return Width; }

    f32 calc(generate_func f, u16 x, u16 y) const
    {
        const f32 xx = (f32)x - Width*0.5f;
        const f32 yy = (f32)y - Height*0.5f;
        return f((u16)xx, (u16)yy, s);
    }

    // La hauteur à (x, y) est la position y * Largeur + x.

    void set(u16 x, u16 y, f32 z) { data[y * Width + x] = z; }
    void set(u32 i, f32 z) { data[i] = z; }
    f32 get(u16 x, u16 y) const { return data[y * Width + x]; }

La seule partie difficile. On considère que la normale à (x, y) est le produit vectoriel des vecteurs entre les points adjacents dans les directions horizontale et verticale.

s est un facteur de redimensionnement, qui est nécessaire si l'unité de hauteur est différente des unités utilisées pour les coordonnées. Par exemple, si votre carte a sa hauteur en mètres et ses unités de coordonnées en kilomètres.

 
Sélectionnez
    vector3df getnormal(u16 x, u16 y, f32 s) const
    {
        const f32 zc = get(x, y);
        f32 zl, zr, zu, zd;

        if (x == 0)
        {
            zr = get(x + 1, y);
            zl = zc + zc - zr;
        }
        else if (x == Width - 1)
        {
            zl = get(x - 1, y);
            zr = zc + zc - zl;
        }
        else
        {
            zr = get(x + 1, y);
            zl = get(x - 1, y);
        }

        if (y == 0)
        {
            zd = get(x, y + 1);
            zu = zc + zc - zd;
        }
        else if (y == Height - 1)
        {
            zu = get(x, y - 1);
            zd = zc + zc - zu;
        }
        else
        {
            zd = get(x, y + 1);
            zu = get(x, y - 1);
        }

        return vector3df(s * 2 * (zl - zr), 4, s * 2 * (zd - zu)).normalize();
    }
};

IV. Génération du mesh

Une classe qui génère un mesh à partir de la carte de hauteur.

 
Sélectionnez
class TMesh
{
private:
    u16 Width;
    u16 Height;
    f32 Scale;
public:
    SMesh* Mesh;

    TMesh() : Mesh(0), Width(0), Height(0), Scale(1.f)
    {
        Mesh = new SMesh();
    }

    ~TMesh()
    {
        Mesh->drop();
    }

    // A moins que la carte de hauteur soit petite, tout ne tiendra pas dans un seul SMeshBuffer.
    // Cette fonction les découpes en pièces et génère un tampon pour chacun d'entre eux.

    void init(const HeightMap &hm, f32 scale, colour_func cf, IVideoDriver *driver)
    {
        Scale = scale;

        const u32 mp = driver -> getMaximalPrimitiveCount();
        Width = hm.width();
        Height = hm.height();
        
        const u32 sw = mp / (6 * Height); // La largeur de chaque pièces.

        u32 i=0;
        for(u32 y0 = 0; y0 < Height; y0 += sw)
        {
            u16 y1 = y0 + sw;
            if (y1 >= Height)
                y1 = Height - 1; // La dernière peut être étroite.
            addstrip(hm, cf, y0, y1, i);
            ++i;
        }
        if (i<Mesh->getMeshBufferCount())
        {
            // Efface le reste.
            for (u32 j=i; j<Mesh->getMeshBufferCount(); ++j)
            {
                Mesh->getMeshBuffer(j)->drop();
            }
            Mesh->MeshBuffers.erase(i,Mesh->getMeshBufferCount()-i);
        }
        // Met le fanion sale pour s'assurer que les copies matériel de ces tampons
        // soient aussi mises à jour, regardez Imesh::setHardwareMappingHint .
        Mesh->setDirty();
        Mesh->recalculateBoundingBox();
    }

    // Génère un SmeshBuffer qui représente tous les vertex et
    // les index pour les valeurs de y entre y0 et y1 et les ajoute au mesh.

    void addstrip(const HeightMap &hm, colour_func cf, u16 y0, u16 y1, u32 bufNum)
    {
        SMeshBuffer *buf = 0;
        if (bufNum<Mesh->getMeshBufferCount())
        {
            buf = (SMeshBuffer*)Mesh->getMeshBuffer(bufNum);
        }
        else
        {
            // Crée un nouveau tampon.
            buf = new SMeshBuffer();
            Mesh->addMeshBuffer(buf);
            // Pour simplifier les choses, nous le jetons ici mais nous continuons d'utiliser buf.
            buf->drop();
        }
        buf->Vertices.set_used((1 + y1 - y0) * Width);

        u32 i=0;
        for (u16 y = y0; y <= y1; ++y)
        {
            for (u16 x = 0; x < Width; ++x)
            {
                const f32 z = hm.get(x, y);
                const f32 xx = (f32)x/(f32)Width;
                const f32 yy = (f32)y/(f32)Height;

                S3DVertex& v = buf->Vertices[i++];
                v.Pos.set(x, Scale * z, y);
                v.Normal.set(hm.getnormal(x, y, Scale));
                v.Color=cf(xx, yy, z);
                v.TCoords.set(xx, yy);
            }
        }

        buf->Indices.set_used(6 * (Width - 1) * (y1 - y0));
        i=0;
        for(u16 y = y0; y < y1; ++y)
        {
            for(u16 x = 0; x < Width - 1; ++x)
            {
                const u16 n = (y-y0) * Width + x;
                buf->Indices[i]=n;
                buf->Indices[++i]=n + Width;
                buf->Indices[++i]=n + Width + 1;
                buf->Indices[++i]=n + Width + 1;
                buf->Indices[++i]=n + 1;
                buf->Indices[++i]=n;
                ++i;
            }
        }

        buf->recalculateBoundingBox();
    }
};

V. Receveur d'événements

Notre implémentation d'un receveur d'événements, pris du quatrième tutoriel.

 
Sélectionnez
class MyEventReceiver : public IEventReceiver
{
public:
    // Ceci est la méthode que nous devons implémenter.
    virtual bool OnEvent(const SEvent& event)
    {
        // Se souvient si la touche est enfoncée ou relâchée.
        if (event.EventType == irr::EET_KEY_INPUT_EVENT)
            KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

        return false;
    }

    // Ceci est utilisé pour vérifier si une touche est enfoncée.
    virtual bool IsKeyDown(EKEY_CODE keyCode) const
    {
        return KeyIsDown[keyCode];
    }

    MyEventReceiver()
    {
        for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
            KeyIsDown[i] = false;
    }

private:
    // Nous utilisons ce tableau pour stocker l'état de chaque touche.
    bool KeyIsDown[KEY_KEY_CODES_COUNT];
};

VI. Initialisation

Une grande partie de ces codes sont tirés d'autres exemples. Nous configurons donc simplement un mesh à partir d'une carte de hauteur, l'éclairons avec une lumière mobile et autorisons à l'utilisateur de se déplacer autour.

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

    MyEventReceiver receiver;
    IrrlichtDevice* device = createDevice(driverType,
            core::dimension2du(800, 600), 32, false, false, false,
            &receiver);

    if(device == 0)
        return 1;
 
    IVideoDriver *driver = device->getVideoDriver();
    ISceneManager *smgr = device->getSceneManager();
    device->setWindowCaption(L"Irrlicht Example for SMesh usage.");

VII. Création et affichage du mesh personnalisé

Crée un mesh personnalisé et l'initialise avec une carte de hauteur.

 
Sélectionnez
    TMesh mesh;
    HeightMap hm = HeightMap(255, 255);
    hm.generate(eggbox);
    mesh.init(hm, 50.f, grey, driver);

    // Ajoute le mesh au graphe de scène.
    IMeshSceneNode* meshnode = smgr -> addMeshSceneNode(mesh.Mesh);
    meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);

    // L'éclairage est juste  pour quelques jolis effets.
    ILightSceneNode *node = smgr->addLightSceneNode(0, vector3df(0,100,0),
        SColorf(1.0f, 0.6f, 0.7f, 1.0f), 500.0f);
    if (node)
    {
        node->getLightData().Attenuation.set(0.f, 1.f/500.f, 0.f);
        ISceneNodeAnimator* anim = smgr->createFlyCircleAnimator(vector3df(0,150,0),250.0f);
        if (anim)
        {
            node->addAnimator(anim);
            anim->drop();
        }
    }

    ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
    if (camera)
    {
        camera->setPosition(vector3df(-20.f, 150.f, -20.f));
        camera->setTarget(vector3df(200.f, -80.f, 150.f));
        camera->setFarValue(20000.0f);
    }

Juste une boucle de rendu habituelle avec la gestion des événements. Le mesh personnalisé est une partie usuelle du graphe de scène qui est dessiné par drawAll().

 
Sélectionnez
    while(device->run())
    {
        if(!device->isWindowActive())
        {
            device->sleep(100);
            continue;
        }

        if(receiver.IsKeyDown(irr::KEY_KEY_W))
        {
            meshnode->setMaterialFlag(video::EMF_WIREFRAME, !meshnode->getMaterial(0).Wireframe);
        }
        else if(receiver.IsKeyDown(irr::KEY_KEY_1))
        {
            hm.generate(eggbox);
            mesh.init(hm, 50.f, grey, driver);
        }
        else if(receiver.IsKeyDown(irr::KEY_KEY_2))
        {
            hm.generate(moresine);
            mesh.init(hm, 50.f, yellow, driver);
        }
        else if(receiver.IsKeyDown(irr::KEY_KEY_3))
        {
            hm.generate(justexp);
            mesh.init(hm, 50.f, yellow, driver);
        }

        driver->beginScene(true, true, SColor(0xff000000));
        smgr->drawAll();
        driver->endScene();
    }

    device->drop();

    return 0;
}

C'est tout ! Compilez et testez le programme.

VIII. Conclusion

Vous pouvez désormais créer vos propres objets personnalisés.

Ainsi s'achève les traductions des tutoriels officiels d'Irrlicht, j'espère que ces derniers vous ont plus.

IX. Remerciements

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

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