New Morph Transfer to Nearest Face could use Blender's closest_point_on_mesh()

Issue #307 resolved
Xin created an issue

Blender internally takes advantage of structures that subdivide the 3d space (kd-trees and BVH trees), which make a lot of spatial lookups way faster.

Instead of manually considering all pairs with numpy, why not use Blender’s API for the Nearest Face method?

The relevant closest_point_on_mesh() method is documented here: https://docs.blender.org/api/current/bpy.types.Object.html (notice that you can get the face index). I attached a .blend that contains a simple example script that finds the closest point to two objects.

This could maybe also be applied to the Geograft method with some additional considerations (you need the closest vertex in that case, not a face index). Even better, the Blender’s KDTree API might be more useful in that case: https://docs.blender.org/api/current/mathutils.kdtree.html . Also the BVHTree API: https://docs.blender.org/api/current/mathutils.bvhtree.html

Comments (19)

  1. Alessandro Padovani

    Also shrinkwrap can be used to avoid transferring jcms at all, see #226, personally I find it very effective. I don’t like to get lots of jcms on the outfit unless necessary. Then some jcms may be needed, especially custom jcms for the outfit, but those are loaded not tranfered.

  2. Thomas Larsson repo owner

    Xin, thank you for this. It promises to make the tool much faster, since almost all time is spent figuring out the nearest faces. However, I am currently stuck because the function seems to return face indices larger than the total number of faces. Consider the following program:

    import bpy
    ob = bpy.context.object
    data = [ob.closest_point_on_mesh(v.co) for v in ob.data.vertices]
    fnums = [idx for res,loc,nor,idx in data]
    print("Max fnum = %d" % max(fnums))
    print("Total fnums = %d" % len(ob.data.polygons))

    When applied to G8F, the output is

    Max fnum = 65468

    Total fnums = 16368

    So what am I missing? One can note that 4*16368 = 65372.

  3. Thomas Larsson repo owner

    Xin, the test script only uses one mesh, but the problem arose with two meshes. In the case of a single triangle, the list is [0,0,0], and max([0,0,0]) = 0, which is less than len(ob.data.polygons) = 1. Fine. Things also work find with primitives that I have tested, both pure triangle and pure quad meshes. The problem arises with G8F, where the max face number > total number of faces.

  4. Xin reporter

    Oh sorry for that, I just misread what you wrote first, disregard my previous answer.

    The real answer is just the subdiv modifier which is being taken into account by the function.

    I forgot to mention this before, but in my attached example, you can see that clearly: one of the cubes was subdivided precisely to demonstrate that the subdiv is taken into account by closest_point_on_mesh(). Try making the subdiv invisible and you will see how my example changes. Same thing happens with g8, which subdivided has way more faces than what ob.data.polygons returns.

    That’s why the function has a depsgraph parameter too, to change the default behavior (more suitable for more complex cases with more modifiers than just a single subdiv like in this example): https://docs.blender.org/api/current/bpy.types.Object.html#bpy.types.Object.closest_point_on_mesh .

  5. Thomas Larsson repo owner

    Thank you, Xin, that was it. Morph transfer is now very much faster, and the actual matching between verts and faces is lightning fast.

  6. Xin reporter

    Good news and thanks again.

    Have you tried seeing if building the BVH tree yourself outside the loop (see; https://docs.blender.org/api/current/mathutils.bvhtree.html ) and querying it inside makes it faster, compared to calling closest_point_on_mesh() each time? I haven’t looked into how Blender implements that function, but it is perhaps not as well optimized for a lot of sequential queries as a direct BVH tree that you can create yourself. I really doubt it will make much of a difference (and it might be worse) since I suspect the tree you are querying with the function already internally exists for rendering purposes, but it might be worth testing.

  7. Thomas Larsson repo owner

    I don’t think there is anything more to gain here. I tested to transfer from G8F to a mesh which covers it tightly and has almost four times as many verts. Not a geograft, but similar in spirit. The standard JCMs were transferred in 21.9 s, but only 0.7 s was spent on matching verts with faces. So the matching code is not critical anymore.

  8. Thomas Larsson repo owner

    Then I transferred all Meipex morphs from Golden Palace to the merged mesh. Total time 29.5 s, vertex matching 1.0 s. Not super critical either. In principle you could see artifacts from using nearest-vertex matching here too, but for the intended application the vertices should match exactly. A KDTree may be useful for the body method though, where you match everything but the geografts. Will check that out.

  9. Alessandro Padovani

    Commit 27eac5d seems to work great, thank you Xin and Thomas for the nice speedup. As for shrinkwrap I can’t reproduce the exact issue with the bardot skirt, but of course I agree that transferring jcms is more precise.

  10. Log in to comment