Panda3D Manual: Clicking on 3D Objects
<<prev | top | next>> |
The simplest way to click on 3D objects in Panda is to use very simplistic collision detection coupled with event processing. First, after a CollisonTraverser and a CollisionHandler have been setup, attach a CollisionRay node to the camera. This node will have its `setFromCollideMask()` set to `GeomNode.getDefaultCollideMask()` in order to be as general as possible:
    pickerNode=CollisionNode('mouseRay')
    pickerNP=camera.attachNewNode(pickerNode)
    pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
    pickerRay=CollisionRay()
    pickerNode.addSolid(pickerRay)
    myTraverser.addCollider(pickerNP, myHandler)
For any object that you want to be pickable you should add a flag to it. The easiest way is to use the `setTag()` function, e.g.:
    object1.setTag('myObjectTag', '1')
    object2.setTag('myObjectTag', '2')
The above example sets the tag 'myObjectTag' on two objects in your graph that you want to designate as pickable (we will check for the presence of this tag later, when we get the response back from the collision system). Now assume that the function `myFunction()` is set up to be called for the 'mouse1' event. In `myFunction()` is where you call `pickerRay.setFromLens(origin, destX, destY)`. This makes the ray's origin `origin` and the ray's vector the direction from `origin` to the point (`destX`,`destY`).
    def myFunction():
       #This gives up the screen coordinates of the mouse
       mpos=base.mouseWatcherNode.getMouse()

       #This makes the ray's origin the camera and makes the ray point
       #to the screen coordinates of the mouse
       pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
After this, you now call the traverser like any other collision, get the closest object, and "pick" it.
    def myFunction():
       mpos=base.mouseWatcherNode.getMouse()
       pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

       myTraverser.traverse(render)
       #assume for simplicity's sake that myHandler is a CollisionHandlerQueue
       if myHandler.getNumEntries() > 0:
          myHandler.sortEntries() #this is so we get the closest object
          pickedObj=myHandler.getEntry(0).getIntoNodePath()
Now, the node `pickedObj` returned by the collision system may not be the object itself, but might be just a tiny piece of the object--a wheel, or a nose, or whatever. In particular, it will be one of the GeomNodes that make up the object (the `GeomNode` class contains the visible geometry primitives that are used to define renderable objects in Panda). Since your object might consist of more than one GeomNode, what you probably would prefer to get is the NodePath that represents the parent of all of these GeomNodes--that is, the NodePath that you set the 'myObjectTag' tag on, above. You can use `nodePath.findNetTag()` to return the parent NodePath that contains a specified tag. (There are also other, similar methods on NodePath that can be used to query the tag specified on a parent node, such as `getNetTag()` and `hasNetTag()`. For simplicity we will restrict this example to `findNetTag()`.) Now you can edit `myFunction` to look like:
    def myFunction():
       mpos=base.mouseWatcherNode.getMouse()
       pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())

       myTraverser.traverse(render)
       #assume for simplicity's sake that myHandler is a CollisionHandlerQueue
       if myHandler.getNumEntries() > 0:
          myHandler.sortEntries() #this is so we get the closest object
          pickedObj=myHandler.getEntry(0).getIntoNodePath()
          pickedObj=pickedObj.findNetTag('myObjectTag')
          if not pickedObj.isEmpty():
             handlePickedObject(pickedObj)
<<prev | top | next>> |