Ralph not roaming

Hello,

I wanted to change out the environment that Ralph walks around in, but when I drop in a different environment (made in Maya), he just stays in the middle of the screen. What’s going on? Is the it the collision detection system, and if so, what’s special about the Ralph world model that I need to do in Maya for it to use a different terrain/world.

Thanks,

Brian

Firstly, in Maya you must give the ground object a specific name, such as ‘ground’ and then in Panda you must find that object by it’s name (‘ground’) and set it as into-node for collision checking. Make sure to keep the name in the collision-response-part exactly the same. You must treat the other objects for collision detection this way.

God bless you
JO

Hello,
this is my first post so be gentle :slight_smile:
I have run into the same problem ( i chose to post here because i don’t want to clutter the forums with the same stuff that has been adressed before) and it would seem i am not yet comfortable enough with collision detection to manage solving this on my own or even after reading this post :slight_smile:

I have too changed the environment in the roaming ralph sample and colision detection is not working on it
this is exactly what i have tried:

i built a simple plane object in max and named it terrain
edited the .egg so it contains the following:

<Group> terrain {
      <Collide> terrain { Polyset keep descend }
      <VertexPool> terrain.verts {

i’ll put the code from the sample here for easy access:

self.cTrav = CollisionTraverser()
        self.environ.setCollideMask(BitMask32.bit(1))

        self.ralphGroundRay = CollisionRay()
        self.ralphGroundRay.setOrigin(0,0,1000)
        self.ralphGroundRay.setDirection(0,0,-1)
        self.ralphGroundCol = CollisionNode('ralphRay')
        self.ralphGroundCol.addSolid(self.ralphGroundRay)
        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(1))
        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
        self.ralphGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)

and the code for the collision check:

self.cTrav.traverse(render)

        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
        # update his Z. If it hit anything else, or didn't hit anything, put
        # him back where he was last frame.

        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.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"):
            self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
        else:
            self.ralph.setPos(startpos)

so i kept the name terrain and i thought it should work but i guess i was wrong
i have no ideea what i’m missing

wouldn’t leaving out “keep” not render the object? well i left it out and the plane is still getting rendered
so please if you know what i’m doing wrong help me as i really am stuck on collision detection for now and even if i mainly understand how it works in theory, i can’t get it to work if it kills me :laughing:

Thanks,
Yonn

Exacty right–leaving out “keep” would not render the object. Since it is still getting rendered without “keep”, this makes me suspect you are not loading the model you think you are. For instance, maybe you are still loading the original egg file, or maybe you have inadvertently put the entry within a group that does not actually contain the ground terrain.

David

i wish it were that simple but i am loading the file testterrain.egg which is a simple untextured plane and when i added … i have overwriten the original testterrain.egg. This is how it looks wihout “keep” and i also enabled the collision visual representations to show. Ralph is not moving an inch:

and also if ralph is high above ground he is not being positioned on the ground as he would be if the colision would work. :confused: help
maybe i have to add something else in the code that would have been added to ralph’s world… but since those files are egg.pz and bam.pz i can’t possibly know

Thanks,
Yonn

Looking at the visualization, everything seems ok, the red rectangle (where collision takes place) position is logically correct. So obviously there was collision with the terrain, and ralph’s position was make sense. What did you exactly mean by not moving ? If you didn’t alter the original code, then it should be fine. Try to not update ralph’s position :


self.ralph.setPos(startpos)

Hey, I found the real problem.
I’ve tried to load my own environment model, and edited the egg as you did. The result was true, ralph didn’t roam.
After I checked the collision result, I found that the entries[0] is not the ‘terrain’. I believe the harmful thing is the egg editing, so you better leave your original egg. In fact, the terrain has been set for collision by the code :


      self.environ.setCollideMask(BitMask32.bit(1))

It means ALL (visible) objects of the environment model are set for collision. If you doubt it, you can try to check the collision detection results, both by editing the egg and otherwise.

        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.getEntry(i)
            print '('+entry.getIntoNode().getName()+')'
            entries.append(entry)

You will see the into-node name within the brackets. If you edited the egg, you will get some into-nodes, the ‘terrain’ and a blank node name, and guess what, after the sort process, the blank one will be considered as the topmost surface, I don’t know why. The result after using the built-in method sortEntries() instead of the original code :


    self.ralphGroundHandler.sortEntries()

will be the same.
I ever done this stuff before (with editing the egg) using the old Panda version and it ran well . May be there are changes in the later versions ?
For me, it’s a LOT more convenient without touching the egg, and set the into-node by code using find() method, especially when the egg file is huge.

