OK then; here's the final working version. There isn't much complicated about it, although you may want to change some of the comments. Don't hesitate to ask if something doesn't make sense. Once again, many thanks for your help with this; what irrlicht does with it is very impressive.
Code: Select all
#include <irrlicht.h>
#include <iostream>
#include <math.h>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
using namespace std;
//////////////////////////////////////////////////////////////////////////////
// The type of the functions which work out the colour. None of these
// have any effect with the OPENGL driver.
typedef SColor colour_func(f32 x, f32 y, f32 z);
// Greyscale, based on the height.
SColor grey(f32, f32, f32 z)
{
u8 n = 255 * z;
return SColor(255, n, n, n);
}
// Interpolation between blue and white, with red added in one
// direction and green in the other.
SColor yellow(f32 x, f32 y, f32)
{
return SColor(255, 128 + 127 * x, 128 + 127 * y, 255);
}
// Pure white.
SColor white(f32, f32, f32) { return SColor(255, 255, 255, 255); }
// The type of the functions which generate the heightmap. x and y
// range between -0.5 and 0.5, and r = sqrt(x * x + y * y), i.e. the
// distance from the centre of the heightmap.
typedef f32 generate_func(s16 x, s16 y, f32 r);
// An interesting sample function :-)
f32 eggbox(s16 x, s16 y, f32 r)
{
f32 z = exp(-r * 2) * (cos(0.2 * x) + cos(0.2 * y));
return 0.25 + 0.25 * z;
}
//////////////////////////////////////////////////////////////////////////////
// A simple class for representing heightmaps. Most of this should be
// obvious.
class HeightMap
{
private:
u16 w, h;
f32 s, *data;
public:
HeightMap(u16 w, u16 h)
{
this -> w = w; this -> h = h;
s = sqrt(w * w + h * h) / 4.0;
data = new f32[w * h];
}
~HeightMap() { delete [] data; }
// Fill the hieghtmap with values generated from f.
void generate(generate_func f)
{
for(u16 y = 0; y < h; y ++)
for(u16 x = 0; x < w; x ++)
set(x, y, calc(f, x, y));
}
u16 height() { return h; }
u16 width() { return w; }
f32 calc(generate_func f, u16 x, u16 y)
{
f32 xx = x * 1.0 - w / 2, yy = y * 1.0 - h / 2;
f32 r = sqrt(xx * xx + yy * yy);
return f(xx, yy, r / s);
}
// The height at (x, y) is at position y * w + x.
void set(u16 x, u16 y, f32 z) { data[y * w + x] = z; }
f32 get(u16 x, u16 y) { return data[y * w + x]; }
// 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. Please don't tell me
// irrlicht already does this somewhere.
//
// 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
// kilometre.
vector3df getnormal(u16 x, u16 y, f32 s)
{
f32 zc = get(x, y), zl, zr, zu, zd;
if( x == 0) { zr = get(x + 1, y); zl = zc + zc - zr; }
else if(x == w - 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 == h - 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();
}
};
//////////////////////////////////////////////////////////////////////////////
// A class which generates a mesh from a heightmap.
class TMesh: public SMesh
{
private:
u16 w, h;
f32 s;
public:
// This constructor takes an actual heightmap as its first parameter...
TMesh(HeightMap &hm, f32 scale, colour_func cf, IVideoDriver *driver)
: SMesh()
{
init(hm, scale, cf, driver);
}
// ... whereas this one generates it for you.
TMesh(u16 w, u16 h, generate_func f, f32 scale,
colour_func cf, IVideoDriver *driver)
{
HeightMap hm = HeightMap(w, h);
hm.generate(f);
init(hm, scale, cf, driver);
}
// Unless the heightmap is small, it won't all fit into a single
// SMeshBuffer. This function chops it into pieces and generates a
// buffer from each one.
void init(HeightMap &hm, f32 scale, colour_func cf, IVideoDriver *driver)
{
u32 mp = driver -> getMaximalPrimitiveCount();
w = hm.width(); h = hm.height(); s = scale;
u16 sw = mp / (6 * h); // the width of each piece
for(u16 y0 = 0; y0 < h; y0 += sw)
{
u16 y1 = y0 + sw;
if(y1 >= h) y1 = h - 1; // the last one might be narrower
addstrip(hm, cf, y0, y1);
}
recalculateBoundingBox();
}
// Add a vertex representing the height at (x, y) to the buffer, and
// colour it according to cf (the OPENGL driver ignores this,
// unfortunately).
void addpoint(SMeshBuffer *buf, HeightMap &hm, colour_func cf, u16 x, u16 y)
{
f32 z = hm.get(x, y);
f32 xx = (1.0 * x) / w, yy = (1.0 * y) / h;
vector3df pos = vector3df(x, s * z, y);
vector3df norm = hm.getnormal(x, y, s);
SColor c = cf(xx, yy, z);
vector2d<f32> t = vector2d<f32>(0, 0);
buf -> Vertices.push_back(S3DVertex(pos, norm, c, t));
}
// Add the six indices which make up the two triangles at (x, y) to
// the buffer.
void addindices(SMeshBuffer *buf, u16 x, u16 y)
{
u16 n = y * w + x;
buf -> Indices.push_back(n);
buf -> Indices.push_back(n + h);
buf -> Indices.push_back(n + h + 1);
buf -> Indices.push_back(n + h + 1);
buf -> Indices.push_back(n + 1);
buf -> Indices.push_back(n);
}
// Generate a SMeshBuffer which represents all the vertices and
// indices for values of y between y0 and y1, and add it to the
// mesh.
void addstrip(HeightMap &hm, colour_func cf, u16 y0, u16 y1)
{
SMeshBuffer *buf = new SMeshBuffer();
addMeshBuffer(buf);
buf -> Vertices.reallocate((1 + y1 - y0) * w);
for(u16 y = y0; y <= y1; y ++)
for(u16 x = 0; x < w; x ++)
addpoint(buf, hm, cf, x, y);
buf -> Indices.reallocate(6 * (w - 1) * (y1 - y0));
for(u16 y = y0; y < y1; y ++)
for(u16 x = 0; x < w - 1; x ++)
addindices(buf, x, y - y0);
buf -> recalculateBoundingBox();
}
};
//////////////////////////////////////////////////////////////////////////////
// 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()
{
// The software driver simply isn't up to this, although it does
// show the colours.
video::E_DRIVER_TYPE driverType = video::EDT_OPENGL;
IrrlichtDevice* device = createDevice(driverType,
core::dimension2d<s32>(640, 480));
if(device == 0) return 1;
IVideoDriver *driver = device -> getVideoDriver();
ISceneManager *smgr = device -> getSceneManager();
IGUIEnvironment *env = device -> getGUIEnvironment();
TMesh mesh = TMesh(255, 255, eggbox, 50.0, yellow, driver);
smgr -> addMeshSceneNode(&mesh);
ISceneNode *node = smgr->addLightSceneNode(0, vector3df(0,0,0),
SColorf(1.0f, 0.6f, 0.7f, 1.0f), 1200.0f);
ISceneNodeAnimator* anim = 0;
anim = smgr->createFlyCircleAnimator (vector3df(0,150,0),250.0f);
node->addAnimator(anim);
anim->drop();
ICameraSceneNode* camera =
smgr->addCameraSceneNodeFPS(0,100.0f,1200.f);
camera->setPosition(vector3df(400.f, 400.f, 0.f));
camera->setTarget(vector3df(127.f, 0.f, 127.f));
camera->setFarValue(12000.0f);
while(device->run())
{
if(!device->isWindowActive()) continue;
driver->beginScene(true, true, SColor(0, 0, 0, 0));
smgr->drawAll();
env->drawAll();
driver->endScene();
}
device->drop();
return 0;
}
//////////////////////////////////////////////////////////////////////////////