Mouse ray collision with multiple cameras/viewports

Hi all!

I came across this awesome tutorial about collisions: Panda3d Collisions made simple

I added a CollisionHandlerEvent like in step5/step6 of the tutorial.

self.colHandler = CollisionHandlerEvent()
id = randrange(1000)
self.pickerNode = CollisionNode('mouseray')
self.pickerNP = self.camera.attachNewNode(self.pickerNode)
self.pickerRay = CollisionRay()
self.pickerNode.addSolid(self.pickerRay)
self.pickerNode.setFromCollideMask(BitMask32.bit(0))
base.cTrav.addCollider(self.pickerNP, self.colHandler)

self.colHandler.addInPattern('%fn-into-%in')
self.colHandler.addOutPattern('%fn-out-%in')

self.accept('mouseray-into-collider' ,self.collideEventIn)
self.accept('mouseray-out-collider', self.collideEventOut)

###### collider code
self.collider = self.model.attachNewNode(CollisionNode('collider'))
self.collider.node().addSolid(CollisionSphere(0, 0, 0, 1))

######
def updateMouseRay(self):
        if self.mouseWatcher.hasMouse():
            mpos = self.mouseWatcher.getMouse()
            self.pickerRay.setFromLens(self.camera.node(), mpos.getX(), mpos.getY())
            self.pickerNode.addSolid(self.pickerRay)

It works well if I only use one camera in the scene. But my program supports multiple viewports into the same world (splitscreen). So basically, updateMouseRay is called for each step for each viewport but only if the mouse is within the viewportā€™s display region.
If I do not change the collision nodeā€™s name for each new viewport, it works for one part of the splitscreen but as one would expect, not for the other one (depending on which one you move the mouse first). As soon as the mouse comes across the other viewport it infinitely throws the following error and finally crashes. What does this error message tell me?

:mathutil(warning): BoundingLine::contains_geometric() called with BoundingLine

Assuming that it would surely be a problem to have multiple collision nodes with the same name, the next idea was to try to uniquely name those by adding an id in the end for each camera (e.g. self.pickerNode = CollisionNode(ā€˜mouseray%dā€™%id) and accordingly for the accept methods). But this throws the following error as soon as I move the mouse into the window:

:collide(error): Invalid attempt to detect collision from CollisionRay into CollisionRay!

This means that a CollisionRay object attempted to test for an
intersection into a CollisionRay object.  This intersection
test has not yet been defined; it is possible the CollisionRay
object is not intended to be collidable.  Consider calling
set_into_collide_mask(0) on the CollisionRay object, or
set_from_collide_mask(0) on the CollisionRay object.

Does that mean that if I have multiple collision rays for the mouse, they interfere with each other? Is there a way to prevent that?

Does anyone have a hint how I could make this work?

Iā€™m not sure that I have a full answer for youā€“I havenā€™t really worked much with multiple viewports that I recallā€“but I have a few individual answers, I think:

This actually isnā€™t a problem, I believe: Iā€™ve done it myself, as I recall! (Indeed, it can be quite useful for event-purposes.)

In short, you would seem to have left your raysā€™ ā€œintoā€ collide-masks non-zero. As a result, the two rays, both of which are ā€œactiveā€ collidersā€“i.e. not passive ā€œintoā€-only objectsā€“attempt to collide with each other. And such a collision isnā€™t supported, hence the error.

It should be possible to fix this simply by setting your raysā€™ ā€œintoā€ collide-masks to zero. Something like this:

self.pickerNode.setIntoCollideMask(0)

# Or, if you prefer, you can have the BitMask class generate
# a zeroed BitMask32 object via the static "allOff" method:
mask = BitMask32.allOff()
self.pickerNode.setIntoCollideMask(mask)

Hi Thaumaturge!

Thanks for your quick reply as always :slight_smile:

Setting the IntoCollideMask to 0 makes it work! Thanks also for the hint with the bitmask classes. I didnā€™t really get how those bitmasks work yet. I will read some more about it. I guess it becomes more important the more things you try to collide with each other :smiley:

1 Like

Itā€™s my pleasure! :slight_smile:

Thatā€™s what Iā€™ve generally found, I believe.

As to how bit-masks work, hereā€™s a short-ish description, if youā€™re interested (and presuming that I recall correctly):

Bit-masks can be thought of as collections of ā€œflagsā€. If two objects might potentially collide, they only do so if their relevant bit-masks have at least one ā€œflagā€ set in common; otherwise, the collision doesnā€™t happen. More specifically, the collision happens if the ā€œfromā€ mask of the ā€œfromā€-object has at least one ā€œflagā€ set in common with with the ā€œintoā€ mask of the ā€œintoā€-object.

These ā€œflagsā€ are, in fact, just the bits of the bit-mask: ā€œsetting a flagā€ here means ā€œsetting that bit to 1ā€.

So, if we start with a ā€œzeroā€ bit-mask:

...0000 0000 0000

And we call ā€œsetBit(5)ā€, we set the ā€œflagā€/bit at position 5:

...0000 0010 0000
          ^               
Position: 54 3210           

Now, if itā€™s tested against another mask, it will only match if the other mask also has the ā€œflagā€/bit at position five set.

If there were more bits set, only one of them need match, if I recall correctly.