Generating terrain from a plain c++ array

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Generating terrain from a plain c++ array

Post by Alastriona »

Hello,

I've been messing around with procedural terrain generation (using fractals) the last few weeks. My algortihm basicly creates a heightmap in a plain (2D) array of chars.

The array looks like this:

Code: Select all

 char[height*width] terrain = new char[height*width];
Now I have code that will save this to an image. Which will allow me to load it as a terrain scene node into irrlicht.

The problem I'm having with this is that its kind of overkill to save to an image and then load it into Irrlicht. What I would like is to use my 2D array directly to provide height values for Irrlicht to use. Since my array is basicly representing a 8-bit grayscale image I'm pretty sure this should be possible.

My questions are:

- Has anyone ever done something similar, and is this code freely available?
- If not, do you guys think it would be hard to implement into Irrlicht?

P.S. I hope the description of my situation is sufficient. I didn't wanna end up with a huge wall of text. Feel free to ask for clarification if needed.
bonsalty
Posts: 120
Joined: Thu Dec 10, 2009 1:30 pm
Location: Budapest,Hungary

Post by bonsalty »

You can get the mesh from ITerrainSceneNode and modify it. To bad there is no initialiser for the size. To bad: if a texture is used for heightmap the mesh might have a different size :twisted: . For instance if your patchsize is 65, your terrain will have the the size of n*64. This is a headache, cos you have to interpolate one point of your grid to more then one points.

What I do is generating a grayscale image. I found, that irrlicht handles r,g,b channels with different weights. Actually you can have ~2500 levels instead of the grayscale version, which supports 255. So I simple interpolate heights to colors. Then I resize the image to n*patchsize. Then I make an Iimage of it in memory and load it.

Huh, a long story indeed. It was often repeated to me: "You can simple make your own terrain of triangles in a custom node. " But whats the point of that? If you have grids larger then 500*500 the whole project will slow down. Thats why IterrainSceneNode is great. Another prise Im dealing with is how to remove the node properly.
Tomi
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

Thanks for the response bonsalty. I think I get most of what you mean. I'm guessing this means that I will have to overload the loadHeightMap() function to take an Iimage and then construct an IterrainSceneNode manually and call my overloaded loadheightmap function?

Browsing through documentation and source I don't see how I could generate a Heightmap from an Iimage without modifying the source. Is this correct?
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

Alastriona wrote:Thanks for the response bonsalty. I think I get most of what you mean. I'm guessing this means that I will have to overload the loadHeightMap() function to take an Iimage and then construct an IterrainSceneNode manually and call my overloaded loadheightmap function?

Browsing through documentation and source I don't see how I could generate a Heightmap from an Iimage without modifying the source. Is this correct?
Never mind all that.Not really familliar with the whole API yet. I just found ISceneManager::addTerrainMesh().
If I am correct this should do what I want if I provide it with an IImage.

Is there any way to create an empty CImage instance that I can them fill myself with my data?

EDIT: Found it: IVideoDriver::createImage()
bonsalty
Posts: 120
Joined: Thu Dec 10, 2009 1:30 pm
Location: Budapest,Hungary

Post by bonsalty »

Code: Select all

terrain = smgr->addTerrainSceneNode(device->getFileSystem()->createMemoryReadFile ((void*)lpBits, size, "heightmap",true),
		NULL,					// parent node
		-1,					// node id
		core::vector3df(0, 0, 0),		// position
		core::vector3df(0, rotZ, 0),		// rotation
		core::vector3df(rescaleX,rescaleZ,rescaleY),	// scale
		video::SColor ( 255, 255, 255, 255 ),	// vertexColor
		3,					// maxLOD
		scene::ETPS_65,				// patchSize
		0				// smoothFactor
		);
	    
This works for memory bitmaps.
You have to pass the lpbits and size of the jpg or png or bmp file.

Actually Im using the freeimage api. Freeimage can load almost every graphic file. It also has a feature of getting the bits.
Other option: you can also make a hbitmap or cbitmap and get the byte another suck.
Third option: You make an FTP and I try to send you my lib. To bad it works only with mfc.
Tomi
bonsalty
Posts: 120
Joined: Thu Dec 10, 2009 1:30 pm
Location: Budapest,Hungary

Post by bonsalty »

And heightmap to image:

Code: Select all


void ConvertToColor(double z,double skala,int &r,int &g,int &b){
// 2500 levels possible!

const double color_skala=250.0;

double rgb=(z/skala)*color_skala;
int floor_rgb=floor(rgb);
double rest=rgb -floor_rgb;

r=floor_rgb;
g=floor_rgb;
b=floor_rgb;

if (rest>=0.95){r++;g++;b++;}
else if (rest>=0.86){r=r+3;}//0.9
else if (rest>=0.76){g++; b=b+2;}//0.81
else if (rest>=0.66){g++; b++;}//0.7
else if (rest>=0.57){r=r+2;}//0.6
else if (rest>=0.47){r++; b=b+2;}//0.52
else if (rest>=0.38){r++; b++;}//0.41
else if (rest>=0.27){r++;}//0.3
else if (rest>=0.17){b=b+2;}//0.22
else if (rest>=0.06){b++;}//0.11


}


