Terrain renderer for Panda3D, release 2

I thought I should release this code, even though it’s still a little warty, because I don’t have time to work on it too much so it might be a while before I get it really
cleaned up, and because I need to release more often to try to
encourage participation.

Download the code here: http://homepages.inf.ed.ac.uk/s0094060/terrain-renderer.tar.gz

The code is a terrain renderer (not generator) [edit: to clarify the code takes a 2D heightfield image as input and as output
provides efficient, renderable 3D terrain.
] that implements
optimised, patch-based LOD terrain with detail texture
(though you can choose when you run it to turn LOD on/off,
because the LOD feature still needs work). As it is the terrain
is not bad looking and runs pretty fast.

Take a look at the code and please do add to it and post or send me your additions, changes, comments, advice etc.
It’s released under the LGPL.

The main issues with the code as it is, that people could help to fix, are:

  1. Currently TerrainNodePath is a custom class that has a NodePath containing the terrain as a member.
    This probably needs to be refactored a bit, I think it would make more sense to have a class TerrainNode that derives from (probably) PandaNode,
    and then let the user create one of these nodes and attach it to a NodePath themselves.

  2. If you say ‘yes’ to both mesh LOD and texture LOD you will notice a horrible change in the colour of the terrain
    at the point in the distance where the detail texture stops.
    This is because the detail texture is not blended properly
    into the terrain and it changes the colour of the terrain, instead of just applying detail to it.

To see the terrain without this ugly bug say ‘no’ to texture LOD.

Need to figure out what the correct blend mode is. This is probably the most important fix that could be made, as
displaying the detail texture across all of the terrain (i.e. texture LOD off) seems to be the biggest performance hit.

  1. If you say ‘yes’ to mesh LOD you will notice some cracks in the terrain. These cracks appear where a higher resolution patch
    joins to a lower resolution patch, and cannot be avoided using the one patch of terrain - one LODNode approach.

To see the terrain without this bug say ‘no’ to mesh LOD.

It looks like Panda3D’s LOD functionality is too simple and generic, and that some more specialised terrain lodding algorithm
is going to need to be implemented for the terrain renderer.

This is a significant task, but it should not be too difficult,
and a potential approach is described well in this article:
http://www.gamasutra.com/features/20000228/ulrich_04.htm
(registration required, but free).

  1. I haven’t gotten round to programming collision detection for the terrain yet, but it would be good to provide an example of how collisions
    with the terrain could be done. A good example would be to prevent
    the camera from moving through the terrain and have it slide along the terrain instead.

Sean Hammond,

I have downloaded and run the demo. It is truly excellent work.

I hope Thatcher Ulrich’s Chunked LOD logic can help.

AIM

Ulrich’s chunked LOD thing looks interesting, thanks for the link. If I do ever get round to improving this terrain renderer,
I’ll definitely take a look at that first.

I’m not currently working with Panda3D anymore, I’ve gone back to writing my research proposal and programming much more abstract
systems with simple 2D graphics, however there is some chance I will come back to the terrain renderer one day,
since I started the work I feel I may as well finish it if I get the chance, and I would like to make a meaningful contribution
to Panda3D since it’s (almost) free software.

In the meantime, if anyone else wants to write a LOD algorithm or otherwise improve it,
they are more than welcome, and please post the results here!

nice there overall…fps seems ok but at 75 fps what is going to happen when I start adding actors and objects ??

anyway, im getting a nasty shimering of the texture as I move around, anyone else ??

cheers
nl

This is really good stuff! Simple and seems to work well!

I have a question: Is there any tools available for creating your own terrain? Seems you need a tool that will create height-map and texture layover at the same time. In order for the terrain to display really well some high-ligthing seems necessary. If I for example just create a plain green texture with a few hills the hills will be hidden. They need highlighting/ different coloring otherwise it is difficult to spot them.

It’d be possible to create procedural textures for the procedural terrain, in which you’d colour each pixel according to its height and angle and that sort of thing. I didn’t do that though, I just used texture and heightmap pairs that I downloaded from the Internet or created using a free software tool called terraform:

