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.2
f *
x) +
cosf(0.2
f *
y));
return
0.25
f+
0.25
f*
z;
}
// Une fonction sinus plutôt bête. :-/
f32 moresine(s16 x, s16 y, f32 s)
{
const
f32 xx=
0.3
f*
(f32)x/
s;
const
f32 yy=
12
*
y/
s;
const
f32 z =
sinf(xx*
xx+
yy)*
sinf(xx+
yy*
yy);
return
0.25
f +
0.25
f *
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.3
f*
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.5
f;
const
f32 yy =
(f32)y -
Height*
0.5
f;
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.0
f, 0.6
f, 0.7
f, 1.0
f), 500.0
f);
if
(node)
{
node->
getLightData().Attenuation.set(0.
f, 1.
f/
500.
f, 0.
f);
ISceneNodeAnimator*
anim =
smgr->
createFlyCircleAnimator(vector3df(0
,150
,0
),250.0
f);
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.0
f);
}
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.