# 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
colorWriter.addData4f( color[0], color[1], color[2], color[3] )

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

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

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

# primitives

primitives = GeomTriangles( Geom.UHStatic )
primitives.closePrimitive()

primitives.closePrimitive()

geom = Geom(vertexData)

node = GeomNode('planeNode')

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.pickerRay=CollisionRay()

#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()

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.

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 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
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()

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

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