What's a clean way to mouse-pick actors?

Hoi hoi!

I understood that Actors don’t really show normally in the scenegraph. That’s okay, kinda makes sense I guess (though I wouldn’t mind a new feature in 1.9 to make Actors full-fledged nodes).

I have a working bit of code, straight from the manual, to click on meshes. Fires a ray from the camera and take the closest collider. Easy.

How, from there, do I go to opening the door I clicked on? The door is animated so it’s an actor. I can find some geoms and stuff, but I don’t really know to which actor they belongs. :confused:

I can think of a few ways to do this, but I don’t want to re-invent the wheel. Furthermore, there may one way that’s better than the others and I don’t know it yet.

For example: I can put all my actors in a dictionary, and use nodepath tag values as keys to this dictionary. Would that be decent? How do you guys do that?

An Actor is a NodePath. It points to a node like any other in the scene. The catch is that you can’t turn a GeomNode back into an Actor object without some tricks.

Search the forums or manual for setPythonTag for more information.

For the record, in the case of doors, it is probably easier to open the door using a simple rotation on the relevant node than using the animation system.

I generate a unique identifier (uid) for my actors and I put all these actors in a dictionary with these uid as keys.

I also set a tag on my Actor: actor.setTag(‘actor_uid’, uid).

It’s very easy then to retrieve the actor on which I clicked:

mpos = self.mouseWatcherNode.getMouse()
self.mousepick_ray.setFromLens(self.camNode, mpos.getX(), mpos.getY())
self.mousepick_traverser.traverse(self.render)
if self.mousepick_handler.getNumEntries() > 0:
    self.mousepick_handler.sortEntries()
    picked = self.mousepick_handler.getEntry(0).getIntoNodePath()
    picked = picked.findNetTag(CLICKABLE_TAG)
    if not picked.isEmpty():
        if picked.hasTag('actor_uid'):
            uid = picked.getTag('actor_uid')
            self.actors[uid].onClick()

But you could also put the Actor, some wrapper class, or a method in a pythonTag, and then do it like this:

picked.getPythonTag("onClickMethod")()

Just keep in mind that there are various things to take into consideration when doing that, since that creates a circular reference, and will result in memory leaks without extra care.

Oh! What creates a circular reference?

Most directly:

actor.setPythonTag("object", actor)

So “actor” holds a reference to itself, which makes sure that the reference count of the actor never reaches zero. This means that before destroying the model, you will have to clear the relevant python tag.

That’s quite direct, but there can also be more sneaky cases. Something like this, for instance:

def blah():
    return actor
actor.setPythonTag("objectMethod", blah)

This is an example of an indirect circular reference, and is just as bad.

Ohhh, I understood what PythonTags were for. Nice thing indeed, when you know what you’re doing. Thanks for the tips!