terraform.sourceforge.net/

I had a couple of good links to pre-created height maps and textures on the web but I’m afraid I’ve lost them.

Anyway, glad you like the code. It’s not finished. Maybe you can do something interesting with it?

Actually shouldnt it be possible to use ambient light to cast the appropriate shadows that will color the terrain in a way that the elevation levels can be distinguished…? I will try and experiment with that.

Had another question: It appears that the model that gets created from the elevation maps is a NodePath whereas models loaded from .egg files using loader.loadModel is something more; it has a bunch of methods that the NodePath doesnt - for example “reparentTo()” and (more interestingly) collision detection functions. Would you know how to convert the NodePath from the terrain renderer example into this extended object so that it can be used like a model loaded from and .egg file?

Another way to go about this whole thing is using a tool that generates models from heightmaps and then generate .egg files in this way. Any input on this approach?

Hi Martin,

Yes, I think an ambient lighting algorithm might work.

I’m afraid my knowledge of Panda3D classes is fading. You’d have to find out what class these objects created from .egg files are, then find out what you need to do to instantiate an instance of that class - i.e. look at the class constructors. It may be that my NodePath gives you everything you need to call one of those constructors, and then you could get the type of object you want.

This section from my release notes might be relevant, I’m not sure:

I’m not sure what you mean with your last comment about generating models from heightmaps. My code is generating a 3D model from a heightmap. It doesn’t save it to an .egg file, but that’s just because I haven’t written that part yet, it should be do-able.

Hello !
(sorry for my english im french)
I use you code for heightmap, but i need to get the height of a specificate point of the heightmap.
Can somebody tell me how get the height of a point(x,y) ?
thank you

hi there
I’d also be very intrested in further development of the terrain renderer. i may not be able to help you out with code but i know quite a bit about theory behind terrain optimisatoin and can provide you with usefull stuff (mathematical processes for optimisation, sample-codes, documents about pro and cons of the single technics, vertexshader examples for geomorphing,how to get rid of the gaps between the lod-pieces(and lod-popping if not yet removed),even a ready algorithm to display a 160km square terrain at 2m ground resolution (no joke,optimized but looks awsome.SOAR-like.runs at 70fps on medium machines) and so on).
and if you’r looking for collision detection… then you might be intrested in my own little theorie(should be quite fast and terrain-size independend)^^ … my only problem is that i cant code :smiley:

since i’m thinking about having a really large, splitted and streamed terrain in a 3d apps i’d really love to see this progressing.putting up a terrain editor with texture-opacy-map generation would be another nice thing(almost as good as a scene editor but by far not as hard to code).

let me know if you need something=)
greetings
thomas

Hi guys,

I’m really pleased that people are taking an interest in this little bit of code, and really sad that I can’t be more helpful in responding.

Right now, I have to submit a finished version of my Phd proposal in 1 week, so I can’t really take any time out to refresh my memory about Panda3D or my own terrain renderer code.

If I have the time at some point, I would like to look at the code again and try to answer your questions properly, but I can’t promise anything.

Additionally, my interest in using Panda3D for my studies is diminishing somewhat, because it is still not licensed as Free Software and it’s license is not compatible with the GNU GPL. There are some very interesting Free Software game engines out there, such as sauerbraten. That said, Panda3D’s excellent Python scripting appears to me to be unmatched, so I may return to it.

I’ll try now to respond to questions from memory, though be warned I have not had time to look back at my code or notes.

Slade992 and ThomasEgi have asked about attaining the heights for specific points on the terrain, and about doing collision detection. I had wanted to release a demo of the terrain renderer with a controllable avatar walking around on the terrain, collission-detecting and with some basic gravity and friction. My intention was to then use that as a base for developing steering behaviour and pathfinding demos (steering and pathfinding is where my previous game programming experience lies).

