I. Prérequis▲

Ok, commençons avec les en-têtes (je pense qu'il n'y a rien à en dire)..
#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.
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.
// 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.
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.
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.
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.
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.
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.
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.
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 là 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().
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.






