Rectangular selection of arbitrary NodePaths

My Panda world has a bunch of different 2D-ish objects, some of which are rectangular and some of which are round.

I’d like to be able to drag-select multiple objects, and found the code ynjh_jo wrote to do just that.

My problem is that the code in that post surrounds each pickable object with a CollisionSphere. I would like to use a CollisionPlane instead of CollisionSphere if possible to match the usually rectangular shape of objects.

Class constructor:

self.nullClickPos = None
# credit: https://discourse.panda3d.org/t/drag-selecting-multiple-nodes/2967/1
self.collTrav = CollisionTraverser('selector')
self.selHandler = CollisionHandlerQueue()
CM = CardMaker('selectionRectangle')
CM.setFrame(0, 1, 0, 1)
self.rect = render2d.attachNewNode(CM.generate())
self.rect.setColor(0, 1, 1, 0.2)
self.rect.setTransparency(1)
self.rect.hide()
LS = LineSegs()
LS.moveTo(0, 0, 0)
LS.drawTo(1, 0, 0)
LS.drawTo(1, 0, 1)
LS.drawTo(0, 0, 1)
LS.drawTo(0, 0, 0)
self.rect.attachNewNode(LS.create())
self.rectTask = None

On object creation:

radius = newNodePath.getBounds().getRadius()
cnp = newNodePath.attachCollisionSphere('cplane', 0, 0, 0, radius, BitMask32.bit(1), BitMask32.allOff())
self.collTrav.addCollider(cnp, self.selHandler)

On mouse down:

def begin_rect_select(self):
    self.unselect_all() # unhighlights all objs
    self.nullClickPos = Point2(base.mouseWatcherNode.getMouse())
    self.rect.setPos(self.nullClickPos[0], 1, self.nullClickPos[1])
    self.rect.show()
    self.rectTask = taskMgr.add(self.update_rect, 'update_rect')

def update_rect(self, tsk):
    # credit: https://discourse.panda3d.org/t/drag-selecting-multiple-nodes/2967/1
    if not self.panda.mouse.has():    # make sure mouse is in panda window
        return Task.cont
    d = Point2(base.mouseWatcherNode.getMouse()) - self.nullClickPos
    self.rect.setScale(d[0] if d[0] else 1e-3, 1, d[1] if d[1] else 1e-3)
    return Task.cont

On mouse up:

def end_rect_select(self):
    if self.nullClickPos is None or self.rectTask is None:
        return
    taskMgr.remove(self.rectTask)

    # credit: https://discourse.panda3d.org/t/drag-selecting-multiple-nodes/2967/1
    bmin, bmax = self.rect.getTightBounds()
    clickBL = Point2(bmin[0], bmin[2])
    clickTR = Point2(bmax[0], bmax[2])
    if clickTR == clickBL:  # fudge the numbers a bit to avoid the degenerate case of no rectangle
        clickTR = Point2(clickTR[0] + 0.00001, clickTR[1] + 0.00001)
    pt = Point3()
    llF = Point3()
    urF = Point3()
    base.camLens.extrude(clickBL, pt, llF)
    base.camLens.extrude(clickTR, pt, urF)

    ulF = Point3(llF[0], llF[1], urF[2])
    brF = Point3(urF[0], urF[1], llF[2])

    camOrigin = Point3(0)
    left = CollisionPlane(Plane(camOrigin, ulF, llF))   # create the 4 sides with planes
    right = CollisionPlane(Plane(camOrigin, brF, urF))  # they should all 'face' OUTward: i.e.
    bot = CollisionPlane(Plane(camOrigin, llF, brF))    # collisions are INSIDE
    top = CollisionPlane(Plane(camOrigin, urF, ulF))
    pyramid = camera.attachNewNode(CollisionNode('pyramid'))
    pyramid.node().addSolid(left)
    pyramid.node().addSolid(top)
    pyramid.node().addSolid(right)
    pyramid.node().addSolid(bot)

    # check for collisions
    self.collTrav.traverse(render)
    hits = []
    for i in range(self.selHandler.getNumEntries()):
        hitNp = self.selHandler.getEntry(i).getFromNodePath()
        print 'testing', hitNp
        hits.append(hitNp)
    print hits

    # if the object is within the rectangle, it collides with all 4 planes.
    self.select(filter(lambda i: hits.count(i) == 4, hits))
    self.selHandler.clearEntries()
    pyramid.removeNode()
    self.rect.hide()
    self.nullClickPos = None

Thank you for any suggestions!

I ended up just placing a small CollisionSphere at the center of each NodePath I wanted to enable selection on. It means the user has to drag over the center of an object to select it, but it does get the job done.