Hello Pandaites!
I tried to find the mysterious Picker.py cited in these forums, and without luck, I decided to write my own. It’s not much, but this straightforward mouse picking class has helped me quite a bit with 3d guis and in-game interaction because it’s so simple to use.
I use a node path instead of collision bits to define the “pickables” I want to search, and create one traverser per picker to keep things separate. Works well for my simple uses.
It can be adjusted to return more information than just node picked and surface point (play with pick()).
Test it with “ppython Picker.py” if you like.
"""
Generic 3d object picker class for Panda3d.
"""
from pandac.PandaModules import *
class Picker(object):
"""
Generic object picker class for Panda3d. Given a top Node Path to search,
it finds the closest collision object under the mouse pointer.
Picker takes a topNode to test for mouse ray collisions.
the pick() method returns (NodePathPicked, 3dPosition, rawNode) underneath the mouse position.
If no collision was detected, it returns None, None, None.
'NodePathPicked' is the deepest NAMED node path that was collided with, this is
usually what you want. rawNode is the deep node (such as geom) if you want to
play with that. 3dPosition is where the mouse ray touched the surface.
The picker object uses base.camera to collide, so if you have a custom camera,
well, sorry bout that.
pseudo code:
p = Picker(mycollisionTopNode)
thingPicked, positionPicked, rawNode = p.pick()
if thingPicked:
# do something here like
thingPicked.ls()
"""
def __init__(self, topNode, cameraObject = None):
self.traverser = CollisionTraverser()
self.handler = CollisionHandlerQueue()
self.topNode = topNode
self.cam = cameraObject
pickerNode = CollisionNode('MouseRay')
#NEEDS to be set to global camera. boo hoo
self.pickerNP = base.camera.attachNewNode(pickerNode)
# this seems to enter the bowels of the node graph, making it
# difficult to perform logic on
#pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
self.pickRay = CollisionRay()
pickerNode.addSolid(self.pickRay)
self.traverser.addCollider(self.pickerNP, self.handler)
def setTopNode(self, topNode):
"""set the topmost node to traverse when detecting collisions"""
self.topNode = topNode
def destroy(self):
"""clean up my stuff."""
self.ignoreAll()
# remove colliders, subnodes and such
self.pickerNP.remove()
self.traverser.clearColliders()
def pick(self):
"""
pick closest object under the mouse if available.
returns ( NodePathPicked, surfacePoint, rawNode )
or (None, None None)
"""
if not self.topNode:
return None, None, None
if base.mouseWatcherNode.hasMouse():
mpos = base.mouseWatcherNode.getMouse()
self.pickRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
self.traverser.traverse(self.topNode)
if self.handler.getNumEntries() > 0:
self.handler.sortEntries()
picked = self.handler.getEntry(0).getIntoNodePath()
thepoint = self.handler.getEntry(0).getSurfacePoint(self.topNode)
return self.getFirstParentWithName(picked), thepoint, picked
return None, None, None
def getFirstParentWithName(self, pickedObject):
"""
return first named object up the node chain from the picked node. This
helps remove drudgery when you just want to find a simple object to
work with. Normally, you wouldn't use this method directly.
"""
name = pickedObject.getName()
parent = pickedObject
while not name:
parent = parent.getParent()
if not parent:
raise Exception("Node '%s' needs a parent with a name to accept clicks." % (str(pickedObject)))
name = parent.getName()
if parent == self.topNode:
raise Exception("Collision parent '%s' is top Node, surely you wanted to click something beneath it..." % (str(parent)))
return parent
if __name__ == "__main__":
# test code
# use the pview-style camera controls to wander about, then click
# happy spheres.
# most of this code is about setting up the scene;
# skip to the end for the four picker lines
from pandac.PandaModules import *
import direct.directbase.DirectStart
def breedOnClick():
"""this is called when mouse1 is pressed."""
global picker
namedNode, thePoint, rawNode = picker.pick()
if namedNode:
# breed smilies for fun (well, 'fun' if you're slap happy)
name = namedNode.getName()
p = namedNode.getParent() # the visible smiley
pos = p.getPos()
# stick a smiley clone on this smiley
# at the click point
clonePos = (thePoint - pos)*2 + pos
newguy = model.copyTo(render)
newguy.setPos(clonePos)
newguy.find("**/smileyCollide").setName("the clone of %s" % (name))
print namedNode.getName()
print "Collision Point: ", thePoint
namedNode.ls()
def rolloverTask(task):
"""
an alternate way to use the picker.
Handle the mouse continuously
"""
global rollover
obj, point, raw = rollover.pick()
if obj:
dt = globalClock.getDt()
p = obj.getParent() # obj is collision sphere, so rotate model as well
p.setH(p.getH() + dt*20)
return task.cont
# mkay lets create some smiley clickables
model = loader.loadModel("models/smiley")
# set up the collision body
min,macks= model.getTightBounds()
radius = max([macks.getY() - min.getY(), macks.getX() - min.getX()])/2
cs = CollisionSphere(0,0,0, radius)
csNode = model.attachNewNode(CollisionNode("smileyCollide"))
csNode.node().addSolid(cs)
# create smiley first-strike army
for x in range(0, 9):
for y in range(0,9):
nodep = model.copyTo(render)
realx, realy = (-50 + x*10, -50 + y * 10 )
colliderNP = nodep.find("**/smileyCollide")
colliderNP.setName("the happy dude at %i, %i" % (realx, realy))
# colliderNP.show()
nodep.setPos(realx, realy, 0 )
# remove the ugly
light = DirectionalLight("prettify")
lnp = render.attachNewNode(light)
render.setLight(lnp)
lnp.setHpr(0,-45,0)
# wait for it... WAIT FOR IT...
picker = Picker(render)
base.accept("mouse1", breedOnClick)
rollover = Picker(render)
taskMgr.add(rolloverTask, 'rollover')
run()