Well, IMO the collision detection in this tut is a bit silly. It only uses the collision ray, without any collision solid wrapping ralph’s body. This will start look so ridiculous when ralph moves under any object, such as the tree branches. If the ray hits a tree branch, ralph will simply stop, obviously the tree branch shouldn’t obstructing ralph’s movement. It’s because the ray’s origin was put so high :


        self.ralphGroundRay.setOrigin(0,0,1000)

You can try to set the origin at lower position, say (0,0,10), or you can add a collision sphere to wrap ralph’s body and it should work better than the ray against much more obstacles.

Good luck !
JO

that is exactly what happens


if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
        self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
    else:
        self.ralph.setPos(startpos)

the - entries[0].getIntoNode().getName() == “terrain” - condition is never met because all into-node names are blank so it just keeps ralph in the same position
thank you for taking the time to look into this
i guess now when i’ll write my own colision detection code for my game i’ll steer clear of the CollisionHandlerQueue and go with events (gotta look into that :smiley:) because i still don’t understand why this happens :slight_smile: but i will experiment some more

Thanks,
Yonn

EDIT: I have just figured it out :smiley: (sort of)
{ 1 }
“Scene Root” {
… <- this i kept
}
just after submiting this i decided to take another look and found out that it really was the egg that caused the problem. in my case i deleted what i wrote above and it would explain how deleting a group from the egg could make the blank entries go away but it wouldn’t work without deleting what is that for anyway?
so now entries are really “terrain” and everything works fine :smiley:

indicates that the egg file contains an animated character with joints, not an environment or terrain. Collision surfaces are not well-defined in files.

Arguably, there should be a better error condition for this kind of mistake–it should have complained from the beginning that you were trying to load a collision surface within a file. But there isn’t right now. :frowning:

David

So what you are saying is that you can’t have collision solids in an egg containing an actor.
is it possible to attach a colision solid to a joint though? (i don’t see why not but it’s better to be sure before i start banging my head against the walls :slight_smile:) if not, i’ll have to rethink some aspects of my game…

Yonn

Yes, this is certainly possible. To do this, you simply need to expose the joint (I believe this is in the manual) and parent the collision solids to the exposed joint node.

David

I have been fighting with this thing all day long, and this is what I found.
Max 7 egg export looks different from the supplied egg that was exported with maya.

First off, even if you set the max exporter to model and have nothing else but the terrain object in your scene, the egg description will still contain a entry.
Secondly, if your max scene contains 2 models (one for terrain and one for start_point) the max exporter will put the start_point object in a hierarchy as a child, while the example code is EXPLICITELY searching for it as a seperate group.

Ultimately I found that I couldnt easily just swap eggs, because what is described in a Max egg is different to what is described in a Maya egg (which is wierd)

using a text editor I was able to correct the hierarchy, had i exported from maya, i probably never would have noticed the difference

hopes this helps

Although I know nothing about MaxEgg, note that, in the Maya converter and in other converters, converting a file as animation type “model” means that it is to have the entry. You must convert it as animation type “none” to avoid the entry.

If the MaxEgg converter only offers “model” and “anim” as options for the animation type, then it may be an oversight on the part of the gui designers, since there also need to be at least a third option, for animation type “none”.

David

Thanks for your reply.

Interestingly, the max exporter doesn’t seem to have a ‘none’ setting.
This picture shows the exporters default setting for just 1 object:

In other words, whatever setting you’ll choose, it looks like a dart setting will end up in any Max egg.

Ah, I think the omission of “None” is a mistake. But it’s just been pointed out to me that you could use “Pose” to achieve the same effect, converting a model without the flag.

David

Just an opinion, logically thinking, don’t the ‘Model’ option should be the one exporting the mesh only ?
So, the ‘Pose’ option should be the one exporting the mesh ready for animation ?

Yes, the names of the animation options are confusing. What’s making it particularly confusing here is the fact that the MaxEgg designers called this field “Export Type” instead of its intended name, “Animation Type”.

The original design was that there are several types of animation export for all egg exporters, but primarily there are “Model” and “Animation”, which are based on Panda’s convention of defining an Actor as a pairing of a single “model” egg file and one or more associated “animation” egg files.

Thus, animation types “Model” and “Animation” are designed to write out an animatable model, and its associated animation channels, respectively. Animation type “Both” puts both model and animation into the same egg file, which Panda also supports, but this is less often used (because it isn’t as flexible). There are also a handful of rarely-used animation types like “Pose”, which is intended to export out a particular frame of the animation in one static (unanimated) pose, and “Strobe”, which exports out a range of poses under a switch node, to achieve a very CPU-light (but memory-heavy) form of animation by completely replacing the model every frame.

