Terrain Issue

How do you mean? Aren’t I working with those already? o_o;’

It doesn’t look like it, based on the code that you pasted. It looks as though you’re trying to take the entries from the collision queue and placing them into another list (“entries”); somewhere along the way you seem to be ending up with a list of PandaNodes instead of CollisionEntries.

Unless you posted the wrong snippet of code, of course, or I’m mistaken in some way.

        self.cTrav = CollisionTraverser()
        self.groundRay = CollisionRay()
        self.groundCol = CollisionNode('camRay')
        self.groundColNp = base.camera.attachNewNode(self.groundCol)
        self.groundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.groundColNp, self.groundHandler)

As you can see there, I’m creatting the groundCol CollisionNode but groundHandler is a CollisionHandlerQueue(). Later in the code, I’m doing…

        entries = []
        for i in range(self.groundHandler.getNumEntries()):
            entry = self.groundHandler.getEntry(i).getIntoNode().getParent(0)
            print entry
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
        if (base.camera.getZ() < self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)

As you can see, the groundHandler(CollisionHandlerQueue) is being called again.

I’m not sure how I’m ending up with the wrong entries, if that’s the case. Though I don’t know much. =P

Aha! Here, I believe, we have the problem. :slight_smile:

First of all, you’re copying results taken from groundHandler into a list, which is what I recommended against: it seems superfluous to me, and adds complexity that doesn’t seem to me to be called for.


According to this you’re already storing the parent of the into-nodes of the entries, not the CollisionEntries themselves.

You then go on to treat the elements of “entries” as though they were CollisionEntries, attempting again to fetch their “into-node”, despite now acting on a list of PandaNodes:

Why copy items into another list in the first place? It looks like the purpose of copying the entries to a list was to sort them, but sortEntries(), called before you start accessing the entries, should do that for you, I believe.

Finally, yet again I apologise - I should perhaps given you getIntoNodePath, instead of getIntoNode; the former returns a NodePath, while the latter returns a PandaNode, and nodepaths seem likely to be of more use to you, I think.

Do you mind throwing something small together to show an example of how it should be done? Like, all what you just said. Sorry, if that’s a bother, just you mentioned so many different things and I understand what you mean but I don’t know how to “do it” the ways you suggest. >_<;’

Hmm… I wonder whether it wouldn’t be more advantageous for you if we were to instead go through the above more slowly, looking for problem points for you.

First, the use of sortEntries: Just calling self.groundHandler.sortEntries() should sort your queue into nearest-to-furthest order for you.

First of all, it appears as though the purpose of the for-loop and the list “entries” is to sort the items in the queue. This doesn’t seem to me to be called for, since you can just call groundHandler.sortEntries(), I believe.

Secondly, in that for-loop, you have:

entry = self.groundHandler.getEntry(i).getIntoNode().getParent(0)

This means that the entries stored in “entries” are the parents of the into-nodes of each entry, and thus not the entries themselves, presumably. This is a potential problem because you seem to later call getIntoNode on the elements of “entries”, meaning that you’re calling it on a PandaNode, not a CollisionEntry.

Allow me to illustrate. I’ve taken your code, removed all but three lines, and added in some comments:

        for i in range(self.groundHandler.getNumEntries()):
----------> entry = self.groundHandler.getEntry(i).getIntoNode().getParent(0)
            #Intervening line

            #"entry" is thus the parent of the
            # into-node of getEntry(i).
            #getEntry(i) should be a Collision Entry,
            # getEntry(i).getIntoNode() should be
            # a PandaNode, and
            # getEntry(i).getIntoNode().getParent(0)
            # should be the parent of that node,
            # I believe.

            #Do other stuff

------> if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):

            #You would here seem to be
            # examining the entries
            # that you moved to the list
            # previously.  As we noted above,
            # these are the parents of PandaNodes,
            # and thus PandaNodes themselves,
            # I think, and therefore don't have
            # a "getIntoNode" method.

            #Do other stuff

Of course, since you can sort the list using sortEntries, the for loop is probably not called for. You should be able to just call sortEntries, and then use getEntry to look at the first entry, I would think…

