Terrain Issue

First off, I’m quite new to Panda3D. Now that that is out… =P

I’m having problems getting my terrain to work properly. I made my own little terrain in L3DT and then exported the height map and the texture map. Here is the way I try to use those maps:

self.terrain = GeoMipTerrain("scene")
       
self.terrain.setHeightfield(Filename("heightmap.png"))
self.terrain.setBlockSize(32)
self.terrain.setFactor(10)
self.terrain.setFocalPoint(base.camera)
self.terrainRoot = self.terrain.getRoot()
        
self.terrainRoot.setTexture(loader.loadTexture("terrainmap.bmp")) 
self.terrainRoot.setPos(0,0,0)
self.terrainRoot.reparentTo(render)
self.terrainRoot.setSz(30)
self.terrain.generate()

And I use:

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

… to set the position of my actor on each “move()”.

That “seems” to work for the basics(the hills are hills, the character can run around, etc) but for some reason when my character(ralph) runs around, he goes partially inside the ground sometimes(on the hill). I’m not sure if I’m not writing the code right, the geometry is not being done right, etc. I’d really appreciate someone shedding some light on my problem, if possible.

Also, I’m just using the basic camera collision code that is from the first Ralph sample program, and when I run down a hill(I start at the top of the hill on program load), the camera follows it for a bit but then stays at it’s Z and won’t follow the character down to it’s location. When ralph runs back up the hill, the Z returns to right behind him and at the right height, etc.

On the subject of the camera behaviour, perhaps you should post that code as well. It sounds, at least, as though you’re only adjusting the camera when the terrain is too high for its current position, and not when it’s too low.

if the character only enters the terrain slightly it might be due to the fact that get-elevation returns the heigth of a single point. but ralph’s legs might move further ahead or back than this point and enter the terrain a little.

another point would be the LOD system of geomipterrain. afaik the get-elevation function does not concider the actual lod-geometry. thus when you have lower-resolution terrain the actual elevation might differ from the geometry.

would be intresting to see an screenshot showing the wrong-height issue.

There is the image of my ralph character inside the map. The map is rendered low quality because I thought it’d be easier to deal with for right now.

Here is the second image, which is when the camera is high(like the last one) and also showing that ralph is not always in the ground:

Here is the code for the camera:

        # If the camera-left key is pressed, move camera left.
        # If the camera-right key is pressed, move camera right.
         
        base.camera.lookAt(self.actor)
        camright = base.camera.getNetTransform().getMat().getRow3(0)
        camright.normalize()
        if (self.controlMap["left"]!=0):
            base.camera.setPos(base.camera.getPos() - camright*(elapsed*20))
        if (self.controlMap["right"]!=0):
            base.camera.setPos(base.camera.getPos() + camright*(elapsed*20))

        # If the camera is too far from the actor, move it closer.
        # If the camera is too close to the actor, move it farther.

        camvec = self.actor.getPos() - base.camera.getPos()
        camvec.setZ(0)
        camdist = camvec.length()
        camvec.normalize()
        if (camdist > 10.0):
            base.camera.setPos(base.camera.getPos() + camvec*(camdist-10))
            camdist = 10.0
        if (camdist < 5.0):
            base.camera.setPos(base.camera.getPos() - camvec*(5-camdist))
            camdist = 5.0

        # Now check for collisions.

        self.cTrav.traverse(render)

        # Keep the camera at one foot above the terrain,
        # or two feet above the actor, whichever is greater.
        
        entries = []
        for i in range(self.groundHandler.getNumEntries()):
            entry = self.groundHandler.getEntry(i)
            entries.append(entry)
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
            base.camera.setZ(entries[0].getSurfacePoint(render).getZ()+1.0)
        if (base.camera.getZ() < self.actor.getZ() + 2.0):
            base.camera.setZ(self.actor.getZ() + 2.0)
            
        # The camera should look in the player's direction,
        # but it should also try to stay horizontal, so look at
        # a floater which hovers above the player's head.
        
        self.floater.setPos(self.actor.getPos())
        self.floater.setZ(self.actor.getZ() + 2.0)
        base.camera.lookAt(self.floater)

Hmm… Have you checked that you are indeed getting results in the “entries” list, and are you sure that the terrain is indeed named “terrain”, and nothing else (in other words, not “my_terrain”, or “terrain1”, for example)? On another note, are you using bitmasks anywhere? For example, if the terrain is not set to be collision geometry, are you setting the detection ray’s “from” bitmask to that of visible geometry?

Note that the camera is set to increase in height when below the player, but the only code that seems to act to lower the camera when it’s above the player is that which detects the terrain, and therefore the problem would seem to be there.

Finally, I wonder whether it might not be a good idea to replace the sorting method (including the separate list) used for the terrain height detection with a call to self.groundHandler.sortEntries (I presume self.groundHandler to be a CollisionHandlerQueue), as mentioned on this page.

How do you know? Like I said, I’m kind of new so a lot of this is mixing different ralph programs I’ve seen with the sample one. =/

