How to project a single x/y coordinate in z-direction onto a specific mesh with scripting I want to place a number of objects on the surface of a mesh (think: terrain, (displaced) ground plane mesh) along the global z-normal. I know my objects' x/y coordinates and need to find the corresponding z value on the mesh surface to do the translation of my objects.

I'd prefer a solution involving scripting. Performance is not a big issue, but the method should be fast enough to handle up to a couple of thousands of objects.

I have found a number of approaches, but I'm unsure where to dig deeper and would appreciate some pointers.

What I looked into so far:


Raycast: I understand that I could place 2 empties - number one just underneath my object, number two shifted far away down along the z-normal, cast a ray and would get a hit on my mesh if it is between the two. However, there may be situations when my target mesh is obstructed and there is no direct line of sight. Still looks like the most promising approach so far. (related issue)
closest_point_on_mesh: that sounds good too and would avoid the line-of-sight issue, but I don't see a way to restrict this to the z-normal
mathutils-geometry: intersect_line_plane sounds also interesting, but my target is not a "plane" (or triangle) but a mesh. I'm unsure how to apply this function to a mesh - do I have to step through all the faces? Is that efficient and if yes, how is this done?
BVH: mytree.ray_cast or mytree.find?
Snapping: maybe in this case, using userspace functions like snapping make sense? But I'd prefer something without reliance on context.


I've tried to use lattice/shrinkwrap to place all (joined) objects in one go, but the shrinkwrap modifier deforms them, which is not what I want.

Thank you!
The script below will move a list of Suzanne's to the surface point directly above them on the terrain object using ray casting. You don't need to worry about obstruction by other objects since the ray is cast directly within the target object's (terrain) local space.

import bpy
from mathutils import Vector

def obj_ray_cast(obj, matrix, ray_origin, ray_target):
    """Wrapper for ray casting that moves the ray into object space"""

    # get the ray relative to the object
    matrix_inv        = matrix.inverted()
    ray_origin_obj    = matrix_inv * ray_origin
    ray_target_obj    = matrix_inv * ray_target
    ray_direction_obj = ray_target_obj - ray_origin_obj

    # cast the ray
    success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj)

    if success:
        return location, normal, face_index
    else:
        return None, None, None

objects = [ o for o in bpy.context.scene.objects if "Suzanne" in o.name ]

targetObj = bpy.data.objects['Landscape']
mw        = targetObj.matrix_world 

for o in objects:
    ray_origin = o.location
    ray_target = o.location + Vector((0,0,100))

    location, normal, face_index = obj_ray_cast(targetObj, mw, ray_origin, ray_target)
    if location: o.location = location