From what I am getting from what you’re saying, I should be doing something like this:

        entry = self.groundHandler.getEntry(0)
        print entry
        if (entry) and (entry.getIntoNode().getName() == "terrain"):
        if (base.camera.getZ() < self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)

I’m still getting the entries printed out, but my problem hasn’t gone away.

I’ve just fixed part of my problem, here is my code. I don’t know why I didn’t try this. >_<

        entry = self.groundHandler.getEntry(0)
        print entry
        if (entry) and (entry.getIntoNode().getName() == "terrain"):
        if (base.camera.getZ() < self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)
        if (base.camera.getZ() > self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)
        if base.camera.getZ() < entry.getSurfacePoint(render).getZ():
            base.camera.setZ(entry.getSurfacePoint(render).getZ() + 1)

Anyway, I tested to see if the camera is under the terrain so to set it higher if it is because it would sometimes see underneath the maps. Even though this fixes that angle of seeing under the maps, you still see under the map but only for a split second. Basically, you see under the map for a second when on a hill at the right angle, but then it suddenly recorrects it’s Z and moves it higher. Is there any way to just disallow the camera from ever seeing under the map at all to make sure you dont see it even for that split second?

We are however, I think, making progress. :slight_smile:

First of all, that does indeed look like what I meant (although checking that there are entries before you access them might be a good idea). :slight_smile:

My next question, then, is to ask what is printed out when the code runs.

  from render/camera/camRay
  into render/terrain/gmm5x8 []
  at 85.8698 140.754 16.1943
  normal -0.00294116 -0.000980387 49.9998
  respect_prev_transform = 0

There ya go. By the way, I’m just curious but what is a good FPS rate for a high res 512x512(or somewhere around there) map size that just has a height and texture map? I seem to be getting fairly low FPS even though I have a pretty good graphics card. x_x;

I’m wondering if something I’m doing is very inefficient. =/

Aah yes, of course, I’d forgotten that - the ray is actually colliding with a sub-part of the terrain. If you look at the output that you posted, note that “terrain” is listed one level before the end, which is to say that “terrain” would seem to be the parent of the intersected node.

So, we again want to check the parent. One correction of my advice that I want to make is to use getIntoNodePath instead of getIntoNode - the former has a getParent method that doesn’t call for any parameters, (having only one optional parameter), as I recall.

In other words, try replacing self.groundHandler.getEntry(0).getIntoNode() with self.groundHandler.getEntry(0).getIntoNodePath().getParent().

As to speed, I’m not sure, I’m afraid. Colliding with visible geometry every frame is inefficient, admittedly, so you can try disabling the ray testing that we’re doing and seeing how that affects your framerate.

You might want to look again at ThomasEgi’s suggestion of using getElevation and looking for a suitable terrain detail level instead of the ray method that we’re looking at. It will probably not produce the accuracy that the ray method might, taking you back to having Ralph not follow the visible terrain as you might like, but should run more quickly, I think.

(I very much apologise; I feel that I should have thought to recommend this earlier, but I think that I got caught up in the ray technique and forgot that it’s inefficient and that a potentially-better method exists.)

Well, I tried what you mentioned but I am still having problems.

I get this:

    if (entry) and (entry.getIntoNode().getName() == "terrain"):
AttributeError: 'libpanda.NodePath' object has no attribute 'getIntoNode'

I still get this even after I made “entry.getIntoNode().getName()” into “entry.getName()”, also:

AttributeError: 'libpanda.NodePath' object has no attribute 'getSurfacePoint'

It’s fine, I really appreciate the help! I’ve learned a lot. :slight_smile:

I wonder what would be the best way for me then. Obviously many games have terrain that their character does not pass through and it’s efficient so I there has to be some way, that I just don’t know of. =(

Ohh, I see - I’d forgotten or overlooked the fact that you were storing the entry in a variable and accessing it later, and, if I’m not much mistaken, you’ve applied the change that I suggested to the code before that which you posted, resulting in more or less one of the mistakes that you made earlier.

Your entry should just be self.groundHandler.getEntry(0), I believe. Instead, try applying my suggested change later, when you want to check the name of the object hit. In other words, change

if (entry) and (entry.getIntoNode().getName() == "terrain"): 


if (entry) and (entry.getIntoNode().getParent().getName() == "terrain"): 


Above that you should simply have something like this, I think:

     #Check that there actually are entries,
     # if you haven't already, just in case.
     if self.groundHandler.getNumEntries() > 0:
          entry = self.groundHandler.getEntry(0) 

Thank you, and I’m glad. :slight_smile:

Well, there are a number of options, I think, although I know little about their implementation in Panda, I’m afraid.

For example, a game might use a high level of detail nearby, only lowering it further away; nearby, using something akin to the getElevation method should look good, since the terrain detail is high and thus should reasonably well match the heightmap, while in the distance errors would likely be less noticeable.

Another, probably more expensive idea might be to perform ray intersections, as we are, but to only check polygons that are nearby. This involves a bit of work, however, I imagine, and I don’t know that it would be particularly efficient.

For you I would suggest going along with ThomasEgi’s suggestion, unless it proves too slow for some reason.

I seem to have missed this edit to a previous post:

I imagine that you could fix that by only setting the camera’s z-value once, when you have a final position, and operating on a temporary variable until that point.

Something along these lines (modified from the code that you posted):

cam_z = entry.getSurfacePoint(render).getZ()+1.0)
        if (cam_z < self.actor.getZ() + 2.0):
            cam_z = (self.actor.getZ() + 2.0)
        if (cam_z > self.actor.getZ() + 2.0):
            cam_z = (self.actor.getZ() + 2.0)
        if cam_z < entry.getSurfacePoint(render).getZ():
            cam_z = entry.getSurfacePoint(render).getZ() + 1)


By the way, your first two “ifs” seem redundant - do you not perhaps mean “< self.actor.getZ() - 2.0”, with a similar change to the line that follows it, in the first of them? Otherwise, why not just set the value to self.actor.getZ() + 2 without any “ifs”?

Similarly, you seem to deal with entry.getSurfacePoint twice…

Perhaps, instead of starting off with setting the z-value to 1 above the entry’s surface point, you might start off by setting it to 2 above the actor’s z-value, and then test against entry.getSurfacePoint and handle any changes appropriately.

Wow, you posted so much! I can’t possibly quote and reply to it all. xD

Here’s the summary though: It worked!

        #Check that there actually are entries,
        # if you haven't already, just in case.
        if self.groundHandler.getNumEntries() > 0:
            entry = self.groundHandler.getEntry(0) 
            print entry
        if (entry) and (entry.getIntoNodePath().getParent().getName() == "terrain"): 
        if (base.camera.getZ() < self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)

That exact piece of code seems to take care of my problems. It doesn’t go beneath the ground, it follows up and down hills, etc. That’s obviously just for the camera though. I probably do want to now go back and see how I can use the getElevation() and try to optimize this as much as possible. My FPS goes anywhere from 25-60 when actually running around the terrain. My graphics card is a GeForce 9800 GT 512MB, so I don’t think that the card is the problem, it’s probably just an inefficient way of writing the code. =/

geomipterrain’s performance HEAVILY depends on your settings. it can make an insanely huge terrain running on terribly old hardware with correct settings. but it can break performance even on highest-end graficcards.
especially blocksize and levelOfDetail settings are important.
since those settings greatly depend on your type of application and the usage of geoMipTerrain you might want to play around a bit.
be aware of a few things. collision against the terrain using rays can be slow.
so while trying to optimize your scene/code make sure to seperate things like collision and geometry. using pstats is highly recommended.

Yeah, I went back looking through my code and noticed I had commented the setBlockSize(32) out, and when I changed it back, I’m getting ALOT better results. The lowest it got to was 45 fps once, but for the most part it was 65-75 fps. I’m sure there is a lot more I can probably do, that I just haven’t figured out yet. ThomasEgi, is it possible for you to show me an example of what my code “should” look like if I were going to use the .getElevation() rather than what I am using now, or point me to a thread or something? You seem to know a good amount on the subject. :slight_smile: Also, even though the rays have solved my current camera problem, they didn’t solve the problem of my character going through the map. I’d try to fix it with ray’s also, but since we know this is an inefficient way, might as well only work on figuring out how to do it the “proper” way?