But as I remember it, I had not gotten as far as figuring out how to use the Panda3D collision detection framework with my terrain, and all I had was some half-working code for it. The difficulty in figuring out the height of a given point on the terrain based on the heightmap, is that the heightmap only provides heights for a grid of specific points dispersed equally across the terrain. So you would need code to interpolate between those points to find the height of a point in-between. Instead of trying to write that, I suggest you look at Panda3D’s collision detection stuff and try to integrate it with my terrain.

I don’t know whether I’ve coded the terrain in such a way as to be incompatible with Panda3D’s collision detection. But if I have, it obviously could be modified to make it compatible. That would be the way to go.

It certainly should be possible to do collision detection with the terrain, but the code needs to be written.

ThomasEgi also mentioned getting rid of the gaps between lod-pieces and optimising the terrain. I had begun work on a new version of the terrain renderer (not polished enough to release) which implemented this terrain-lodding algorithm:

gamasutra.com/features/20000 … ich_01.htm

(you have to register with gamasutra to read that, but it’s worth it)

instead of using Panda3D’s generic LOD support (which is not really suitable for terrain rednering). I’d suggest that if you want to get rid of the gaps between terrain, optimise the terrain rendering, and get proper LOD support (which should make it much faster) then try implementing that.

Also I recieved this code (which I can’t attest for, haven’t tried it) from user kelly in a private message, and I’m going to be bold enough to assume he or she won’t mind me posting it here:

Lastly, if anyone is working on modifying or adding to the code and has specific questions about the code, then if you can post code snippets here and ask specific questions I may be able to help you. Seeing the snippets is likely to refresh my memory about what I was doing with the code. And please post any modifications here! :slight_smile:

I tryed to modify your code for doing an editor. It mean this :


Placing 3d models with the mouse , for now its just with the camera :confused:
I need to know how doing with the mouseX and mouseY get the Z of the 3d model i want to place on the scene.
I dont know a lot of 3d programming :confused: but if you can write me a little bit of code it will be really helpfull.

Slade,

that looks really cool.

One thing I had thought about doing was placing trees and bushes etc. of different sizes around the terrain, to make it look more realistic. But I was thinking of doing it with an algorithm.

A couple of things you might want to look into are the scene editor that already exists for Panda3D, and the really awesome approach to world building in the sauerbraten game engine, check out this tutorial.

Nice work, keep it up.

It will be implemented to scale the different item placed on the scene.
You can click on the objects placed on the map, i just have to make an entry with the scale you can do each model.
All is save on a dictionnary and will be exported to a .txt that will be read by the game , the one who would place each model at the right place.
I dont really understand all of you said :confused:
But i want to maid my own editor, but ill take a look at what your talking about.
Can i look to the code of the panda 3d editor ?
It is using heightmap ?
My only problème is this problème of getting the height. Placing object with the camera sucks a lot, but i need this for placing with the mouse :confused:
Thank you for your interest
(sorry for my english again)

Hi !
I got some problem with terrainnodepath.py
sometimes (really often) when i tried to modify the heightmap.png and run the program he said me :
[…] \TerrainNodePath.py", line 97, in init
xTexScale = .01/self.heightMap.getXSize()
ZeroDivisionError : float division

My map is a 257x257 pixels (i dont change it)
And is totally white (for testing)
I dont uderstand, i tried with customyze the map (not all white) and he tell me the same thing…

xTexScale = (1.0/scaleValue)/self.heightMap.getXSize()

thats my line 97 in the same file …therefore o guess your scale-value is 100.

ZeroDivisionError : float division
ZeroDivision error… hm shure that the filename and everything is allright? if (for some reason) getXSize() would return zero you would divide a floating number through 0 … dividing thorugh 0 generally is a bad idea
first make shure it really finds the imgae and it really gets the size right.
well i’m neither a good programmer nor a good debugger but thats what it looks like to me.
if nothing works try to enter the x-size value manually
xTexScale = .01/257
might work… well maybe it wont but it’s worth trying

good luck!
thomy e

