loading a dynamic terrain map

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.
Post Reply
magicat
Posts: 19
Joined: Sun Apr 09, 2006 6:52 am

loading a dynamic terrain map

Post by magicat »

I'm trying to add 'procedural' terrain maps to my game, that are generated in game(not generated with a program and saved as a texture, then loaded by my game). I've got this nice class that generatesa height map and stores it in a class. The problem is Irrlicht seems to only load the heightmaps for Terrain nodes from images, so I've been trying to come up with a way to get the heightmap from my class to the TerrainNode. My idea was to create a custom ITerrainNode class and change the way it loaded the height map. So I looked through the source, but it seems ITerrainNode doesn't actualy take care of the loading of the image(the scene manager does?). So basicly, I'm completely lost. Can anyone point me in the right direction or give me any tips?
hybrid

Post by hybrid »

If you do want to use the usual heightmap approach you can generate your heightmap into a manually created image. Then pass this image to the terrain generator. If you can just pass a filename you can easily add a new method which bypasses the image loading.
magicat
Posts: 19
Joined: Sun Apr 09, 2006 6:52 am

Post by magicat »

Yeah, I thought about doing like you suggested and creating an image then loading it, but thats kind of like, a crappy way to do it. Would of course take more time, and its not really flexible enough. I'm considering just writing my own terraining node since the built in ones not working out too well. Would take much longer, but will be easier to add other features I have in mind later.

On another note, something I would like to implement for my terrain generation is the ability to do some complex texturing with it. For example, scan the heightmap and find places greater than a certain elevation and give them special textures, or also detect the angle of a hill and depending on the steepness, texture it differently(grass doesnt usualy grow on the side of steep hills right?). Anyways, what would be the best way to do this? I don't deny being new, so I don't really know how the texturing works for terrain maps, but it seems like to do this I would actualy have to split the terrain into layers and texture each one differently. Would this work? And would it be a viable/managable solution?
hybrid

Post by hybrid »

I did not think of a real image file, but an IImage data structure. It gives you merely a large area to write your pixel information into. Then pass the IImage to the terrain generator, instead of a filename.
For the texture generation do a search. I have recently seen such a screenshot where someone wrote a terrain tool just like that.
strale
Posts: 119
Joined: Wed Nov 23, 2005 1:58 pm
Location: Lambrugo Italy
Contact:

Post by strale »

Hi

i tried with few success this metods to load an heigh map directly from a bufffer


HeighMap = driver->createImageFromData( format ,dimension2d<s32>(Width,height),u4HeighBuffer,true );

TxtrMap = driver->createImageFromData( format ,dimension2d<s32>(Width,height),u4Buffer,true );


iTerrainMesh = smgr->addTerrainMesh("iTerrainMesh", TxtrMap,HeighMap,
core::dimension2d< f32 >(128, 128), 25000.0f, core::dimension2d< s32 >(128, 128) );
node = smgr->addOctTreeSceneNode(iTerrainMesh);


the problem is that


node = smgr->addTerrainSceneNode("../Mappe/terrain-heightmap.bmp");


seem work better and from the forum seems also to be faster
on the forum i found an example of use
node = smgr->addTerrainSceneNode( *IImage ); but dowes not compile


bye
hybrid

Post by hybrid »

Yes, you will probably have to add a new method which takes an image pointer instead of the filename. But then, both ways shoudl be the same, as the bmp loader also just creates an IImage from the file. If you want to mimic the behaviour of the original terrain node more closely copy all the important lines from it's generation in Irrlicht to you application and replace the image loading by your image generation. This should be ok for a first approach.
magicat
Posts: 19
Joined: Sun Apr 09, 2006 6:52 am

Post by magicat »

Yeah that would be my ideal approach. Copy the current one then change what I need. At the moment I'm still a bit confused as to how I can accomplish this though. I think the problem is mostly because I'm still a tad unsure of how inheritence works with c++, specificaly in Irrlicht. Should I just create a custom ISceneNode class, and transfer the methods from CTerrainNode.cpp to it(and also create a header for my custom ISceneNode and put the contents of the CTerrainNode.h in there)?
hybrid

Post by hybrid »

Ok, just checked CTerrainSceneNode, here's my advice:
Add a new method

Code: Select all

bool CTerrainSceneNode::createHeightMap( video::IImage* heightMap, video::SColor vertexColor )
by replacing the original loadHeightMap(...) and removing the first lines dealing with the image loading (except for checking if heightMap is null). Move these lines into a newly created loadHeightMap() method with the original API, doing just the following: Load the file and call your new method.
I hope you get the idea. Should be quite easy and probably useful to be included in the Irrlicht core if well done.
magicat
Posts: 19
Joined: Sun Apr 09, 2006 6:52 am

Post by magicat »

Okay, I think I've made some progress, pretty much in the way you suggested. I'm not sure what file you were saying I should load though since the terrain will be generate from scratch. But anyways. It turned out to be more complicated than I had hoped, because of the way its set up. As it is now, these are the changes I've made:

CTerrainSceneNode.h - added overloaded method for loadHeightMap()
CTerrainSceneNode.cpp - added overloaded method for loadHeightMap() (actual code part of course). Its the same code as the original loadHeightMap(), except it doesn't accept an image argument(just a bool dynamic and the other required argument that was already there for now). And also the new file includes the class used to produce the height map from scratch(and uses its namespace). The new loadHeightMap() simply creates an object of this class, calls its function to generate the heightmap, then in the place where the orignial loadHeightMap() got information from the image, it gets it from the new class instead. Simple.
include\ISceneManager.h - added another method called addDynamicTerrainSceneNode().
CSceneManager.h - added the new addDynamicTerrainSceneNode() method here too.
CSceneManager.cpp - added the new addDynamicTerrainSceneNode() method here too, with the actual code. Basicly its a copy/paste of the original addTerrainSceneNode() except it doesn't take an image for a parameter, and as such I had to change it up a bit. Basicly all it does is create a CTerrainSceneNode object, call the new loadHeightMap() method, and return the object in ITerrainSceneNode form.

It all compiles fine now, no warnings except some about converting from float to ints in the terrain generation class. Unfortunately, the Irrlicht.dll it generates doesn't work right. The program runs and no errors are generated, but nothing at all is drawn to the screen. Pure black. Its funny though because the console prints out that all meshes and the terrain map(regular one, not using dynamic map yet) were loaded successfully. But its all black. I compiled the .dll with just the CTerrainSceneNode changes, but with all the original SceneManager files to try and figure out where the problem was, and this dll worked fine. So I think my problem is somewhere with my new method. Heres what it looks like, tell me if theres anything wrong that catches your eye:

include\ISceneManager.h:

Code: Select all

        //! used for adding a dynamic terrain node!
		virtual ITerrainSceneNode* addDynamicTerrainSceneNode(
				bool dynamic,
				ISceneNode* parent=0, s32 id=-1,
			const core::vector3df& position = core::vector3df(0.0f,0.0f,0.0f),
			const core::vector3df& rotation = core::vector3df(0.0f,0.0f,0.0f),
			const core::vector3df& scale = core::vector3df(1.0f,1.0f,1.0f),
			video::SColor vertexColor = video::SColor(255,255,255,255),
			s32 maxLOD=5, E_TERRAIN_PATCH_SIZE patchSize=ETPS_17) = 0;
CSceneManager.h:

Code: Select all

        //! Adds a dynamic terrain scene node to the scene graph.
		virtual ITerrainSceneNode* addDynamicTerrainSceneNode(
			bool dynamic,
			ISceneNode* parent=0, s32 id=-1,
			const core::vector3df& position = core::vector3df(0.0f,0.0f,0.0f),
			const core::vector3df& rotation = core::vector3df(0.0f,0.0f,0.0f),
			const core::vector3df& scale = core::vector3df(1.0f,1.0f,1.0f),
			video::SColor vertexColor = video::SColor(255,255,255,255),
			s32 maxLOD=5, E_TERRAIN_PATCH_SIZE patchSize=ETPS_17);
CSceneManager.cpp:

Code: Select all

//! Adds a dynamic terrain scene node to the scene graph.
ITerrainSceneNode* CSceneManager::addDynamicTerrainSceneNode(
	bool dynamic,
	ISceneNode* parent, s32 id,
	const core::vector3df& position,
	const core::vector3df& rotation,
	const core::vector3df& scale,
	video::SColor vertexColor,
	s32 maxLOD, E_TERRAIN_PATCH_SIZE patchSize)
{
    if (!parent)
    parent = this;

	CTerrainSceneNode* node = new CTerrainSceneNode(parent, this, id,
		maxLOD, patchSize, position, rotation, scale);

	if (!node->loadHeightMap(dynamic, vertexColor))
	{
		node->remove();
		node->drop();
		return 0;
	}

	node->drop();

	ITerrainSceneNode* terrain = node;

	return terrain;
}
I'm a bit confused as to how this can be causing a problem with rendering though, since the new methods are never actualy called in my program.
Spintz
Posts: 1688
Joined: Thu Nov 04, 2004 3:25 pm

Post by Spintz »

Can we see the code where you are generating the terrain data?
Image
hybrid

Post by hybrid »

Maybe it's just using the wrong includes when compiling your app. This could lead to completely wrong SceneManager calls due to wrong method offset calculations.
magicat
Posts: 19
Joined: Sun Apr 09, 2006 6:52 am

Post by magicat »

Heres my overloaded loadHeightMap():

