CollisionHandlerFloor

How do you use collision handler floor? The manual’s explination isn’t good enough to be usable.

I’ve never used it, but Fixer has. Ask him.

CollisionHandlerFloor is basically intended to solve the very specific problem of keeping an object on the floor. It works without using physics; basically, it detects for collision, and when it finds a collision has occured it moves the node it controls to the point of collision. So if you use a ray pointing downwards as your from collider, and the floor’s IntoCollideMask matches the ray’s FromCollideMask, you can have the CollisionHandlerFloor move a node to the floor. There’s also a parameter in there to have the handler move the node towards the point of collision over time instead of immediately jumping it into place; this gives you a simulation of gravity. It’s an inaccurate simulation, because the motion is at a constant rate (think Mario in Super Mario Bros. stepping off a ledge), but it works perfectly fine over short distances or if your world doesn’t need to adhere to real-world physics.

In terms of how to use it, the CollisionHandlerFloor operates much the same as a CollisionHandlerPusher. You need to specify the collision node and the node that is controlled to the collider, so it knows (a) what collision it is detecting and (b) what to do when collision is detected. It should be noted, however, that CollisionHandlerPusher and CollisionHandlerFloor can interfere; if you have both of them controlling the same node, they may fight over the node’s position because they aren’t aware of each other (NOTE: does anyone know how one could make them aware of each other?). So you want to structure your scene such that if your CollisionHandlerPusher and CollisionHandlerFloor both control the same node, they are never reacting against the same polygon. If they are, a condition I like to call “zany hijinks” ensues :wink: The way we do it over at SOS is to have the walls and floors use two different IntoCollideMasks (this works for us, since all our walls cannot be stood upon).

The reason you would use CollisionHandlerFloor instead of, say, CollisionHandlerPusher or PhysicsCollisionHandler is because of normals. If you have a sloped floor and a gravity effect pushing your object downwards, then these collision handlers will solve collision by pushing directly along the normal line of the surface. But with the gravity aligned in a direction that isn’t the normal, on the next frame the object gets pushed downwards again, and the collision handler pushes outwards along the normal. So you end up with a net direction of motion down the slope of the surface. There may be a way to fix this with friction coefficients, but I haven’t tried this so I can’t say one way or another.

So that’s the CollisionHandlerFloor in a nutshell. Let me know if you have any more specific questions!

Take care,
Mark

I tried to setup a test script using the roaming ralph models. The controls are crude and I haven’t added animations, but that shouldn’t affect the collision handler. Still, something seems to be very wrong.

from pandac import PandaModules as P
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.actor import Actor

base.openMainWindow(type = 'onscreen')

base.cTrav = P.CollisionTraverser()
class World(DirectObject):
    def __init__(self):
        #load ralph
        self.ralph = Actor.Actor("models/ralph",
            {"run":"models/ralph-run",
            "walk":"models/ralph-walk"})
        self.ralph.reparentTo(render)
        self.ralph.setScale(.2)
        self.ralph.setCollideMask(P.BitMask32.allOff())
        self.ralph.setZ(5)
        #set up ralph's collision ray
        self.ray = P.CollisionRay(0, 0, 1, 0, 0, -1)
        self.ralphRay = self.ralph.attachNewNode(P.CollisionNode('ray'))
        self.ralphRay.node().addSolid(self.ray)
        self.ralphRay.show()

        #load environ
        self.environ = loader.loadModel("models/world")
        self.environ.reparentTo(render)
        self.environ.setCollideMask(P.BitMask32.allOn())
        self.environ.setH(180)
        self.environ.setPos(0,0,-5)


        #the floor handler keeps ralph grounded
        self.floor = P.CollisionHandlerFloor()
#uncomment this line and ralph bounces
##        self.floor.setMaxVelocity(1)
        base.cTrav.addCollider(self.ralphRay,self.floor)
        self.floor.addCollider(self.ralphRay,self.ralph, base.drive.node())
        #shows the collision solids
        base.cTrav.showCollisions(render)

        for k,v in {'arrow_up':'up','arrow_down':'dn','arrow_left':'lf',
            'arrow_right':'rt'}.items():
            self.accept(k,self.move,[v])
    def move(self,direction):

        if direction == 'up':
            self.ralph.setFluidY(self.ralph.getY()+.1)
        if direction == 'dn':
            self.ralph.setFluidY(self.ralph.getY()-.1)
        if direction == 'lf':
            self.ralph.setFluidX(self.ralph.getX()-.1)
        if direction == 'rt':
            self.ralph.setFluidX(self.ralph.getX()+.1)
World()
run()

I checked the documentation and there appear to be three relavent methods to setting up a CollisionHandlerFloor: setMaxVelocity
setOffset
setReach If I don’t use setMaxVelocity() then ralph always appears under the floor, but if I setMaxVelocity() to something small, then ralph jitters, and glides down the hills instead of walking. I still can’t figure out what setOffset() and setReach() do.

The offset is the number of units along the collision ray that the floor handler will subract from the position of the node it is controlling. This is useful if, for example, you are controlling an avatar where the center is at its head; if you just use the floor handler without an offset, it would align the model’s center (its head) with the point of intersection, and the model would be under the floor. Offset allows you to solve that problem by adding a constant offset to the position.

I’m not sure what causes the jitter from looking at your code, but I’ve never seen the three-argument form of CollisionHandlerFloor.addCollider used before. Can you tell me what the third argument does? It doesn’t seem to be in the documentation.

-Mark

Actually I have no idea. The pusher example in the manual had the third argument. I tried removing it and it behaves exactly the same, as far as I can tell.

Is that any different than setting the z of the collision node? Why does ralph jitter? And why does he end up under the floor when I setMaxVelocity() too high?

