Billboard effect on clickable label

In my application, I have several text labels that the user can click on to select the corresponding object. As the camera can move freely, I’m using the billboard effect (setBillboardPointEye) to make the labels always look at the camera and stay up. This works fine, but the associated collision solid (used to detect the mouse click) is not rotated by the billboard effect. According to this thread Collision detect of the models rotated by billboard effect this is the expected behavior however.

I guess I can at the beginning of each frame use look_at() to rotate the label towards the camera and calculate the new up vector in the camera frame to simulate the billboard effect, but will this also be applied on the collision solid ? Or is there a more optimal way to do it ?

A normal billboard effect will face the camera that is currently rendering the node. The collision traverser knows nothing about that, so it won’t be able to take into account the transform.

It is also possible, however, to construct a BillboardEffect that faces a specified node (which could be the camera). I’m wondering whether it would work as expected if you create such a BillboardEffect that explicitly mentions the camera.

This would require using the generic constructor, something like this:

model.setEffect(BillboardEffect.make((0, 0, 1), True, False, 0.0, base.cam, (0, 0, 0)))

If that doesn’t work, I’m happy to look further into the matter and check what would be needed to make the CollisionTraverser aware of billboard effects.

It almost works :slight_smile: If I use the line as is, when using picker.showCollisions() I see the collision card aligned with the label. However, clicking on it is not detected by the traverser. If I click on the left of the label though, the traverser detect the ray collision. It is as if the collision solid is flipped vertically to the left.

If I use (0, 0, -1) as upVector, then the collision solid is still aligned properly with the label and the traverser detect the ray collision at the right place. The only drawback is that the text is upside down, which is a bit annoying to be honest :slight_smile:

The problem seems to come from the camera rotation, if there is no rotation applied on the camera, the effect works fine and the ray collision detect the click and the exact location. However, if there is a rotation applied on the camera, this goes quickly wrong.

Here is a (ugly) test code that show the problem :

#!/usr/bin/env python

from direct.showbase.ShowBase import ShowBase
from panda3d.core import CollisionTraverser, CollisionNode, DecalEffect, NodePath
from panda3d.core import TransparencyAttrib, TextNode, CardMaker
from panda3d.core import CollisionHandlerQueue, CollisionRay, BillboardEffect
from direct.task.Task import Task

base = ShowBase()
pickerRay = None
picker = None
pq = None
box = None
card = None
flipped = False

def click():
    if base.mouseWatcherNode.hasMouse():
        mpos = base.mouseWatcherNode.getMouse()
        pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
        picker.traverse(render)
        if pq.getNumEntries() > 0:
            pq.sortEntries()
            np = pq.getEntry(0).getIntoNodePath()
            print("Click on", np)
    return Task.cont

def flip():
    global flipped
    base.camera.set_pos(0, 0, 0)
    if not flipped:
        base.cam.set_pos(0, 20, 0)
        base.cam.set_hpr(180, 0, 0)
    else:
        base.cam.set_pos(0, 0, 0)
        base.cam.set_hpr(0, 0, 0)
    flipped = not flipped

picker = CollisionTraverser()
pq = CollisionHandlerQueue()
pickerNode = CollisionNode('mouseRay')
pickerNP = base.cam.attachNewNode(pickerNode)
pickerNode.setFromCollideMask(CollisionNode.getDefaultCollideMask())
pickerRay = CollisionRay()
pickerNode.addSolid(pickerRay)
picker.addCollider(pickerNP, pq)
picker.showCollisions(render)

label = TextNode('text')
label.set_text("CLICK ME")
cardMaker = CardMaker('card')
cardMaker.setFrame(label.getFrameActual())
cardMaker.setColor(0, 0, 0, 0)
card_node = cardMaker.generate()
card = NodePath(card_node)
tnp = card.attachNewNode(label)
card.setTransparency(TransparencyAttrib.MAlpha)
card.setEffect(DecalEffect.make())
card.setEffect(BillboardEffect.make((0, 0, 1), True, False, 0.0, base.cam, (0, 0, 0)))
card.reparent_to(render)
card.setCollideMask(CollisionNode.getDefaultCollideMask())
card.set_pos(-1, 10, 0)
box = loader.loadModel('box')
box.reparent_to(render)
box.set_pos(0, 10, 0)
box.setCollideMask(CollisionNode.getDefaultCollideMask())
box.setEffect(BillboardEffect.make((0, 0, 1), True, False, 0.0, base.cam, (0, 0, 0)))

base.accept("mouse1-up", click)
base.accept("mouse3-up", flip)

base.run()

You can use the right button to move the camera on the other side of the “scene” and rotate it. When H is 0, everything works as excepted, when H is 180, the collision solids are highlighted when no collision is detected.

I forgot this one too, I made a feature request about it : https://github.com/panda3d/panda3d/issues/695