Procedural geometric and picking

Hi,

I studied the doc and the demo that shows how to use picking. Seem quite easy, but I have to make picking on procedural generated nodes so I modified the demo to have a minimal test scenario.

This is the code:

import direct.directbase.DirectStart
#for the events
from direct.showbase import DirectObject
#for collision stuff
from pandac.PandaModules import *

def PlaneFactory( _l, _b, _w, _h ):
    
    #creates format
    vertexFormat=GeomVertexFormat.getV3n3c4()
    # now the associated vertex data
    vertexData = GeomVertexData('plane', vertexFormat, Geom.UHStatic)
    
    #vertex writers ....
    vertexWriter = GeomVertexWriter(vertexData, 'vertex')
    normalWriter = GeomVertexWriter(vertexData, 'normal')
    colorWriter = GeomVertexWriter(vertexData, 'color')
    
    color = ( 1, 1, 1, 1 )
    
    vertexWriter.addData3f( _l + _w, _b, 0 ) #A
    normalWriter.addData3f( 0, 0, 1 )
    colorWriter.addData4f( color[0], color[1], color[2], color[3] )

    vertexWriter.addData3f( _l + _w, _b + _h, 0 ) #B
    normalWriter.addData3f( 0, 0, 1 )
    colorWriter.addData4f( color[0], color[1], color[2], color[3] )

    vertexWriter.addData3f( _l, _b + _h, 0 ) #C
    normalWriter.addData3f( 0, 0, 1 )
    colorWriter.addData4f( color[0], color[1], color[2], color[3] )

    vertexWriter.addData3f( _l, _b, 0 ) #D
    normalWriter.addData3f( 0, 0, 1 )
    colorWriter.addData4f( color[0], color[1], color[2], color[3] )

    # primitives
    
    primitives = GeomTriangles( Geom.UHStatic )
    primitives.addVertices( 0, 1, 3 )
    primitives.closePrimitive()
    
    primitives.addVertices( 1, 2, 3 )
    primitives.closePrimitive()
    
    geom = Geom(vertexData)
    geom.addPrimitive(primitives)

    node = GeomNode('planeNode')
    node.addGeom(geom)

    return node    

class Picker(DirectObject.DirectObject):
    def __init__(self):
      #setup collision stuff
        
        self.picker= CollisionTraverser()
        base.cTrav = self.picker
        self.queue=CollisionHandlerQueue()
        self.pickerNode=CollisionNode('mouseRay')
        self.pickerNP=camera.attachNewNode(self.pickerNode)
        self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
        self.pickerRay=CollisionRay()
        self.pickerNode.addSolid(self.pickerRay)
        self.picker.addCollider(self.pickerNP, self.queue)
        
        #this holds the object that has been picked
        self.pickedObj=None
        self.accept('mouse1', self.printMe)
        
   #this function is meant to flag an object as being somthing we can pick
    def makePickable(self,newObj):
        newObj.setTag('pickable','true')

   #this function finds the closest object to the camera that has been hit by our ray
    def getObjectHit(self, mpos): #mpos is the position of the mouse on the screen
      self.pickedObj=None #be sure to reset this
      self.pickerRay.setFromLens(base.camNode, mpos.getX(),mpos.getY())
      self.picker.traverse(render)
      if self.queue.getNumEntries() > 0:
         self.queue.sortEntries()
         self.pickedObj=self.queue.getEntry(0).getIntoNodePath()

         parent=self.pickedObj.getParent()
         self.pickedObj=None

         while parent != render:
            if parent.getTag('pickable')=='true':
               self.pickedObj=parent
               return parent
            else:
               parent=parent.getParent()
      return None

    def getPickedObj(self):
        return self.pickedObj

    def printMe(self):
        self.getObjectHit( base.mouseWatcherNode.getMouse())
        print self.pickedObj

mousePicker=Picker()

#load thest models
panda=loader.loadModel('panda')
teapot=loader.loadModel('teapot')
box=loader.loadModel('box')

basePlane = PlaneFactory( 0, 0, 100, 100)
planeNode = NodePath(basePlane)


#put them in the world
panda.reparentTo(render)
panda.setPos(camera, 0, 50,0)

teapot.reparentTo(render)
teapot.setPos(panda, -30, 0, 0)

box.reparentTo(render)
box.setPos(panda, 30,0,0)

planeNode.reparentTo(render)
planeNode.setPos( panda, 0,0,-10)

mousePicker.makePickable(panda)
mousePicker.makePickable(teapot)
mousePicker.makePickable(box)
mousePicker.makePickable(planeNode)

run()

If you are familiar with the code, each picking action causes the name of the model to be printed. After adding the procedure generated plane, picking keeps working for the loaded models, but fails on the plane, causing ‘None’ to be printed. After doing some debug, I found that the queue is empty after the traverse operation. This is funny. Seems like the world is unable to recognize the plane at all.

Thanks for your help
Omar