Probably best to take it out then, at least until you have a use for base.drive.

Possibly. If you try them both and they both do the same thing, then they’re probably synonymous. But I was under the impression (I’m afraid I can’t pull my example out right now to check) that setting the position of the CollisionNode had no effect on anything, since the CollisionHandlerFloor will be moving the NodePath you specified as the controlled NodePath (i.e. self.ralph), not the collision node (the one created in the line self.ralph.attachNewNode(P.CollisionNode(‘ray’)) ).

The second one I can answer with some certainty; we’ll have to explore a bit more to figure out the first one. If your setMaxVelocity is high enough, ralph will be pulled toward the floor at a rate high enough that he ends up passing through the floor in one frame. Once the collision ray has passed completely through the floor, it can no longer collision detect against the ground.

Hm… now that I think about it, perhaps the jitter and the floor issue are related. When you describe that he ‘jitters,’ do you mean that he oscillates above-and-below the floor every other frame? I think that might mean that your motion isn’t smoothed. I see you’re using setFluidX and setFluidY; did you use base.cTrav.setRespectPrevTransform(1) anywhere to turn on fluid motion handling? If not, Panda optimizes out the smoothing step from the collision engine.

See if that helps. We’ll figure this out yet!

Take care,
Mark

Didn’t help.

Let’s see if we can break it down a bit more.

  • Is the floor collider the only collider used?
  • Where do you set the collision ray’s FromCollideMask?
  • Where do you initialize the collision traverser? I don’t see that anywhere in the code snippet you provided.

It may also help if you post your code again with the changes added in; that way, we can double-check and make sure that it isn’t some kind of ordering issue.

I should be able to get to my own example code tomorrow morning; that ought to allow me to check that against your code and see if any pieces are missing.

-Mark

See for yourself.

from pandac import PandaModules as P
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from direct.actor import Actor
#window-type none is in my Config.prc (the only change).
base.openMainWindow(type = 'onscreen')#you may not need this

base.cTrav = P.CollisionTraverser()#traverser
base.cTrav.setRespectPrevTransform(1)
class World(DirectObject):
    def __init__(self):
        #load ralph
        self.ralph = Actor.Actor("models/ralph",
            {"run":"models/ralph-run",#animations are not used yet
            "walk":"models/ralph-walk"})
        self.ralph.reparentTo(render)
        self.ralph.setScale(.2)
        self.ralph.setCollideMask(P.BitMask32.allOff())
        #set up ralph's collision ray
        self.ray = P.CollisionRay(0, 0, 2, 0, 0, -1)#from head & point down?
        self.ralphRay = self.ralph.attachNewNode(P.CollisionNode('ray'))
        self.ralphRay.node().addSolid(self.ray)
        self.ralphRay.show()
        #load environ
        self.environ = loader.loadModel("models/world")
        self.environ.reparentTo(render)
        self.environ.setCollideMask(P.BitMask32.allOn())
        self.environ.setH(180)
        self.environ.setPos(0,0,-5)
        #the floor handler keeps ralph grounded
        self.floor = P.CollisionHandlerFloor()
        self.floor.setMaxVelocity(2)#if set higher or to 0 ralph goes under.
##        self.floor.setOffset(.2) #I haven't figured this out yet
        base.cTrav.addCollider(self.ralphRay,self.floor)
        self.floor.addCollider(self.ralphRay,self.ralph)
        #shows the collision solids
        base.cTrav.showCollisions(render)
        #setup keyboard
        for k,v in {'arrow_up':'up','arrow_down':'dn','arrow_left':'lf',
            'arrow_right':'rt'}.items():
            self.accept(k,self.move,[v])
    def move(self,direction):
        if direction == 'up':
            self.ralph.setFluidY(self.ralph.getY()+.1)
        if direction == 'dn':
            self.ralph.setFluidY(self.ralph.getY()-.1)
        if direction == 'lf':
            self.ralph.setFluidX(self.ralph.getX()-.1)
        if direction == 'rt':
            self.ralph.setFluidX(self.ralph.getX()+.1)
World()
run()

That wasn’t just a snippet. That was the whole test script. It’s just before the class definition.

I didn’t, but note the line:

self.environ.setCollideMask(P.BitMask32.allOn())

I think so. That’s the whole script.

1 Like

Ah, I see the cTrav instantiation now. I’m sorry I missed it earlier!

I tried out your code, and I think I’ve found the issue. If I remove this line

self.ralph.setScale(.2)

… then everything appears to work correctly. My suggestion is to encapsulate Ralph’s actor in a container node instead of operating on it directly, like so:

self.ralph=NodePath("ralph")
self.ralphActor=Actor.Actor("models/ralph",
            {"run":"models/ralph-run",#animations are not used yet
            "walk":"models/ralph-walk"})
self.ralphActor.setScale(.2)
self.ralphActor.reparentTo(self.ralph)

Then basically leave everything else unchanged, and simply remember that when you are ready to animate, you’ll need to apply animations to ralphActor and not ralph.

In general, the collision algorithm is sensitive to scaling, although this specific case surprises me; I know that spheres behave incorrectly when subjected to non-uniform scaling (because it collapses the sphere into an ellipse), but a ray by any scale should still be a ray. We must not have run into this before because we’ve gotten in the habit of keeping our “visible” nodes (nodes containing geometry you can see) separate from our “logical” nodes (nodes describing collision, position, and heading). Can anyone weigh in with some insight as to what would cause this bizarre jitter effect from simply scaling a ray?

In any case, the scaling / collision issue should be made more clear, I think. I’ll update the manual with a general warning until a better solution can be devised.

Best of luck with the rest of your project!
-Mark