Adding pickable objects during runtime

Hi guys,

I’m working on a project that allows the user to place models on terrain. Right now, I can’t seem to be able to detect collisions between the mouse and the objects.

I’m using parts of the code from the Chessboard tutorial. Here are the relevant parts of the code:

class World(DirectObject):
  def __init__(self):
    print "initializing world"
    
    self.keys = {"cube": 0}
    self.mouse = {"holding-obj": 0} 

    #collision detection, from the Chessboard tutorial
    self.picker = CollisionTraverser()           # make a traverser
    self.pq = CollisionHandlerQueue()            # make an handler
    self.pickerNode = CollisionNode('mouseRay')  # make a collision node for our picker ray
    self.pickerNP = camera.attachNewNode(self.pickerNode) # attach that node to the camera since the ray will need to be positioned
      												      # relative to the camera
    self.pickerNode.setFromCollideMask(BitMask32.bit(1))  # everything to be picked will use bit 1. This way if we're detecting other
     											          # collisions we can use other bits
    self.pickerRay = CollisionRay()              # make our collision ray!
    self.pickerNode.addSolid(self.pickerRay)     # add it to the node
    self.picker.addCollider(self.pickerNP, self.pq) # register the ray as something that can cause collisions    

    self.cubes = render.attachNewNode("cubes") #list of objects

    #binds keys to actions
    self.mouseTask = taskMgr.add(self.mouseTask, 'mouseTask')
    self.accept('mouse1', self.testObj)                          # Left click -> if mouse is not holding an object, it will grab the object on the cursor.
    									                         #   If mouse already has something on it, it will attempt to put the object down.   
    self.accept('space', self.setKey, ["cube", 1])               # TEST FUNCTION: Space bar -> Generates a cube

    #Other parts of __init__ are mainly camera controls (using keyboard with base.disableMouse(), and generating the terrain.

When the space bar is pressed, createCube() (which is also in class World) is executed. createCube() generates a cube with random size and places it randomly on the map:

  def createCube(self):
    cube = loader.loadModel("models/box")
    cube.reparentTo(self.cubes) 
    x = random.randint(20,self.width-20)
    z = random.randint(20,self.height-20)
    s = random.randint(0, 15)
    
    cube_coord_lower_left = [float(x), float(self.height_data[self.width*z+x]/16.0-s), float(z)]
    cube_coord_lower_right = [float(x+s), float(self.height_data[self.width*z+x+s]/16.0-s), float(z)]
    cube_coord_upper_left = [float(x), float(self.height_data[self.width*(z-s)+x]/16.0-s), float(z-s)]
    #don't need upper right, since we just need to calculate x-tilt (upper left - lower left) and z-tilt (lower right - lower left)
    cube_tilt = [0, cube_coord_upper_left[1] - cube_coord_lower_left[1], cube_coord_lower_right[1] - cube_coord_lower_left[1]]
    
    cube.setTag('cube', str(self.cube_count))
    cube.setScale(s, s, s)
    cube.setPos(float(x), float(self.height_data[self.width*z+x]/16.0)-s, float(z))  
    cube.setHpr(cube_tilt[0], cube_tilt[1], cube_tilt[2])    #tilts the cube according to the terrain (ie. start and end points of the cube
                                                             #  doesn't really work yet
                                                             # because, it needs the angles in RADIANS. Need to find a way to convert gradient into radians
    #set up cube collision
    bounds = cube.getBounds()
    cSphere = CollisionSphere(bounds.getCenter(), bounds.getRadius())
    cNode = CollisionNode('cube')
    cNode.setIntoCollideMask(BitMask32.bit(1))
    cNode.addSolid(cSphere)
    cube.attachNewNode(cNode)
                                                   
    print self.cube_count

mouseTask is currently empty, as the mouse is checked only when the user clicks the LMB.

  def mouseTask(self, task):
      
    return Task.cont

  def testObj(self):
    if self.mouse["holding-obj"]:
      self.putObj()
    else:
      self.holdObj()

Most of this taken from the Chessboard tutorial. printing self.pickerRay and self.pq are just test functions to see everything is working correctly.

  def holdObj(self):

    if base.mouseWatcherNode.hasMouse():
      mpos = base.mouseWatcherNode.getMouse() #gets mouse position
      
      #Set the position of the ray based on the mouse position
      self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())  
      print self.pickerRay

      if self.mouse["holding-obj"] == 0:
        #self.picker.traverse(self.cube_list)
        self.picker.traverse(self.cubes)
        print self.pq
        if self.pq.getNumEntries() > 0:
          self.pq.sortEntries()
          i = int(self.pq.getEntry(0).getIntoNode().getTag('cube'))
          print i

Sample outputs of print self.pickerRay and print self.pq:

ray, o (0.00562693 1 -0.162556), d (56.2121 998.983 -162.361)

CollisionHandlerQueue, 0 entries:

I’m using Panda 1.4.2. Am I going in the right direction in implementing this, or is there something I missed out?

Thanks.

The result of cube’s bounds center is at cube’s parent coord space. Say the cube is at (1,1,0), its bounds center is (1,1,0), so setting it as CollisionSphere’s origin puts the sphere at (1,1,0) relative to the cube, since you parented the sphere to the cube.
You must want this instead :

    cSphere = CollisionSphere(Point3(0,0,0), bounds.getRadius())

show the sphere to see it :

    cube.attachNewNode(cNode).show()

But still, the cube has non-identity scale, which in turn will be applied to the sphere too.
This should work with cube’s all transform properly :

    #set up cube collision
    bounds = cube.getBounds()
    cNP = self.cubes.attachCollisionSphere('cube',bounds.getCenter(), bounds.getRadius(),
                                           BitMask32.allOff(), BitMask32.bit(1))
    cNP.wrtReparentTo(cube)
    cNP.show()

But I’m not sure if the collision system has been improved with respect to non-uniform scale.

Thanks! I overlooked the fact that the position of the collision sphere is relative to the cube it’s attached to. It works now!

I set it to:

cSphere = CollisionSphere(Point3(0.5,0.5,0.5), 0.8)

So that the center of cSphere is at the center of the cube. The sphere is scaled along with the cube as well.

The code in your second reply doesn’t work as the interpreter says ‘attachCollisionSphere’ takes 8 arguments. Maybe I need an updated version of Panda. In any case, the repositioning of cSphere’s center point should be enough.

Thanks again!