I’ve made sure this says terrain now, if that’s what you mean:

self.terrain = GeoMipTerrain("terrain")

Same thing happens.

All you see there, is exactly what I’m doing to the terrain itself. I only have those two image files for the terrain. I just did the basic L3DT settings and clicked export.

It is a CollisionHandlerQueue, and that’s fine with me, but might you be able to give an example? I’m trying to get this terrain stuff down with a small simple map like the one I’ve got before I start making bigger(more complex) one’s. =x

That looks right to me, although I’m afraid that I’m not highly confident of that.

Heh, ironically, I just realised that the line that you corrected was present in the code that you gave previously, so it would seem that I missed it too! laughs

Fair enough - this is taken from some code of my own. with comments added (and some formatting changes):

Notes:

  • “self.picking_traverser” is my traverser, presumably analogous to your “self.cTrav”
  • “self.picking_queue” is, of course, my CollisionHandlerQueue
     #Have the traverser traverse.
     self.picking_traverser.traverse(render)

     #Check to see whether there are any entries
     if self.picking_queue.getNumEntries() > 0:
          #Sort the entries from nearest to furthest,
          # if I recall correctly.
          self.picking_queue.sortEntries()
          
          #Get the surface collision position of
          # the first (and thus nearest) entry
          selected_pos = self.picking_queue.getEntry(0).getSurfacePoint(render)

          #I then go on to do something with
          # that data...

Oh yes, another thing that just occurred to me - are you adding your ray to the collision traverser? I don’t see that in your code, although of course you may simply have not posted it; I think that it might well help to check that, allowing me to check for bitmask usage and traverser addition.

In case that is where your problem lies, this is how I set up the system whose code I posted above (with additional comments, and some formatting changes):

     #Create my traverser
     self.picking_traverser = CollisionTraverser("picker")

     #Create my picking ray
     self.picking_ray = CollisionRay()
     ray_path = camera.attachNewNode(CollisionNode("picking ray"))

     #At this point I set up my bitmasks;
     # for your purposes, you should probably 
     # be setting the "from" bitmask to
     # "GeomNode.getDefaultCollideMask()"
     # and the "into" bitmask to BitMask32(0).
     # The latter accounts for rays not being
     # good objects into which to collide.
     # Please see this page: https://www.panda3d.org/manual/index.php/Collision_Bitmasks

     #Connect the ray nodepath with the
     # ray solid
     ray_path.node().addSolid(self.picking_ray)

     #Create my queue
     self.picking_queue = CollisionHandlerQueue()

     #Add the ray and the queue to
     #my collision handler.
     self.picking_traverser.addCollider(ray_path, self.picking_queue)

One final note: If your picking ray is within an object, and set to collide with visual geometry, you may well find that your first collision is the object within which the ray is set. There are a few approaches to this, including:

  • Ignoring the first entry, if you’re confident that this will be the case.
  • Checking the first entry, and if it’s the object within which the ray sits, moving on to the next entry.
  • Searching through the list for an entry that belongs to the terrain object, and using that. The applicability of sortEntries then perhaps becomes debatable.
  • Moving the ray to a point below the object. If the object is that which you want to keep outside of the terrain, however, this is probably not a good idea.

low quality terrain can cause vertical offestts between your actual terrain mesh in high and in low quality. up as well as downwards. if you use the terrrain.getElevation() function thats what can happen. since the getElevation always returns the heights based on the best level of detail. so if your lower-level of detail differs. you can get what you got in your screen. either increase terrain details to a well balanced value. or use the visual geometry and collide against it using a ray. i’d recommommend the first choice since rays and visual geometry collision is not very efficient and easy to handle. compared to the thing you already use.

Let me see if I’m reading this correctly. =P

So basically try increasing the quality size of the terrain and see if that fixes my issue? If it does work, then great, that’s the way it should be done(Efficient, etc)? If it doesn’t work, then try with the collision rays like Thaumaturge posted?

Also, is there any default settings you guys think I should use to make sure my image is “prepared” correctly? I’ve been using L3DT but I’m not partial to any program. I’ll use Terragen, L3DT, Vue 6, etc especially if you know the default “options” to use to get me started at least.

|Edit|
I just went and tried to make it more detailed, which it seems to be, and I’m still getting the same problem. Is it possible that doing setSz() where the size is wrong would be a problem, or?

I’ll look at the collision rays, etc but I’d rather not use anything that is inefficient(if that’s the case) if there is a better way, such as getting it to just normally work with my maps. =x

Hmm, hold on… Am I correct in understanding you to mean that you’re not currently using a ray? In that case, what’s producing entries in your CollisionHandlerQueue?