Finally, there is supposed to be animation type “None”, which means to export out the model exactly as it is, without any animation at all. This is the intended way to export environments, scenes, and props that are to be loaded directly by loader.loadModel(), rather than via the Actor interface. It is supposed to be the default animation type if the user does not specify one of the others explicitly.

I guess the people who designed the MaxEgg exporter were a little confused by this selection of options. In particular, they appear to have noticed a similarity between “Pose” and “None”, and concluded that both were not necessary. And, like many people, they may have been confused by the unfortunate naming of animation type “Model”, which is intended to refer specifically to the animatable model and its associated hierarchy of joints, but is often misunderstood to mean any environment or unanimated model.

David

Cool!

Thanks very much for your replies David, giving ralph a new environment to roam in now works instantly!

thanks.

I’m going to bump this topic ^^

I am having the same porblem with the files. I think.
Ralph moves on the spot, but not along the terrain.

I would of thought that if the detection was stopping him going along the model, it should also stop his walking animation?

Im complelty stuck, if anyone could throw some information my way it would help :slight_smile:

Ok ralph is just stuck on the spot, he will still play the run animation though.
Just to see if it was the land, i rotated it away from him and attempted to run away. Nothing.

Here is the code i am using.

import direct.directbase.DirectStart
from pandac.PandaModules import CollisionTraverser,CollisionNode
from pandac.PandaModules import CollisionHandlerQueue,CollisionRay
from pandac.PandaModules import Filename
from pandac.PandaModules import PandaNode,NodePath,Camera,TextNode
from pandac.PandaModules import Vec3,Vec4,BitMask32
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.task.Task import Task
from direct.showbase.DirectObject import DirectObject
import random, sys, os, math
SPEED = 0.5

# Figure out what directory this program is in.
MYDIR=os.path.abspath(sys.path[0])
MYDIR=Filename.fromOsSpecific(MYDIR).getFullpath()

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
			pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
	                pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