I’ve been messing around with the settings. I’ve tried changing the LOD to all sorts of levels, like 10,15,50,100, etc. I’ve also tried setting the BlockSize to 64 instead of 32.

Again, here is my code for setting my Z for my ralph actor:

self.actor.setZ(self.parent.terrainRoot, self.parent.terrain.getElevation(self.actor.getX(self.parent.terrainRoot), self.actor.getY(self.parent.terrainRoot)))

since i dunno your code i’ll just post what you need in order to use geomipterrain the way its supposed.

since you already set up the terrain i’ll skip that.

geomipterrain creates terrain with different quality.
to do that it uses a focal point around which the quality of the terrain is best.
usualy you would want this focal point to be either the camera or your main actor.
(as long as the camera is not terribly far away from the actor , then you should use the node representing your actor)
to do this just do


in your case the “yourGeoMipTerrainInstance” might be just “self.parent”
(dunno if supplying a nodepath already works. it got added just recently. you might need to pass X,Y coordinates,too)

since your character and camrea moves around. the terrain has to get updated from time to time. just do a


once in a while. depending on your game you might want to call it every second, every few seconds, or even every frame. but once per second should be enough in most cases.

if you experience problems with the player diving into the ground. you should set the setMinLevel to 0. instead tune the setFactor() it takes a value of 0 to 100. 0 is worst quality. 100 is best.

next thing is the auto-flatten feature.
i dunno how exactly it works behind so i cant tell you how you use it best. just experience to check what works best.

last but not least. the blocksize.
its a very important part to tune. too small will result in too many nodes, resulting in a very extrem performance drop. too high will cause the lod to get inefficient , thus defeat the whole purpose of geomipterrain.
this value heavily depends on your terrainsize, your type of game and some other things.
you would not want to set it below 16, it will break performance on any terrain.
setting it above 128 might only be a good idea on VERY VEEEERY huge terrains. sane values are usualy 32 or 64. depends on your hardware.
larger block sizes can cause more “popping” when terrain gets updates. but usualy renders faster. too large values will break the LOD system, too small will cause the geometry go get inefficient to render (autoflatten might fix this issue)

as you see. using geomipterrain is a science for itself.
guess it wouldnt be too bad to write a small performance-messuring application which tests all possible settings and decides for the best settings possible. thought thats totaly offtopic :slight_smile:

good luck

  File "C:\Panda3D-1.5.2\direct\src\showbase\ShowBase.py", line 2262, in run
  File "C:\Panda3D-1.5.2\direct\src\task\Task.py", line 939, in run
  File "C:\Panda3D-1.5.2\direct\src\task\Task.py", line 877, in step
  File "C:\Panda3D-1.5.2\direct\src\task\Task.py", line 776, in __stepThroughList
    ret = self.__executeTask(task)
  File "C:\Panda3D-1.5.2\direct\src\task\Task.py", line 696, in __executeTask
    ret = task(*task.extraArgs)
TypeError: update() takes exactly 0 arguments (1 given)

I seem to be getting that error when I try to add the “terrain.update()” to the taskMgr. This example was taken from the manual:

taskMgr.add(self.terrain.update, "update")

Is it wrong or am I doing something wrong?

terrain.update is not a task itself.
you need to set up a task which calls terrain.update()

def self.udpateTask(self,task):
    return task.again

myTask = taskMgr.doMethodLater(2,updateTask, updateTask')

this would call update every 2 seconds which should be totaly fine in most cases

Okay, well that worked…kind of. I mean, I see the terrain being updated but it’s all very jittery, even with the LOD set very low. Every 2 seconds, it like…jerks kind of, cause of the “re-rendering”(I think).

the lower the quality the uglier the updateing.

what you named as re-rendering is actually the geomipterrain working.
since you experience it as ugly . your terrain and the parameters you chose for it are simply not suited. can you make a screenshot of your terrain+character.
if possible with wire-frame mode set for the terrain. and one with and withotu bruteforce terrain.
its also a help to know the current settings you’r using like blocksize etc. aswell as the terrains overall size (input image x and y resolution)