Which ray do you mean? Ralph’s ray or the cam’s ray? Either way here is both:

        self.cTrav = CollisionTraverser()
        self.groundRay = CollisionRay()
        self.groundRay.setOrigin(0,0,1000)
        self.groundRay.setDirection(0,0,-1)
        self.groundCol = CollisionNode('ralphRay')
        self.groundCol.addSolid(self.groundRay)
        self.groundCol.setFromCollideMask(BitMask32.bit(1))
        self.groundCol.setIntoCollideMask(BitMask32.allOff())
        self.groundColNp = self.actor.attachNewNode(self.groundCol)
        self.groundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.groundColNp, self.groundHandler)

And cam’s ray…

        self.cTrav = CollisionTraverser()
        self.groundRay = CollisionRay()
        self.groundRay.setOrigin(0,0,1000)
        self.groundRay.setDirection(0,0,-1)
        self.groundCol = CollisionNode('camRay')
        self.groundCol.addSolid(self.groundRay)
        self.groundCol.setFromCollideMask(BitMask32.bit(1))
        self.groundCol.setIntoCollideMask(BitMask32.allOff())
        self.groundColNp = base.camera.attachNewNode(self.groundCol)
        self.groundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.groundColNp, self.groundHandler)

They look very similar in name, such as “groundRay” because the code for the Control and the code for the Camera were made into Classes.

Anyways, just so you know, the Camera collision testing code has always been in place but I’ve currently got the ralph collision testing code commented out(the “entries” code) because some guy had an example found here: https://discourse.panda3d.org/viewtopic.php?t=4559&highlight=ralph

That just uses:

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

Heh, sorry, I meant the camera ray. ^^;

Hmm… Are you setting the terrain’s “into” collide mask appropriately as well? I’m afraid that I’m not terribly familiar with the GeoMipTerrain system; perhaps it does that itself?

Essentially, I’m wondering whether your camera ray is set up so as to collide with the terrain. For that matter, have you checked that you are getting entries when you examine the camera ray’s collision queue, and if so, what entries you’re getting?

I’m not sure, if it looks like the code for the ralph ray or camera ray, then no, otherwise I’m not sure. xD

I tried this for the camera:

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

If you notice, I pointed out the prints and the initialization. When I went running around, I always got blank brackets like “[]”.

Then it sounds as though you’re not getting collisions at all, and my current guess is that the culprit is a lack of appropriate collision mask setting.

For now, try replacing your setting of the ray’s “from” mask to something along these lines: self.groundCol.setFromCollideMask(GeomNode.getDefaultCollideMask())

If you get results in your queue using the above, then your mask setting (or lack thereof) might well be the issue.

Bear in mind, again, that I’m not terribly familiar with GeoMipTerrain, so there might be procedures to be followed that I don’t know.

I think we’re making progress, look what I just got! O_O;’

CollisionEntry:
  from render/camera/camRay
  into render/scene/gmm6x8 []
  at 100 136.957 19.4469
  normal -0.00833304 -0.000735268 49.9983
  respect_prev_transform = 0

[render/camera/camRay into render/scene/gmm6x8 at 100 136.957 19.4469]

I don’t know what to do from there, but that’s what I’ve got now from the “entries” in the collision ray, after using the code you told me to use. xD

Aah, excellent! I’m glad to hear it. :slight_smile:

Hmm… I notice that the name of the second element in the intersected nodepath is “scene” - have you changed your terrain’s name back to “scene”?

The last element of the nodepath looks like the specific terrain chunk that was hit, so I’m guessing that you’ll be interested in the name of the parent of the first result in this case. In other words, instead of looking at groundHandler.getEntry(0), it looks as though you should examine at groundHandler.getEntry(0).getParent() (if I recall the calls correctly).

You’re right, so I switched it back to “terrain”. I don’t know how I changed it. xD

Also, I tried the “getParent()” but that’s not found. I’ll see if I can find what you mean in the Manual while waiting for your reply. ^-^

Fair enough. :stuck_out_tongue:

Ah, that’s just a silly mistake on my end, I think - I should written something along the lines of “groundHandler.getEntry(0).getIntoNode().getParent()”. Sorry about that! ^^;

“TypeError: getParent() takes 2 or 3 arguments (1 given)”

That’s the error I am getting. =/

|Edit|
I also get this error:

    if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
AttributeError: 'libpanda.PandaNode' object has no attribute 'getIntoNode'

If I try to use “getParent(0)”. Not sure if that’s the correct way to use that function, I just threw that in there. xD

Hmm… Having gone back to the reference, it appears that while NodePath’s getParent method should work without parameters (other than the automatic “self” parameter, of course), PandaNode does have a getParent method that calls for a count, such as you provide.

The question, then, is how you ended up working with the nodes, instead of nodepaths… o_0

This seems odd to me, however. Your collision queue should contain CollisionEntries, not PandaNodes, I’m pretty sure… o_0

On the other hand, we seem to be back to some list of entries other than that in the collision queue, as in your old code…

This seems to tie into the above error - you seem to have ended up with a list of PandaNodes instead of CollisionEntries somehow.

I suggest that you switch to working directly with the CollisionHAndlerQueue’s entries. However, if you wish to stick with the current code, perhaps we should have another look at the point at which you fill that list of entries…