Code: Select all

	bool CTerrainSceneNode::loadHeightMap( bool dynamic, video::SColor vertexColor )
	{
	    HillTerrain tTerrain;
	    tTerrain.GenerateTerrain();

		u32 startTime = os::Timer::getTime();

		// Get the dimension of the heightmap data
		//TerrainData.Size = heightMap->getDimension().Width;
		TerrainData.Size = tTerrain.GetSize();

		// Make sure the maximum level of detail is compatible with the heightmap size
		if( TerrainData.Size <= 17 && TerrainData.MaxLOD > 1 )
			TerrainData.MaxLOD = 1;
		else
		if( TerrainData.Size <= 33 && TerrainData.MaxLOD > 2 )
			TerrainData.MaxLOD = 2;
		else
		if( TerrainData.Size <= 65 && TerrainData.MaxLOD > 3 )
			TerrainData.MaxLOD = 3;
		else
		if( TerrainData.Size <= 129 && TerrainData.MaxLOD > 4 )
			TerrainData.MaxLOD = 4;
		else
		if( TerrainData.Size <= 257 && TerrainData.MaxLOD > 4 )
			TerrainData.MaxLOD = 5;
		else
		if( TerrainData.Size <= 513 && TerrainData.MaxLOD > 6 )
			TerrainData.MaxLOD = 6;
		else
		if( TerrainData.Size <= 1025 && TerrainData.MaxLOD > 7 )
			TerrainData.MaxLOD = 7;

		// --- Generate vertex data from heightmap ----
		// resize the vertex array for the mesh buffer one time ( makes loading faster )
		SMeshBufferLightMap* pMeshBuffer = new SMeshBufferLightMap();
		pMeshBuffer->Vertices.reallocate( TerrainData.Size * TerrainData.Size );
		pMeshBuffer->Vertices.set_used( TerrainData.Size * TerrainData.Size );

		video::S3DVertex2TCoords vertex;
		core::vector3df normal = core::vector3df( 0.0f, 1.0f, 0.0f );
		vertex.Color = vertexColor;

		// Read the heightmap to get the vertex data
		// Apply positions changes, scaling changes
		for( s32 x = 0; x < TerrainData.Size; ++x )
		{
			for( s32 z = 0; z < TerrainData.Size; ++z )
			{
				s32 index = x * TerrainData.Size + z;

				vertex.Pos.X = (f32)x;
				vertex.Pos.Y = (f32)( tTerrain.GetCell( x,z ) ) / 3.0f;
				vertex.Pos.Z = (f32)z;

				vertex.Normal = normal;

				vertex.TCoords.X = x / (f32)TerrainData.Size;
				vertex.TCoords.Y = z / (f32)TerrainData.Size;

				vertex.TCoords2.X = x / (f32)TerrainData.Size;
				vertex.TCoords2.Y = z / (f32)TerrainData.Size;

				pMeshBuffer->Vertices[index] = vertex;
			}
		}

		// calculate smooth normals for the vertices
		calculateNormals( pMeshBuffer );

		// add the MeshBuffer to the mesh
		Mesh.addMeshBuffer( pMeshBuffer );
		s32 vertexCount = pMeshBuffer->getVertexCount();

		// We copy the data to the renderBuffer, after the normals have been calculated.
		RenderBuffer.Vertices.reallocate( vertexCount );
		RenderBuffer.Vertices.set_used( vertexCount );

		for( s32 i = 0; i < vertexCount; i++ )
		{
			RenderBuffer.Vertices[i] = pMeshBuffer->Vertices[i];
			RenderBuffer.Vertices[i].Pos *= TerrainData.Scale;
			RenderBuffer.Vertices[i].Pos += TerrainData.Position;
		}

		// We no longer need the pMeshBuffer
		pMeshBuffer->drop();

		// calculate all the necessary data for the patches and the terrain
		calculateDistanceThresholds();
		createPatches();
		calculatePatchData();

		// set the default rotation pivot point to the terrain nodes center
		TerrainData.RotationPivot = TerrainData.Center;

		// Rotate the vertices of the terrain by the rotation specified.  Must be done
		// after calculating the terrain data, so we know what the current center of the
		// terrain is.
		setRotation( TerrainData.Rotation );

		// Pre-allocate memory for indices
		RenderBuffer.Indices.reallocate( TerrainData.PatchCount * TerrainData.PatchCount *
			TerrainData.CalcPatchSize * TerrainData.CalcPatchSize * 6 );
		RenderBuffer.Indices.set_used( TerrainData.PatchCount * TerrainData.PatchCount *
			TerrainData.CalcPatchSize * TerrainData.CalcPatchSize * 6 );

		u32 endTime = os::Timer::getTime();

		c8 tmp[255];
		sprintf(tmp, "Generated terrain data (%dx%d) in %.4f seconds",
			TerrainData.Size, TerrainData.Size, ( endTime - startTime ) / 1000.0f );
		os::Printer::print( tmp );

		return true;
	}
I didn't have to change much to allow it to get the vertices from my dynamic height map as you can see, just a few lines, and then I cut out the old image stuff that was no longer needed. Right now, the overloaded methods not taking any useful parameters, so the HillTerrain(dynamic terrain object) is created with all default values. Once I get it to work at all, I'll add the values that can be passed to it. The RobotFrog HillTerrain classes can be found here: http://www.robot-frog.com/3d/hills/code.html. Oh and yes, I changed the Generate() function to GenerateTerrain() in my class, thats why its different in my code above. :).
Post Reply