A tutorial by geoff.
In this tutorial we'll learn how to create custom meshes and deal with them with Irrlicht. We'll create an interesting heightmap with some lighting effects. With keys 1,2,3 you can choose a different mesh layout, which is put into the mesh buffers as desired. All positions, normals, etc. are updated accordingly.
Ok, let's start with the headers (I think there's nothing to say about it)
#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
using namespace video;
using namespace core;
using namespace scene;
using namespace io;
using namespace gui;
Main header file of the irrlicht, the only file needed to include.
Everything in the Irrlicht Engine can be found in this namespace.
This is the type of the functions which work out the colour.
typedef SColor colour_func(
f32 x,
f32 y,
f32 z);
float f32
32 bit floating point variable.
Here comes a set of functions which can be used for coloring the nodes while creating the mesh.
{
return SColor(255, n, n, n);
}
{
return SColor(255, 128 + (
u32)(127.f * x), 128 + (
u32)(127.f * y), 255);
}
SColor white(
f32,
f32,
f32) {
return SColor(255, 255, 255, 255); }
unsigned int u32
32 bit unsigned variable.
The type of the functions which generate the heightmap. x and y range between -0.5 and 0.5, and s is the scale of the heightmap.
{
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;
}
{
const f32 z = sinf(xx*xx+yy)*sinf(xx+yy*yy);
return 0.25f + 0.25f * z;
}
{
const f32 z = (xx*xx+yy*yy);
return 0.3f*z*cosf(xx*yy);
}
signed short s16
16 bit signed variable.
A simple class for representing heightmaps. Most of this should be obvious.
class HeightMap
{
private:
public:
HeightMap(
u16 _w,
u16 _h) : Width(_w), Height(_h), s(0.f), data(0)
{
s = sqrtf((
f32)(Width * Width + Height * Height));
}
void generate(generate_func f)
{
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);
}
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]; }
Axis aligned bounding box in 3d dimensional space.
void set_used(u32 usedNow)
Sets the size of the array and allocates new elements if necessary.
unsigned short u16
16 bit unsigned variable.
The only difficult part. This considers the normal at (x, y) to be the cross product of the vectors between the adjacent points in the horizontal and vertical directions.
s is a scaling factor, which is necessary if the height units are different from the coordinate units; for example, if your map has heights in metres and the coordinates are in units of a kilometer.
vector3df getnormal(
u16 x,
u16 y,
f32 s)
const
{
const f32 zc = get(x, y);
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);
}
}
};
vector3d< T > & normalize()
Normalizes the vector.
vector3d< f32 > vector3df
Typedef for a f32 3d vector.
A class which generates a mesh from a heightmap.
class TMesh
{
private:
public:
SMesh* Mesh;
TMesh() : Mesh(0), Width(0), Height(0), Scale(1.f)
{
Mesh = new SMesh();
}
~TMesh()
{
Mesh->drop();
}
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);
for(
u32 y0 = 0; y0 < Height; y0 += sw)
{
if (y1 >= Height)
y1 = Height - 1;
addstrip(hm, cf, y0, y1, i);
++i;
}
if (i<Mesh->getMeshBufferCount())
{
for (
u32 j=i; j<Mesh->getMeshBufferCount(); ++j)
{
Mesh->getMeshBuffer(j)->drop();
}
Mesh->MeshBuffers.erase(i,Mesh->getMeshBufferCount()-i);
}
Mesh->setDirty();
Mesh->recalculateBoundingBox();
}
void addstrip(
const HeightMap &hm, colour_func cf,
u16 y0,
u16 y1,
u32 bufNum)
{
if (bufNum<Mesh->getMeshBufferCount())
{
}
else
{
Mesh->addMeshBuffer(buf);
buf->drop();
}
buf->Vertices.set_used((1 + y1 - y0) * Width);
for (
u16 y = y0; y <= y1; ++y)
{
for (
u16 x = 0; x < Width; ++x)
{
const f32 z = hm.get(x, y);
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();
}
};
CMeshBuffer< video::S3DVertex > SMeshBuffer
Standard meshbuffer.
Our event receiver implementation, taken from tutorial 4.
{
public:
virtual bool OnEvent(
const SEvent& event)
{
KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
return false;
}
virtual bool IsKeyDown(
EKEY_CODE keyCode)
const
{
return KeyIsDown[keyCode];
}
MyEventReceiver()
{
KeyIsDown[i] = false;
}
private:
};
Interface of an object which can receive events.
@ EET_KEY_INPUT_EVENT
A key input event.
SEvents hold information about an event. See irr::IEventReceiver for details on event handling.
Much of this is code taken from some of the examples. We merely set up a mesh from a heightmap, light it with a moving light, and allow the user to navigate around it.
int main(int argc, char* argv[])
{
if (driverType==video::EDT_COUNT)
return 1;
MyEventReceiver receiver;
&receiver);
if(device == 0)
return 1;
The Irrlicht device. You can create it with createDevice() or createDeviceEx().
virtual void setWindowCaption(const wchar_t *text)=0
Sets the caption of the window.
virtual scene::ISceneManager * getSceneManager()=0
Provides access to the scene manager.
virtual video::IVideoDriver * getVideoDriver()=0
Provides access to the video driver for drawing 3d and 2d geometry.
E_DRIVER_TYPE
An enum for all types of drivers the Irrlicht Engine supports.
Create the custom mesh and initialize with a heightmap
TMesh mesh;
HeightMap hm = HeightMap(255, 255);
hm.generate(eggbox);
mesh.init(hm, 50.f, grey, driver);
IMeshSceneNode* meshnode = smgr -> addMeshSceneNode(mesh.Mesh);
meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);
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);
}
Just a usual render loop with event handling. The custom mesh is a usual part of the scene graph which gets rendered by drawAll.
{
{
continue;
}
{
meshnode->setMaterialFlag(video::EMF_WIREFRAME, !meshnode->getMaterial(0).Wireframe);
}
{
hm.generate(eggbox);
mesh.init(hm, 50.f, grey, driver);
}
{
hm.generate(moresine);
mesh.init(hm, 50.f, yellow, driver);
}
{
hm.generate(justexp);
mesh.init(hm, 50.f, yellow, driver);
}
driver->beginScene(true, true, SColor(0xff000000));
smgr->drawAll();
driver->endScene();
}
return 0;
}
bool drop() const
Drops the object. Decrements the reference counter by one.
virtual bool run()=0
Runs the device.
virtual void sleep(u32 timeMs, bool pauseTimer=false)=0
Pause execution and let other processes to run for a specified amount of time.
virtual bool isWindowActive() const =0
Returns if the window is active.
That's it! Just compile and play around with the program.