python snippet: Get object under mouse


I sometimes write little bits of code which are a good solution to a fairly common problem. Rather than keep them to myself I’m going to start posting them, with examples of course.

Many people using the mouse over sensor, or the mouse over (any) sensor will have found that it’s pretty slow.

There are two reasons for this:

  1. The function runs every frame. Actually we often don’t need to know what’s under the mouse except when clicking one of the mouse buttons, so the rest of the time it’s just eating logic for no reason.

  2. The function returns 8 different items of information about the object hit. Including, hit position in 3d space, the poly hit and its normal vector, the position in the UV map of the hit and information about the ray cast from the camera to the mouse position. We can say 9 returns if we include the True/False trigger for the controller. It gets this info even if we don’t need it, just because we might. Because it gets info about which poly was hit, it needs to check every poly on the object. With an object which has 1000+ polys that can take quite a long time, and because it’s making that check every frame while returning a positive reading, the drain can be quite high.

So for those cases where I just need to know, for one instant what object is under the mouse cursor, I use the following script:

def mouse_hit_ray(camera,mouse_position,property="",distance=50.0):
    return_normal = 0
    x_ray = 1
    return_polygon = 0

    screen_vect = camera.getScreenVect(mouse_position[0],mouse_position[1])
    screen_vect.negate()        
    target_position = camera.worldPosition + screen_vect                
    target_ray = camera.rayCast( target_position, camera, distance , property, return_normal, x_ray, return_polygon)
    
    return target_ray

This returns [hit_object,hit_position,hit_normal (pointing back towards the camera), and the poly which was hit(turned off here, but doing so doesn’t give any performance bonus, so you might as well turn it on if you want)]

If you want to know if any objects are there, and return the first one hit, use “” as the property.
If you want to only check for a certain object (I usually use it for getting the ground or an item) use a property like “ground” or “item”.
If you’re playing in a game with different rooms and don’t want to detect things in the other rooms, use a short distance setting.

You need to set at least the camera from which you are viewing the scene and the mouse position. Mouse position is a set of two floats, 0.0-1.0, so you can use the following code:

mouse_position = bge.logic.mouse.position 

This would have been much more useful before the latest version of blender came along, as previously you couldn’t set x_ray for mouse_over, but now you can. Anyway, it’s still useful as it’s a quick function you can call without using a dedicated sensor.

But please remember, if you want to check every frame, you should use the original mouse-over sensor, as it’s compiled to run faster than my script.

Here’s a demo which uses the function:
mouse_click_delete.blend (540 KB)

Click on the cubes to delete them.

INFO: For anyone writing their own code which utilizes getScreenVect() you should know that the vector returned runs from the screen position to the camera, so it needs to be first inverted (negate()) and flipped back towards the screen position before it can get information about what is under the cursor. I don’t know if this is the intended behavior of the function, or a BUG.

EXTRA: If you don’t need to use x-ray, you can use the much simpler getScreenRay(), but this function doesn’t have an x-ray ability, so it won’t get the object if it is blocked by anything, even ghost objects AND EVEN INVISIBLE GHOST OBJECTS!. This can be a problem if you’re using dynamic, ghost objects for special effects like smoke or explosions or whatever as they will block the ray from finding the object you want. Or if you have invisible UI elements in the main scene.

Here’s a Blend using the simpler, but less useful function:
mouse_click_delete_simple.blend (553 KB)

In fact I originally wrote this snippet because of the lack of an x_ray fucntion in both the mouseover sensor and getScreenRay() but I find it is still very useful.

1 Like

Pretty nice!
I actually did something similar recently for debugging purposes, it’s mouse drag to drag any object on-screen, with just 1 always sensor and a python script. I used the built in getScreenRay function instead of manually getting the 3d Vector and ray casting.

.blend:

Attachments

mousedrag.blend (502 KB)

Very nice, I’m just about to post another snippet for a custom mouse cursor which uses a similar function.

There’s some useful things in there that I didn’t know about, like using

mousePos = Vector((camera.worldPosition - vec)*z)

instead of negating the vector and

vec = camera.getScreenVect(*logic.mouse.position)

the star to get it to accept the list as two arguments. Nice.

The main reason I use a manual raycast is because get screen ray can be blocked easily by many kinds of objects, though that doesn’t matter in your script, as you don’t want to to go through to the back side cube.

is it possibile to avoid the poly by poly check without replacing the collision mesh?