int CreateMap(CMyImage & myImage ,HeightMap *myArray, double min ,double max){


	int size=max(myArray->Width(),myArray->Height());
	
	myImage.New(size,size); 

	int R,G,B;

	for (int x=0;x< size; x++)for (int y=0;y< size; y++){
		
		
		ConvertToColor(myArray->value(x,y)-min,max-min,R,G,B);
		myImage.Canvas->SetPixel(x,y,RGB(R,G,B));
		
	}

return 0;	
	
}


This makes a bitmap from a heightmap, simple...

int CreateMap(CMyImage & myImage ,HeightMap *myArray, double min ,double max)
Replace HeightMap with a 2D vector or class, whatever. CMyImage is my custom image loader , you can replace it with the CDC-device context.
Min is the lowest point of the terrain, max is the highest.
Tomi
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Better create a memory read file from your array and use the RAW loader of terrain scene node. You can directly load any number array with ints or floats.
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

bonsalty wrote:

Code: Select all

terrain = smgr->addTerrainSceneNode(device->getFileSystem()->createMemoryReadFile ((void*)lpBits, size, "heightmap",true),
		NULL,					// parent node
		-1,					// node id
		core::vector3df(0, 0, 0),		// position
		core::vector3df(0, rotZ, 0),		// rotation
		core::vector3df(rescaleX,rescaleZ,rescaleY),	// scale
		video::SColor ( 255, 255, 255, 255 ),	// vertexColor
		3,					// maxLOD
		scene::ETPS_65,				// patchSize
		0				// smoothFactor
		);
	    
This works for memory bitmaps.
This code will result in a memory leak. You need to remember to drop() the pointer to the IReadFile returned by createMemoryReadFile().

Travis
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

hybrid wrote:Better create a memory read file from your array and use the RAW loader of terrain scene node. You can directly load any number array with ints or floats.
Alright ill try this.
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

How do I use the RAW loader? I can't seem to create an empty instance of ITerrainSceneNode*? I need an instance of the ITerrainSceneNode to call the raw loader right?

P.S. I'm really tired, maybe I'm missing something.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Just create the terrain scene node with an empty heightmap. Then call the RAW load method on it.
bonsalty
Posts: 120
Joined: Thu Dec 10, 2009 1:30 pm
Location: Budapest,Hungary

Post by bonsalty »

Can you load vectors containing floats with the raw loader?
For instance:

Code: Select all


vector<double > lpBits;

		for (int x=0;x<map->Width();x++)for (int     
                  y=0;y<map->Height();y++){
		  lpBits.push_back(map->value(x,y));
		}

size=lpBits.size();
		
		
io::IReadFile * file=device->getFileSystem()->createMemoryReadFile ((void *) &lpBits[0], size, "heightmap",true);

	terrain = params->smgr->addTerrainSceneNode(NULL);
	
	terrain->loadHeightMapRAW(file);
wont work. Acces violation, Im not familier with casting :?
Tomi
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

I'm totaly stuck on this. What I currently am trying (assume terrainArray is initialized):

Code: Select all

int terrainArray[257*257];	
io::IReadFile* file = device->getFileSystem()->createMemoryReadFile(&terrainArray, 257*257*sizeof(int), "heightmap");

scene::ITerrainSceneNode* terrain = smgr->addTerrainSceneNode(	
		NULL,	
		0,					
		-1,					
		core::vector3df(0.f, 0.f, 0.f),		
		core::vector3df(0.f, 0.f, 0.f),		
		core::vector3df(40.f, 4.4f, 40.f),	
		video::SColor ( 0, 0, 0, 255 ),	
		5,					
		scene::ETPS_17,				
		4,					
		true
		); 		
terrain->loadHeightMapRAW(file,32,true);
Perhaps someone could give me a code sample of how this should be done?
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Both code frags should basically work. Use floats instead of doubles in the first code. And don't forget to fill the array of ints in the second version.
Alastriona
Posts: 23
Joined: Sat Apr 17, 2010 3:36 pm

Post by Alastriona »

hybrid wrote:Both code frags should basically work. Use floats instead of doubles in the first code. And don't forget to fill the array of ints in the second version.
I am still getting access violations, but that must be somewhere else in my code then. Strangely when I remove the rawloader line it works fine. I can't reproduce it in debug mode either.
Post Reply