Clicking on 3D objects

I find it very strange that I need to use a collision ray to click on a 3d object on screen (method from the manual). There must be another way to do that. I guess internally Panda knows which pixel on screen belongs to which object, so, there should be the way to get this information out and by combining this information with the mouse position to find the object. What would you recommend?

I agree that it sure does seem that Panda should know which pixel belongs to which object–who’s responsible for drawing the pixels in the first, place, right–but in fact that’s not the case at all.

The pixels end up on the screen under control of the graphics hardware and its associated drivers. Panda’s job ends once the vertices have been issued. Panda itself really has no notion of pixels, and the graphics hardware isn’t designed to answer questions about which pixel came from which object.

So, yeah, the best way to pick a 3-D object based on a 2-D position is often by using a collision ray. There are other possible approaches, but most of them involve mathematical analysis of some kind, irrespective of pixels.

David

Honestly, this operation (to know what the cursor points at) is so much essential to practically any type of game, that I propose to right an efficient function for that and to include it to the Panda code, the same way as lookAt() or setHpr() are integrated. What do you think about it?

:blush: sorry for interrupting, im thinking now on a shader which is doing that. masking objects and project it on 2d. like a orthogonal stencil shadow which is projecting the mask onscreen 2d. hmm, maybe that could be the answer…

hmmm, but you have to benchmark the shader variante, modern grafx cards will have no problem by doing that, what i think, but old ones could slow down amazing by doing this. its just the question which way you want to go, cpu or gpu…

greetz
dirk

you could always do the opengl way!
Render the objects with solid color, setting different color per object. Then get the color under the mouse - that color is that object.
I should write up a class that handless all the setup - this can be interesting because it does depth sorting and could do alpha clipping properly.

Please, please! This is really interesting, helpful and practical thing.
Btw, ditus, I also thought of something like “stencil shadows onscreen” :slight_smile:

:smiley:

The method shown in the manual can be put in a function with a CollisionHandlerQueue.

The advantage of using a collision ray is that we get a list sorted by proximity of all the objects that intersect the ray. If the object you want to check up is the parent of the collision object you can then check up the hierarchy upwards.

Don’t know if using the OpenGL method compensates. It’s necessary to render the visible scene to an image and we loose to much info when the scene is rendered.

This is needed not only for clicking on the objects. I wanted the game to show objects’ names (as text above items, you know, as in many RPGs), not always but only when cursor is near the item (maybe not even when pointing at the item, it’s enough to be near). For that I should determine the position of the items on the screen. The method with the collision ray is the last that could help me, I guess. Much better is to know which object is where on the screen.

I agree that this is essential for almost all PC games. I just picked up Panda yesterday but I know this function already. Using a collision ray (picking ray) is simply projecting a point from view space to world space and returning a “yay” or “nay” if it intersects with a bounding box. That is the most rudimentary way. Detecting meshes makes things more complicated because you are dealing with convex shapes and requires many multiple cycles to determine if it passes within it’s object space.

I’m getting the impression from the feedback here that Panda takes this one step further and returns an array of objects that it intersects from nearest to farthest. This is important because most people want to use this to say, select an object or a character, not the environment that is beneath them. Having the user move the mouse over the character to reveal their names is exactly the same, except that you need to check every frame instead of on a single mouse click event. Keep this in mind when you have many objects in memory because you will be doing this EVERY frame. If the proprietary code for doing this is not cheap, you may be facing some excessive computations.

The general rule is, anything goes for small demos. You can be as inefficient as you want. But the moment you step up into full games, you need to consider optimizations.

if im not wrong it does not matter if the collision check returns 1 or multiple objects. As it got to check every collidable object anyway for intersections which are in the scene (except the ones that can be removed by advanced algorithms, aka culling etc.)…

Panda returns a list of objects, but you can sort them by distance. so you will only have to handle the first collision (if any exist).
check:

myHandler.sortEntries()

in
http://www.panda3d.org/manual/index.php/Clicking_on_3D_Objects

If you really need a picking function, here’s some untested code that may work. There’s no guarantees.

def pickRay(rootNP, origin, dir):
    "Returns a collision entry for the closest object colliding with the ray."

    traverser = CollisionTraverser("")
    chq = CollisionHandlerQueue()
    rayNode = CollisionNode("")
    ray = CollisionRay()

    # Make a collision node with one collision ray.
    ray.setOrigin(origin)
    ray.setDirection(dir)
    rayNode.addSolid(ray)

    # Attach the collision node to the provided node path.
    rayNP = rootNP.attachNewNode(rayNode)

    # This will let the ray detect collisions with normal geometry.
    rayNode.setFromCollideMask(GeomNode.getDefaultCollideMask())

    # Add the queue to a traverser, which will do the traversal
    # on the provided root node path.
    traverser.addCollider(rayNP, chq)
    traverser.traverse(rootNP)

    # Get the first node path in the sorted queue and discard the rest.
    if chq.getNumEntries() < 1:
        return None
    chq.sortEntries()
    return chq.getEntry(0)