class World(DirectObject):
    def __init__(self):
        
        self.keyMap = {"left":0, "right":0, "forward":0, "cam-left":0, "cam-right":0}
        base.win.setClearColor(Vec4(0,0,0,1))

        # Post the instructions

        self.title = addTitle("Engine testing")
        self.inst1 = addInstructions(0.95, "[ESC]: Quit")
        self.inst2 = addInstructions(0.90, "[Left Arrow]: move him")
        self.inst3 = addInstructions(0.85, "[Right Arrow]: move him")
        self.inst4 = addInstructions(0.80, "[Up Arrow]: move him")
        self.inst6 = addInstructions(0.70, "[A]: Rotate Camera Left")
        self.inst7 = addInstructions(0.65, "[S]: Rotate Camera Right")
        
        # Set up the environment
        
        self.environ = loader.loadModel ("models/testing")
        self.environ.reparentTo(render)
        self.environ.setPos(-20,30,-20) 
        self.environ.setHpr(30, 30, 30) 

                #load in lead character
        ralphStartPos = self.environ.find("**/start_point").getPos()
        self.ralph = Actor(MYDIR+"models/ralph",
                      {"run":MYDIR+"models/ralph-run",
                       "walk":MYDIR+"models/ralph-walk"})
        self.ralph.reparentTo(render)
        self.ralph.setScale(.2)
        self.ralph.setPos(ralphStartPos)
        
        #create floter object
        self.floater = NodePath(PandaNode("floater"))
        self.floater.reparentTo(render)
        
        #keyinput
        self.accept("escape", sys.exit)
        self.accept("arrow_left", self.setKey, ["left",1])
        self.accept("arrow_right", self.setKey, ["right",1])
        self.accept("arrow_up", self.setKey, ["forward",1])
        self.accept("a", self.setKey, ["cam-left",1])
        self.accept("s", self.setKey, ["cam-right",1])
        self.accept("arrow_left-up", self.setKey, ["left",0])
        self.accept("arrow_right-up", self.setKey, ["right",0])
        self.accept("arrow_up-up", self.setKey, ["forward",0])
        self.accept("a-up", self.setKey, ["cam-left",0])
        self.accept("s-up", self.setKey, ["cam-right",0])

        taskMgr.add(self.move,"moveTask")
        
        # Game state variables
        self.prevtime = 0
        self.isMoving = False

        # Set up the camera
        
        base.disableMouse()
        base.camera.setPos(self.ralph.getX(),self.ralph.getY()+10,2)
        
        # Collision
        
        self.cTrav = CollisionTraverser()
        self.environ.setCollideMask(BitMask32.bit(1))

        self.ralphGroundRay = CollisionRay()
        self.ralphGroundRay.setOrigin(0,0,1)
        self.ralphGroundRay.setDirection(0,0,1)
        self.ralphGroundCol = CollisionNode('ralphRay')
        self.ralphGroundCol.addSolid(self.ralphGroundRay)
        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(1))
        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
        self.ralphGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)

        self.camGroundRay = CollisionRay()
        self.camGroundRay.setOrigin(0,0,1000)
        self.camGroundRay.setDirection(0,0,-1)
        self.camGroundCol = CollisionNode('camRay')
        self.camGroundCol.addSolid(self.camGroundRay)
        self.camGroundCol.setFromCollideMask(BitMask32.bit(1))
        self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol)
        self.camGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
        
        #Records the state of the arrow keys
    def setKey(self, key, value):
        self.keyMap[key] = value
    

    # Accepts arrow keys to move either the player or the menu cursor,
    # Also deals with grid checking and collision detection
    def move(self, task):

        elapsed = task.time - self.prevtime

        # If the camera-left key is pressed, move camera left.
        # If the camera-right key is pressed, move camera right.

        base.camera.lookAt(self.ralph)
        camright = base.camera.getNetTransform().getMat().getRow3(0)
        camright.normalize()
        if (self.keyMap["cam-left"]!=0):
            base.camera.setPos(base.camera.getPos() - camright*(elapsed*20))
        if (self.keyMap["cam-right"]!=0):
            base.camera.setPos(base.camera.getPos() + camright*(elapsed*20))
            
            # save ralph's initial position so that we can restore it,
        # in case he falls off the map or runs into something.

        startpos = self.ralph.getPos()

        # If a move-key is pressed, move ralph in the specified direction.

        if (self.keyMap["left"]!=0):
            self.ralph.setH(self.ralph.getH() + elapsed*300)
        if (self.keyMap["right"]!=0):
            self.ralph.setH(self.ralph.getH() - elapsed*300)
        if (self.keyMap["forward"]!=0):
            backward = self.ralph.getNetTransform().getMat().getRow3(1)
            backward.setZ(0)
            backward.normalize()
            self.ralph.setPos(self.ralph.getPos() - backward*(elapsed*5))

        # If ralph is moving, loop the run animation.
        # If he is standing still, stop the animation.

        if (self.keyMap["forward"]!=0) or (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0):
            if self.isMoving is False:
                self.ralph.loop("run")
                self.isMoving = True
        else:
            if self.isMoving:
                self.ralph.stop()
                self.ralph.pose("walk",5)
                self.isMoving = False

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

        camvec = self.ralph.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)

        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
        # update his Z.

        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.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"):
            self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
        else:
            self.ralph.setPos(startpos)

        # Keep the camera at one foot above the terrain,
        # or two feet above ralph, whichever is greater.
        
        entries = []
        for i in range(self.camGroundHandler.getNumEntries()):
            entry = self.camGroundHandler.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.ralph.getZ() + 2.0):
            base.camera.setZ(self.ralph.getZ() + 2.0)
            
        # The camera should look in ralph's direction,
        # but it should also try to stay horizontal, so look at
        # a floater which hovers above ralph's head.
        
        self.floater.setPos(self.ralph.getPos())
        self.floater.setZ(self.ralph.getZ() + 2.0)
        base.camera.lookAt(self.floater)

        # Store the task time and continue.
        self.prevtime = task.time
        return Task.cont    
W = World()
run()

One thing you should know, that the entry name returned by CollisionHandlers is the name of the geomnode, i.e. the mesh’s name. So, you should name your terrain mesh (not it’s parent or group) as “terrain” to get it work with the original Ralph code. You can try add the following code to the original code & environment. The original lilSmiley’s mesh name is “poly”, and I changed it to “terrain”. Try to comment the name changing line, and read the output on the console.

        lilSmiley=loader.loadModel('misc/lilSmiley')
        lilSmiley.reparentTo(render)
        lilSmiley.setScale(10)
        lilSmiley.setAlphaScale(.4)
        lilSmiley.setPosHpr(-110,10,1, 180,-70,0)
        lilSmiley.setCollideMask(BitMask32.bit(1))
        lilSmileygeom=lilSmiley.find('**/poly')
        lilSmileygeom.setName('terrain')
        lilSmiley.ls()