First, there is difference in scene graph hierarchy of loaded models and your procedural plane. The root (top parent) of any loaded model (before attached to scene graph) is ModelRoot, while your plane’s root is itself (GeomNode).
Add this at the end of makePickable :

        print '======='
        newObj.ls()

to see where exactly you set the tag.

Hi,

Did what you said. This is the output:


=======
ModelRoot panda.egg [pickable] T:(pos 0 50 0)
  Character panda_soft
    GeomNode  (6 geoms: CullFaceAttrib TextureAttrib TransparencyAttrib)
=======
ModelRoot teapot.egg [pickable] T:(pos -30 50 0)
  PandaNode teapot
    GeomNode body (1 geoms)
    GeomNode lid (1 geoms)
    GeomNode handle (1 geoms)
    GeomNode spout (1 geoms)
=======
ModelRoot box.egg [pickable] T:(pos 30 50 0)
  GeomNode box (1 geoms: TextureAttrib)
=======
GeomNode planeNode (1 geoms) [pickable] T:(pos 0 50 -10)

You’re right. My procedural plane doesn’t have a ModelRoot root parent!. This is necessary for picking to work?. How I put my model into a ModelRoot?

::omar

I had this problem before, but it was relatively easy to fix… You can then get the name of any model (provided you set the name earlier) or any geometry. I set the names at runtime from a script that defines the world and the objects that it contains but you could do it any way you like…

heres the jist of it - please note the coding is TERRIBLE. Its extremely convoluted; but it works… If you can modify it and post back that would be great :slight_smile: As for the small functions ive used, they do exactly what they say and are easy to implement, but if you need them just say and ill post it. Please ignore the stuff about surfaces as that’s my program specific code:

def selectObject(self, task):
        """
        This task deals with the selecting of objects within the Virtual world (by clicking/targeting them)
        """
        if self.mode == ROAM_MODE:
            #Check to see for a click
            #if (self.mousebtn[0]):
            self.selectedObject = None
            self.selected = 'None'
            self.selection = False
            #Set the position of the ray from the centre of the viewpoint
            self.pickerRay.setFromLens(base.camNode, 0, 0)
            self.picker.traverse(render)
            if self.pq.getNumEntries() > 0:
                #if we have hit something, sort the hits so that the closest
                #is first, and get that node
                self.pq.sortEntries()
                num = 0
                itemNum =0
                itemFound = False
                chosen = False

                for x in range(self.pq.getNumEntries()):
                    if chosen == False:
                        num = x
                        chosen = True
                    else:
                        name = getParentName(self.pq.getEntry(x).getIntoNodePath().getParent())
                        for item in self.takable:
                            if item.getName() == name:
                                itemNum = x
                                itemFound = True
                                break

                if self.mousebtn[0]:
                    if itemFound == True:
                        self.selectedObject = self.pq.getEntry(itemNum).getIntoNodePath()
                    else:
                        self.selectedObject = self.pq.getEntry(num).getIntoNodePath()

                    parent=self.selectedObject.getParent()

                    self.selectedObject=None




                    while parent != render:

                        if parent.getParent() == render:
                            self.selectedObject = parent
                            self.selection = True
                            self.selected = parent.getName()

                            #sZ = base.camera.getZ()
                            #if self.selected[len(self.selected)-7:len(self.selected)] == 'Surface':
                            try:
                                sZ = self.objects[self.selected].getZ()
                            except KeyError:    #perhaps selected an unsavory item?
                                break
                            # set pointX,pointY,pointZ if selecting a surface
                            #Gets the point described by pickerRay.getOrigin(), which is relative to
                            #camera, relative instead to render
                            nearPoint = render.getRelativePoint(camera, self.pickerRay.getOrigin())
                            #Same thing with the direction of the ray
                            nearVec = render.getRelativeVector(camera, self.pickerRay.getDirection())
                            pos = PointAtZ(sZ, nearPoint, nearVec)
                            self.holder.setPos(pos)

                            self.pointX = self.holder.getX()
                            self.pointY = self.holder.getY()
                            self.pointZ = self.holder.getZ()
                            #2
                            break
                        else:
                            parent=parent.getParent()

                else:
                    if itemFound == True:
                        so = self.pq.getEntry(itemNum).getIntoNodePath()
                    else:
                        so = self.pq.getEntry(num).getIntoNodePath()

                    #parent=so.getParent()
                    #so=None
                    parent = so


                    while parent != render:
                        if parent.getParent() == render:
                            so = parent
                            self.highlighted = parent.getName()
                            break
                        else:
                            parent=parent.getParent()

        return Task.cont

at the end of it all self.selectedObject contains the object and I just use a getName type method (to get the name I set earlier)

EDIT:

You probably need this too :slight_smile:

def getParentName(parent):
    while parent != render:
        if parent.getParent() == render:
            name = parent.getName()
            return name
        else:
            parent=parent.getParent()