Someone mentioned the terrain editor that comes with Panda. I noticed that my release (latest I believe) just had a note stating that it is currently not working… Does anyone know a good (as in easy to use and rapid to learn good) terrain/model editor to use with Panda and where to get it?

Sort of related topic: Does anyone know if it is possible to modify polygons at runtime? My idea was to start from a totally plane surface with multiple grid-points and then creating an editor (using the mouse pointer) that lifts and sinks nodes to create hills and valleys in the terrain…

shure it is possible. have a lookt at the manual.
http://panda3d.org/manual/index.php/Advanced_operations_with_Panda%27s_internal_structures
and the following ~16 sites have detailed information on how panda stores, generates and modifies geometry.
the terrian generator of this topic uses the technices,except that he takes the color-value of the image as heigh. all you would have to do is get the vertex and lower/increase the heigh. shouldn’t be too difficult.

I found a tips for make collision with the heightmap.
I read the rgb value of the pixel where is the actor (on an heightmap the r, v and b value are always the same), i make the same thing with the pixel where the actor must move and if the result is > as the start pixel, then the character can’t move, else he fall.
here is the code:

	def tomber(self,dist,v) :
			x =int(self.Ralph.getX()/5)
			y =int(self.Ralph.getY()/5)
			valr =self.heightMap.getRedVal(x,y)
			valb =self.heightMap.getBlueVal(x,y)
			valg =self.heightMap.getGreenVal(x,y)
			val1 = valr+valb+valg
			angle = self.Ralph.getH()*math.pi/180.0
			self.dx = dist*math.sin(angle)
			self.dy = dist*-math.cos(angle)
			a = self.Ralph.getX()
			b = self.Ralph.getY()
			self.c = self.Ralph.getZ()
			a,b = int(a), int(b)
			if v == 0 :
				x =int((self.Ralph.getX()/5)+self.dx)
				y =int((self.Ralph.getY()/5) + self.dy)
				x2 =int((self.Ralph.getX()/5)+2*self.dx)
				y2 =int((self.Ralph.getY()/5) +2*self.dy)
			else :
				x =int((self.Ralph.getX()/5)-self.dx)
				y =int((self.Ralph.getY()/5) - self.dy)
				x =int((self.Ralph.getX()/5)-(2*self.dx))
				y =int((self.Ralph.getY()/5) -(2*self.dy))
			valf =self.heightMap.getRedVal(x,y)
			vale =self.heightMap.getBlueVal(x,y)
			val2 = valf*3
			val3 = vale*3
			if val2 > val1 :
				return 0
			elif val2 < val1 and val3 == val2:
				self.c = (val2*0.98)/3
				d = 15+(6*(375-(val1-val2))/243)
				dist += d
			elif val2 < val1 and val3 < val2:
				self.c = (val3*0.98)/3
				d = 15+(6*(375-(val1-val3))/243)
				dist += d
			elif val2== val1 :
				self.c = (val1*0.98)/3

			self.dx = dist*math.sin(angle)
			self.dy = dist*-math.cos(angle)
			self.infox.setText(str(a))
			self.infoy.setText(str(b))
			self.infoz.setText(str(self.c))
			self.infov.setText(str(val1))
			
	def recul(self):		

			#charge le modele animé et le fait bouger
			dist = 1.0
			a = self.tomber(dist,1)
			if a == 0 :
				return
			self.RalphWalk = Parallel(
					self.Ralph.posInterval(.5,Vec3(self.Ralph.getX()-self.dx,self.Ralph.getY()-self.dy,self.c)),
					self.Ralph.actorInterval("Walk"))
			self.RalphWalk.start()
			# fin de walk 

	def walk(self):		
			#charge le modele animé et le fait bouger
			dist = 1.5
			a = self.tomber(dist,0)
			if a == 0 :
				return
			self.RalphWalk = Parallel(
					self.Ralph.posInterval(.5,Vec3(self.Ralph.getX()+self.dx,self.Ralph.getY()+self.dy,self.c)),
					self.Ralph.actorInterval("Walk"))
			self.RalphWalk.start()

			# fin de walk