Clicking on a 3D object

Can anyone tell me how to click on 3D objects?
I read the manuals but it was complicated to me.
So can you simplify it please?
(Note: I have no code)

To start with, let me check: Have you read this manual page, specifically?

If so, is there anything specific that you’re having trouble with there?

Otherwise, to what degree have you used the collision system thus far? If this is one of your first uses of it, then perhaps, in order to reduce the number of complexities clouding matters, it might help to start with simpler, more foundational applications of it and work up from there.

Thanks for the reply but I read the manual properly and understood. Unfortunately I ran into a problem. I click on the object but it doesn’t execute anything (in this case ‘print’ for debugging).

Code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import *

ShowBase()

base.cTrav = CollisionTraverser()

collHandEvent = CollisionHandlerEvent()
collHandEvent.addInPattern('into-%in')

smiley = loader.loadModel('models/smiley')
smiley.reparentTo(render)
smiley.setPos(5, 25, 0)
smiley.setTag('smileyTag', '1')

cNode = CollisionNode('smiley')
cNode.addSolid(CollisionSphere(0, 0, 0, 1))
smileyC = smiley.attachNewNode(cNode)

pickerNode = CollisionNode('mouseRay')
pickerNP = camera.attachNewNode(pickerNode)
pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
pickerRay = CollisionRay()
pickerNode.addSolid(pickerRay)
base.cTrav.addCollider(pickerNP, collHandEvent)

def click():
    mpos = base.mouseWatcherNode.getMouse()
    pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
    base.cTrav.traverse(render)
    
    base.accept('into-smiley', print, ['Smiley Clicked!'])

base.accept('mouse1', click)

base.run()

I started to gain interest in collision on Panda3D a week ago (mostly because I know everything else). I have worked with collision since then, like you helped me in this one:

But this is the first time I am using mouse clicking. :disappointed:

1 Like

Okay, fair enough! We know now more or less from what point we’re working. :slight_smile:

I did a little debugging of the code that you gave, and it looks like the main problem is simply that you’re setting the ray’s collide-mask to have the Geom default collide-mask.

Now, doing so is specified in the example. However! The example does this because it’s not bothering to set up actual collision geometry for the target–it’s just making the ray collide with visible geometry.

Note that colliding with visible geometry is generally not recommended, unless you have a particularly good reason to do so: Visible geometry isn’t optimised for collisions, and thus collisions with such geometry can be significantly slower, I believe.

In your case, however, you do have collision geometry. As a result, you don’t require collisions with visible geometry.

What’s more, in setting the ray to use that Geom collide-mask you’re telling the ray to only collide with visible geometry (after all, you only specified the Geom collide-mask, not that mask and some other).

As a result, your ray is working as intended, but it’s only registering collisions with visible geometry, such as the smiley-model. And the smiley-model’s node isn’t named “smiley”, so the event with pattern “into-smiley” is never generated.

To fix the problem, just remove the setting of the collide-mask.

If I may add a few notes:

  1. You’re using base.cTrav, which means that the system will automatically traverse the scene-graph each frame, regardless of any clicking, I believe. This may result in additional events being generated, even when not clicking.
    • If you want your ray to only traverse when you click, consider using a separate traverser for it.
  2. You’re currently using a CollisionHandlerEvent for your ray. This is fine–but note that you might then end up with multiple events if more than one object intersects with your ray, even if one is in front of the other.
    • If this is what you want, then well and good!
    • However, if you only want to get the first hit of the ray, consider using a CollisionHandlerQueue and sorting its entries to get the first hit.
      • Note that CollisionHandlerQueue provides a method to sort its entries.
      • I think that the manual has an example of how to do what I’m describing here.
  3. While likely harmless, note that you don’t have to accept your “into-smiley” event on each click. You can do it just once, outside of the “click” method.
    • (Presuming that you stick with an event-handler, of course.)

Please tell me what you mean by this. How to make a separate traverser?

Why isn’t int named smiley? Didn’t I already name it smiley by executing the line of code:

cNode = CollisionNode('smiley')

?

The manual does have what you are saying here

But then how will I handle events on click?

Can you give me the code of mine that you altered so I can understand?

To start with, could I ask you to put your various replies into a single post in future, please? That way I can reasonably assume that you’re done posting, and I don’t keep getting notifications beyond the first. ^^;;

That said:

The same way as you made the first one, but just assigned to a new variable.

So, something like this:

self.myTraverser = CollisionTraverser()

When you want to have it traverse, instead of calling base.cTrav.traverse(render), you instead call self.myTraverser.traverse(render).

You named the collision-object “smiley”–but as I said, you’re not colliding with the collision-object. You’re colliding with the visible object, which you didn’t name.

Put another way, you have two nodes: one produced by “loader.loadModel”, and one produced by “CollisionNode(‘smiley’)”. Because of the mask that you’re applying to the ray, you’re getting collisions with the former of those, and not with the latter.

You still have an event that’s generated when you click the mouse-button–just as you currently have. You can simply put all of the relevant logic–traversal, checking the CollisionHandlerQueue object, responding if there’s a hit, etc.–in that event.

Note: Right now you’re setting up two events: the mouse-click event, and the ray-hit event. I’m suggesting that you remove the latter, and just use the former.

If I recall correctly, all that I did was delete the line that calls “setFromCollideMask”–the rest should be the same.

Sorry I am new to this site, so I don’t know how to do that. I will try now

So what if I am doing it outside the init method? Should I just do this:

myTraverser = CollisionTraver()

How do I overcome this?

Ok. I will try

You seem to have done it–thank you for being willing. :slight_smile:

Well, you’ll want to access it later, presumably, and it’s likely not ideal to keep re-constructing it over and over again, so it makes sense to me to keep it in a variable that’s accessible outside of the location in which it’s constructed.

That said, note that “self” exists outside of the __init__ method.

And if you’re constructing it outside of the relevant class itself, then you can just assign it to the relevant instance of the class. Something like this:

class MyClass
    # Presume that we set up the class; I'll omit that
    # for brevity and clarity

myClassInstance = MyClass()

myClassInstance.traverser = CollisionTraverser()

Simply switching from colliding with visual geometry to the default state of colliding with all collision geometry should do the job, I believe. And that’s what removing the call to “setFromCollideMask” should do.

If I do that, how do I make a NodePath?

… I’m confused–what does one have to do with the other?

Let me try to explain again: what I’m saying is this:

The problem, essentially, is that your ray is colliding with visible geometry and not collision geometry. (Note that the collision geometry is named “smiley”, and the visible geometry is not.) It’s doing this because you’re assigning a collision-mask that tells the ray to collide with things that have the collision-mask associated with visible geometry, and only that. Thus the solution is to not assign that collision-mask. This shouldn’t interfere with construction of NodePaths at all.

I am not really getting it. Can you show me the code
Just take my code from above and make the necessary changes

I describe the relevant change earlier, and you said that you intended to try it:

It worked, but its giving me information that I don’t really want. How do I remove it?:

Known pipe types:
wglGraphicsPipe
(all display modules loaded.)
Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 4.83174 24.0431 -0.236218
normal -0.168274 -0.957014 -0.236237
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 5.20222 24.0681 0.300955
normal 0.202225 -0.931946 0.300969
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 4.49187 24.1787 -0.259146
normal -0.508151 -0.821353 -0.259157
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 4.77212 24.0673 -0.279449
normal -0.227885 -0.932722 -0.27946
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 5.16706 24.2055 -0.583726
normal 0.167066 -0.79455 -0.583763
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 4.5614 24.3191 0.586465
normal -0.438615 -0.680923 0.586482
respect_prev_transform = 0

Smiley Clicked! CollisionEntry:
from render/camera/mouseRay
into render/smiley.egg/smiley []
at 4.58334 24.0919 0.043036
normal -0.416643 -0.908051 0.0430344
respect_prev_transform = 0

Also, I am not able to click again and again. I once click, then it prints, and then I have to click somewhere else to do so again.

I’m guessing that you mean that it keeps printing out, even if you aren’t clicking. Is that correct?

If so, then that’s likely to do with the fact that you’re still using base.cTrav, rather than a traverser of your own.

That’s… odd. It doesn’t print again if you click immediately in the same place?

(Or do you mean that you want it to keep colliding while the mouse-button is held down?)

Actually, I clicked it four times. So that info is actually four times of what it should be. But in each time, after printing ‘Smiley Clicked!’, it is showing some info of the collision. So how do I avoid this info being printed?

That’s right
So how to fix it?

I am not using base.cTrav, I am using a traverser called myTraverser which is not an attribute of base