Slopes and sliding

Greetings fellow Panda3D devs. It’s been a while since I posted here about my 3d adventure game, but there is something that has been stumping me for a long time and giving me a sort of coder’s block if you will. I am ashamed to admit I haven’t come up with any viable solutions so I am hoping you can help get me back on track.

My problem is: My game has uneven terrain such as hills and slopes and stairs that the player and NPCs can climb. I want them to move slowly up slopes and smoothly down slopes, but I also want them to slide down steep surfaces.

I need generic code that can apply in any situation with slopes of any angle / direction, not just cardinal directions.

The way I handled this so far is by checking the difference in the current Z value of the player compared to the surface below their feet, and adjusting speed based on that. But this does not address steep surfaces or sliding down while not providing any control input. Code below (highestZ here is the Z value of the surface below the player that collides with a downcast ray).

        fudge=0.15
        zd = highestZ - (self.node.getZ()-fudge)
        if zd > 0:
            self.grounded=True          # we're on solid ground
            self.node.setZ(highestZ+fudge) # set Z to floor height
            self.moveZ = 0              # stop downward momentum
            speedMult = max(0.2, min(1,1-(zd*self.climb_speed_penalty)))
            self.moveX *= speedMult
            self.moveY *= speedMult

This is not what I really want and it leads to some janky physics.

What I considered was to cast more rays downward, offset in a circle around the player, to detect the slope at various directions from the center of the player. But this seems dumb, as it requires an arbitrary number of rays to approach perfect slope detection and would probably slow things down if every NPC that moved needed so many collision rays. If we used fewer collision rays the performance would increase but the slope detection would suffer.

Is there any mathematical way to detect the angle / steepness of the slope that our downcast ray collides with, or is there some solution I haven’t thought of which does not require such a brute force method?

Please let me know your thoughts on this. I’m very new to 3D game design, and I think I lack the tools to solve this problem on my own. Thanks in advance, and I look forward to your comments.

I would suggest getting the normal-vector of the surface (a ray-cast will usually include this in its results, if I’m not much mistaken), and then generate a vector at ninety degrees to that, which will be your “downslope vector”.

I see two ways of generating that vector from the normal vector, offhand:

  1. Take the cross product of your normal vector and a vertical unit vector, which should give you a vector pointing along the side of the slope. Then take the cross product of the normal vector with this new side vector, and you should get a vector pointing at ninety degrees to both–in this case down (or up) the slope.

  2. (I haven’t tested this as far as I recall, so I don’t know whether it works.) The downslope vector is a vector at ninety degrees to the surface normal, pointing in the same direction as the surface normal’s xy-component, and with a z-component pointing in the opposite direction to the surface normal’s z-component. Furthermore, the downslope vector should have an xy-component with length equal to that of the surface normal’s z-component, and a z-component with length equal to that of the surface normal’s xy-component. Thus we should be able to create a downslope vector by copying the surface normal, swapping the lengths of its xy- and z- components, and then giving the z-component a sign opposite to the sign of the surface normal’s z-component.

Whichever way it’s done, there may be some edge-cases to watch out for, and some minus signs to insert or remove along the way (that is, some elements might call for negation if they’re pointing the wrong way).

Once you have the downslope vector, you should have both the direction and, with a bit of maths, the angle that indicates which way to slide and whether to do so or not, respectively.

(Although you perhaps don’t really need the angle, as such; you could probably get away with just examining the length of the z-component of the surface normal: a high value indicates a flat surface, and a low value indicates a steep surface.)

Thanks so much! That’s exactly what I needed. I’m not sure how exactly I’ll do the actual slope-sliding but this will for sure help me get there. For anyone curious, the documentation is here:

https://docs.panda3d.org/1.10/python/programming/collision-detection/collision-entries#collision-entries

entry.getSurfaceNormal(nodePath)

And that is the function to receive the normal from the collision entry object. It seems to return a VectorF object.

What I think I will do is more along the lines of your edit at the bottom, where I will examine the angle and the Z coordinate. If it’s high enough the player/NPC will not slide. Otherwise they will slide in the direction of the normal relative to the extremity of the Z coordinate but I think I might actually keep my existing code as well after a slight modification or two, because I think that will still be useful for stairs.

In any case, thanks again! I’ll try to post again once I come up with something more specific.

Interesting idea about getting the 90-degree angle to the normal, though… worth considering but I just think that may cause other problems (though I’m not sure)

It’s my pleasure! I hope that you get it working as you intend. :slight_smile:

(For what it’s worth, my own code, as implemented in my main project, uses the first of my suggestions above I believe.)