Yeah, I've done that before. By the sounds of it, your terrain mesh is procedural. The solution is to put the multimesh instance through the same algorithm as the terrain vertex placement.
You didn't say how you're doing this, say you're using OpenSimplexNoise. You set the seed, period, octaves and persistence. Say we start with a plane which we subdivide. Then with SurfaceTool and MeshDataTool we loop through each vertex offsetting the position. When we do this we call a method set_noise_position(v)
which returns the repositioned vertex. Something like:
func set_noise_position(v):
return v.y *= 1 + (noise.get_noise_3d(v.x, v.y, v.z) * scalar)
Now when you make your MultiMeshInstance you can just set the loop the MultiMesh setting the x and y randomly / according to whatever criteria you have and call set_noise_position(v)
on the y. Remember to position the vector within the plane. Something like this:
func position_objs():
randomize()
for i in objs.multimesh.instance_count:
var v = Vector3(rand_range(-plane_size/2, plane_size/2),
0.0,
rand_range(-plane_size/2, plane_size/2))
v = set_noise_position(v)
objs.multimesh.set_instance_transform(i, Transform(Basis.IDENTITY, v)
Done. Just make sure the origin for the multimesh is at the bottom so it sits nicely on the ground. Now on slopes you might need to recalculate the basis with a y on the normal if its a wide object.
Now I did mine on a cube-sphere with chunks so I have to take the xform
/ xform_inv
but hopefully you can just use a simple basis identity.