Data Transfer: Add an option to 'auto-transform' destination mesh so that it matches...
authorBastien Montagne <montagne29@wanadoo.fr>
Mon, 13 Jul 2015 16:00:08 +0000 (18:00 +0200)
committerBastien Montagne <montagne29@wanadoo.fr>
Mon, 13 Jul 2015 16:05:38 +0000 (18:05 +0200)
This allows to match and transfer data between two meshes with similar shape but complete arbitrary different transform.

Note that the result will be best if the meshes (more precisely, their vertices) are exact copies of each other.
Otherwise, method used can only perform an approximated best match, which means you'll likely get better
results if you 'visually' make them match in 3D space (and use 'Object Transform') instead.

source/blender/blenkernel/BKE_data_transfer.h
source/blender/blenkernel/BKE_mesh_remap.h
source/blender/blenkernel/intern/data_transfer.c
source/blender/blenkernel/intern/mesh_remap.c
source/blender/editors/object/object_data_transfer.c
source/blender/modifiers/intern/MOD_datatransfer.c

index cea093adca4fee7774eee4723adc94b27bef8883..7c64d525575958feb0c418c061eab788fc91a757 100644 (file)
@@ -136,8 +136,8 @@ bool BKE_object_data_transfer_mesh(
         struct Scene *scene,
         struct Object *ob_src, struct Object *ob_dst, const int data_types, bool use_create,
         const int map_vert_mode, const int map_edge_mode, const int map_loop_mode, const int map_poly_mode,
-        struct SpaceTransform *space_transform, const float max_distance, const float ray_radius,
-        const float islands_handling_precision,
+        struct SpaceTransform *space_transform, const bool auto_transform,
+        const float max_distance, const float ray_radius, const float islands_handling_precision,
         const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX],
         const int mix_mode, const float mix_factor, const char *vgroup_name, const bool invert_vgroup,
         struct ReportList *reports);
@@ -146,8 +146,8 @@ bool BKE_object_data_transfer_dm(
         struct Object *ob_src, struct Object *ob_dst, struct DerivedMesh *dm_dst,
         const int data_types, bool use_create,
         const int map_vert_mode, const int map_edge_mode, const int map_loop_mode, const int map_poly_mode,
-        struct SpaceTransform *space_transform, const float max_distance, const float ray_radius,
-        const float islands_handling_precision,
+        struct SpaceTransform *space_transform, const bool auto_transform,
+        const float max_distance, const float ray_radius, const float islands_handling_precision,
         const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX],
         const int mix_mode, const float mix_factor, const char *vgroup_name, const bool invert_vgroup,
         struct ReportList *reports);
index c6d8da1656556b92f37ab7384bdce7c2f7c119de..5c77ab8a94ec9b654019cf9dbbd9668c58105359 100644 (file)
@@ -140,6 +140,14 @@ enum {
        MREMAP_MODE_TOPOLOGY                 = MREMAP_MODE_VERT | MREMAP_MODE_EDGE | MREMAP_MODE_LOOP | MREMAP_MODE_POLY,
 };
 
+float BKE_mesh_remap_calc_difference_from_dm(
+        const struct SpaceTransform *space_transform,
+        const struct MVert *verts_dst, const int numverts_dst, struct DerivedMesh *dm_src);
+
+void BKE_mesh_remap_find_best_match_from_dm(
+        const struct MVert *verts_dst, const int numverts_dst, struct DerivedMesh *dm_src,
+        struct SpaceTransform *r_space_transform);
+
 /* TODO add mesh2mesh versions (we'll need mesh versions of bvhtree funcs too, though!). */
 
 void BKE_mesh_remap_calc_verts_from_dm(
index da00aecf9c0ff89e2003519c96a6b0713fb75b08..53b6f4a1019a72d70b5d24115158743dd8db7f89 100644 (file)
@@ -1064,8 +1064,8 @@ void BKE_object_data_transfer_layout(
 bool BKE_object_data_transfer_dm(
         Scene *scene, Object *ob_src, Object *ob_dst, DerivedMesh *dm_dst, const int data_types, bool use_create,
         const int map_vert_mode, const int map_edge_mode, const int map_loop_mode, const int map_poly_mode,
-        SpaceTransform *space_transform, const float max_distance, const float ray_radius,
-        const float islands_handling_precision,
+        SpaceTransform *space_transform, const bool auto_transform,
+        const float max_distance, const float ray_radius, const float islands_handling_precision,
         const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX],
         const int mix_mode, const float mix_factor, const char *vgroup_name, const bool invert_vgroup,
         ReportList *reports)
@@ -1076,6 +1076,8 @@ bool BKE_object_data_transfer_dm(
 #define PDATA 3
 #define DATAMAX 4
 
+       SpaceTransform auto_space_transform;
+
        DerivedMesh *dm_src;
        Mesh *me_dst, *me_src;
        bool dirty_nors_dst = true;  /* Assumed always true if not using a dm as destination. */
@@ -1126,6 +1128,17 @@ bool BKE_object_data_transfer_dm(
                return changed;
        }
 
+       if (auto_transform) {
+               MVert *verts_dst = dm_dst ? dm_dst->getVertArray(dm_dst) : me_dst->mvert;
+               const int num_verts_dst = dm_dst ? dm_dst->getNumVerts(dm_dst) : me_dst->totvert;
+
+               if (space_transform == NULL) {
+                       space_transform = &auto_space_transform;
+               }
+
+               BKE_mesh_remap_find_best_match_from_dm(verts_dst, num_verts_dst, dm_src, space_transform);
+       }
+
        /* Check all possible data types.
         * Note item mappings and dest mix weights are cached. */
        for (i = 0; i < DT_TYPE_MAX; i++) {
@@ -1388,15 +1401,17 @@ bool BKE_object_data_transfer_dm(
 bool BKE_object_data_transfer_mesh(
         Scene *scene, Object *ob_src, Object *ob_dst, const int data_types, const bool use_create,
         const int map_vert_mode, const int map_edge_mode, const int map_loop_mode, const int map_poly_mode,
-        SpaceTransform *space_transform, const float max_distance, const float ray_radius,
-        const float islands_handling_precision,
+        SpaceTransform *space_transform, const bool auto_transform,
+        const float max_distance, const float ray_radius, const float islands_handling_precision,
         const int fromlayers_select[DT_MULTILAYER_INDEX_MAX], const int tolayers_select[DT_MULTILAYER_INDEX_MAX],
         const int mix_mode, const float mix_factor, const char *vgroup_name, const bool invert_vgroup,
         ReportList *reports)
 {
        return BKE_object_data_transfer_dm(
                scene, ob_src, ob_dst, NULL, data_types, use_create,
-               map_vert_mode, map_edge_mode, map_loop_mode, map_poly_mode, space_transform,
-               max_distance, ray_radius, islands_handling_precision, fromlayers_select, tolayers_select,
+               map_vert_mode, map_edge_mode, map_loop_mode, map_poly_mode,
+               space_transform, auto_transform,
+               max_distance, ray_radius, islands_handling_precision,
+               fromlayers_select, tolayers_select,
                mix_mode, mix_factor, vgroup_name, invert_vgroup, reports);
 }
index 08823c1c4a194f11bd516af95e7dfe4871fdb6c6..c818c6b9f19c597cdf2b0b823a79ac5fcaee888f 100644 (file)
 
 /* -------------------------------------------------------------------- */
 
+/** \name Some generic helpers.
+ * \{ */
+
+static bool mesh_remap_bvhtree_query_nearest(
+        BVHTreeFromMesh *treedata, BVHTreeNearest *nearest,
+        const float co[3], const float max_dist_sq, float *r_hit_dist)
+{
+       /* Use local proximity heuristics (to reduce the nearest search). */
+       if (nearest->index != -1) {
+               nearest->dist_sq = min_ff(len_squared_v3v3(co, nearest->co), max_dist_sq);
+       }
+       else {
+               nearest->dist_sq = max_dist_sq;
+       }
+       /* Compute and store result. If invalid (-1 index), keep FLT_MAX dist. */
+       BLI_bvhtree_find_nearest(treedata->tree, co, nearest, treedata->nearest_callback, treedata);
+
+       if ((nearest->index != -1) && (nearest->dist_sq <= max_dist_sq)) {
+               *r_hit_dist = sqrtf(nearest->dist_sq);
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+static bool mesh_remap_bvhtree_query_raycast(
+        BVHTreeFromMesh *treedata, BVHTreeRayHit *rayhit,
+        const float co[3], const float no[3], const float radius, const float max_dist, float *r_hit_dist)
+{
+       BVHTreeRayHit rayhit_tmp;
+       float inv_no[3];
+
+       rayhit->index = -1;
+       rayhit->dist = max_dist;
+       BLI_bvhtree_ray_cast(treedata->tree, co, no, radius, rayhit, treedata->raycast_callback, treedata);
+
+       /* Also cast in the other direction! */
+       rayhit_tmp = *rayhit;
+       negate_v3_v3(inv_no, no);
+       BLI_bvhtree_ray_cast(treedata->tree, co, inv_no, radius, &rayhit_tmp, treedata->raycast_callback, treedata);
+       if (rayhit_tmp.dist < rayhit->dist) {
+               *rayhit = rayhit_tmp;
+       }
+
+       if ((rayhit->index != -1) && (rayhit->dist <= max_dist)) {
+               *r_hit_dist = rayhit->dist;
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+/** \} */
+
+/**
+ * \name Auto-match.
+ *
+ * Find transform of a mesh to get best match with another.
+ * \{ */
+
+/**
+ * Compute a value of the difference between both given meshes.
+ * The smaller the result, the better the match.
+ *
+ * We return the inverse of the average of the inversed shortest distance from each dst vertex to src ones.
+ * In other words, beyond a certain (relatively small) distance, all differences have more or less the same weight
+ * in final result, which allows to reduce influence of a few high differences, in favor of a global good matching.
+ */
+float BKE_mesh_remap_calc_difference_from_dm(
+        const SpaceTransform *space_transform, const MVert *verts_dst, const int numverts_dst, DerivedMesh *dm_src)
+{
+       BVHTreeFromMesh treedata = {NULL};
+       BVHTreeNearest nearest = {0};
+       float hit_dist;
+
+       float result = 0.0f;
+       int i;
+
+       bvhtree_from_mesh_verts(&treedata, dm_src, 0.0f, 2, 6);
+       nearest.index = -1;
+
+       for (i = 0; i < numverts_dst; i++) {
+               float tmp_co[3];
+
+               copy_v3_v3(tmp_co, verts_dst[i].co);
+
+               /* Convert the vertex to tree coordinates, if needed. */
+               if (space_transform) {
+                       BLI_space_transform_apply(space_transform, tmp_co);
+               }
+
+               if (mesh_remap_bvhtree_query_nearest(&treedata, &nearest, tmp_co, FLT_MAX, &hit_dist)) {
+                       result += 1.0f / (hit_dist + 1.0f);
+               }
+               else {
+                       /* No source for this dest vertex! */
+                       result += 1e-18f;
+               }
+       }
+
+       result = ((float)numverts_dst / result) - 1.0f;
+
+//     printf("%s: Computed difference between meshes (the lower the better): %f\n", __func__, result);
+
+       return result;
+}
+
+/* This helper computes the eigen values & vectors for covariance matrix of all given vertices coordinates.
+ *
+ * Those vectors define the 'average ellipsoid' of the mesh (i.e. the 'best fitting' ellipsoid
+ * containing 50% of the vertices).
+ *
+ * Note that it will not perform fantastic in case two or more eigen values are equal (e.g. a cylinder or
+ * parallelepiped with a square section give two identical eigenvalues, a sphere or tetrahedron give
+ * three identical ones, etc.), since you cannot really define all axes in those cases. We default to dummy
+ * generated orthogonal vectors in this case, instead of using eigen vectors.
+ */
+static void mesh_calc_eigen_matrix(
+        const MVert *verts, const float (*vcos)[3], const int numverts, float r_mat[4][4])
+{
+       float center[3], covmat[3][3];
+       float eigen_val[3], eigen_vec[3][3];
+       float (*cos)[3] = (float (*)[3])vcos;
+
+       bool eigen_success;
+       int i;
+
+       if (verts) {
+               const MVert *mv;
+               float (*co)[3];
+
+               cos = MEM_mallocN(sizeof(*cos) * (size_t)numverts, __func__);
+               for (i = 0, co = cos, mv = verts; i < numverts; i++, co++, mv++) {
+                       copy_v3_v3(*co, mv->co);
+               }
+       }
+       unit_m4(r_mat);
+
+       /* Note: here we apply sample correction to covariance matrix, since we consider the vertices as a sample
+        *       of the whole 'surface' population of our mesh... */
+       BLI_covariance_m3_v3n(cos, numverts, true, covmat, center);
+
+       eigen_success = BLI_eigen_solve_selfadjoint_m3(covmat, eigen_val, eigen_vec);
+       BLI_assert(eigen_success);
+       UNUSED_VARS_NDEBUG(eigen_success);
+
+       /* Special handling of cases where some eigen values are (nearly) identical. */
+       if (compare_ff_relative(eigen_val[0], eigen_val[1], FLT_EPSILON, 64)) {
+           if (compare_ff_relative(eigen_val[0], eigen_val[2], FLT_EPSILON, 64)) {
+                       /* No preferred direction, that set of vertices has a spherical average,
+             * so we simply returned scaled/translated identity matrix (with no rotation). */
+                       unit_m3(eigen_vec);
+               }
+               else {
+                       /* Ellipsoid defined by eigen values/vectors has a spherical section,
+                        * we can only define one axis from eigen_vec[2] (two others computed eigen vecs
+                        * are not so nice for us here, they tend to 'randomly' rotate around valid one).
+                * Note that eigen vectors as returned by BLI_eigen_solve_selfadjoint_m3() are normalized. */
+                       ortho_basis_v3v3_v3(eigen_vec[0], eigen_vec[1], eigen_vec[2]);
+               }
+       }
+       else if (compare_ff_relative(eigen_val[0], eigen_val[2], FLT_EPSILON, 64)) {
+               /* Same as above, but with eigen_vec[1] as valid axis. */
+               ortho_basis_v3v3_v3(eigen_vec[2], eigen_vec[0], eigen_vec[1]);
+       }
+       else if (compare_ff_relative(eigen_val[1], eigen_val[2], FLT_EPSILON, 64)) {
+               /* Same as above, but with eigen_vec[0] as valid axis. */
+               ortho_basis_v3v3_v3(eigen_vec[1], eigen_vec[2], eigen_vec[0]);
+       }
+
+       for (i = 0; i < 3; i++) {
+               float evi = eigen_val[i];
+
+               /* Protect against 1D/2D degenerated cases! */
+               /* Note: not sure why we need square root of eigen values here (which are equivalent to singular values,
+                * as far as I have understood), but it seems to heavily reduce (if not completly nullify)
+                * the error due to non-uniform scalings... */
+               evi = (evi < 1e-6f && evi > -1e-6f) ? ((evi < 0.0f) ? -1e-3f : 1e-3f) : sqrtf_signed(evi);
+               mul_v3_fl(eigen_vec[i], evi);
+       }
+
+       copy_m4_m3(r_mat, eigen_vec);
+       copy_v3_v3(r_mat[3], center);
+
+       if (verts) {
+               MEM_freeN(cos);
+       }
+}
+
+/**
+ * Set r_space_transform so that best bbox of dst matches best bbox of src.
+ */
+void BKE_mesh_remap_find_best_match_from_dm(
+        const MVert *verts_dst, const int numverts_dst, DerivedMesh *dm_src, SpaceTransform *r_space_transform)
+{
+       /* Note that those are done so that we successively get actual mirror matrix (by multiplication of columns)... */
+       const float mirrors[][3] = {
+           {-1.0f,  1.0f,  1.0f},  /* -> -1,  1,  1 */
+           { 1.0f, -1.0f,  1.0f},  /* -> -1, -1,  1 */
+           { 1.0f,  1.0f, -1.0f},  /* -> -1, -1, -1 */
+           { 1.0f, -1.0f,  1.0f},  /* -> -1,  1, -1 */
+           {-1.0f,  1.0f,  1.0f},  /* ->  1,  1, -1 */
+           { 1.0f, -1.0f,  1.0f},  /* ->  1, -1, -1 */
+           { 1.0f,  1.0f, -1.0f},  /* ->  1, -1,  1 */
+           {0.0f, 0.0f, 0.0f},
+       };
+       const float (*mirr)[3];
+
+       float mat_src[4][4], mat_dst[4][4], best_mat_dst[4][4];
+       float best_match = FLT_MAX, match;
+
+       const int numverts_src = dm_src->getNumVerts(dm_src);
+       float (*vcos_src)[3] = MEM_mallocN(sizeof(*vcos_src) * (size_t)numverts_src, __func__);
+       dm_src->getVertCos(dm_src, vcos_src);
+
+       mesh_calc_eigen_matrix(NULL, vcos_src, numverts_src, mat_src);
+       mesh_calc_eigen_matrix(verts_dst, NULL, numverts_dst, mat_dst);
+
+       BLI_space_transform_global_from_matrices(r_space_transform, mat_dst, mat_src);
+       match = BKE_mesh_remap_calc_difference_from_dm(r_space_transform, verts_dst, numverts_dst, dm_src);
+       best_match = match;
+       copy_m4_m4(best_mat_dst, mat_dst);
+
+       /* And now, we have to check the otehr sixth possible mirrored versions... */
+       for (mirr = mirrors; (*mirr)[0]; mirr++) {
+               mul_v3_fl(mat_dst[0], (*mirr)[0]);
+               mul_v3_fl(mat_dst[1], (*mirr)[1]);
+               mul_v3_fl(mat_dst[2], (*mirr)[2]);
+
+               BLI_space_transform_global_from_matrices(r_space_transform, mat_dst, mat_src);
+               match = BKE_mesh_remap_calc_difference_from_dm(r_space_transform, verts_dst, numverts_dst, dm_src);
+               if (match < best_match) {
+                       best_match = match;
+                       copy_m4_m4(best_mat_dst, mat_dst);
+               }
+       }
+
+       BLI_space_transform_global_from_matrices(r_space_transform, best_mat_dst, mat_src);
+}
+
+/** \} */
+
 /** \name Mesh to mesh mapping
  * \{ */
 
@@ -147,57 +391,6 @@ static int mesh_remap_interp_poly_data_get(
        return sources_num;
 }
 
-static bool mesh_remap_bvhtree_query_nearest(
-        BVHTreeFromMesh *treedata, BVHTreeNearest *nearest,
-        float co[3], const float max_dist_sq, float *r_hit_dist)
-{
-       /* Use local proximity heuristics (to reduce the nearest search). */
-       if (nearest->index != -1) {
-               nearest->dist_sq = min_ff(len_squared_v3v3(co, nearest->co), max_dist_sq);
-       }
-       else {
-               nearest->dist_sq = max_dist_sq;
-       }
-       /* Compute and store result. If invalid (-1 index), keep FLT_MAX dist. */
-       BLI_bvhtree_find_nearest(treedata->tree, co, nearest, treedata->nearest_callback, treedata);
-
-       if ((nearest->index != -1) && (nearest->dist_sq <= max_dist_sq)) {
-               *r_hit_dist = sqrtf(nearest->dist_sq);
-               return true;
-       }
-       else {
-               return false;
-       }
-}
-
-static bool mesh_remap_bvhtree_query_raycast(
-        BVHTreeFromMesh *treedata, BVHTreeRayHit *rayhit,
-        const float co[3], const float no[3], const float radius, const float max_dist, float *r_hit_dist)
-{
-       BVHTreeRayHit rayhit_tmp;
-       float inv_no[3];
-
-       rayhit->index = -1;
-       rayhit->dist = max_dist;
-       BLI_bvhtree_ray_cast(treedata->tree, co, no, radius, rayhit, treedata->raycast_callback, treedata);
-
-       /* Also cast in the other direction! */
-       rayhit_tmp = *rayhit;
-       negate_v3_v3(inv_no, no);
-       BLI_bvhtree_ray_cast(treedata->tree, co, inv_no, radius, &rayhit_tmp, treedata->raycast_callback, treedata);
-       if (rayhit_tmp.dist < rayhit->dist) {
-               *rayhit = rayhit_tmp;
-       }
-
-       if ((rayhit->index != -1) && (rayhit->dist <= max_dist)) {
-               *r_hit_dist = rayhit->dist;
-               return true;
-       }
-       else {
-               return false;
-       }
-}
-
 /* Little helper when dealing with source islands */
 typedef struct IslandResult {
        float factor;           /* A factor, based on which best island for a given set of elements will be selected. */
index d6a1694b4b035a59d0d6bc33a14bf43ced573395..0b604a73977c906ac2fdb8c65fbafeb7cdef27b7 100644 (file)
@@ -339,6 +339,7 @@ static int data_transfer_exec(bContext *C, wmOperator *op)
        const int map_loop_mode = RNA_enum_get(op->ptr, "loop_mapping");
        const int map_poly_mode = RNA_enum_get(op->ptr, "poly_mapping");
 
+       const bool use_auto_transform = RNA_boolean_get(op->ptr, "use_auto_transform");
        const bool use_object_transform = RNA_boolean_get(op->ptr, "use_object_transform");
        const bool use_max_distance = RNA_boolean_get(op->ptr, "use_max_distance");
        const float max_distance = use_max_distance ? RNA_float_get(op->ptr, "max_distance") : FLT_MAX;
@@ -355,7 +356,7 @@ static int data_transfer_exec(bContext *C, wmOperator *op)
        const float mix_factor = RNA_float_get(op->ptr, "mix_factor");
 
        SpaceTransform space_transform_data;
-       SpaceTransform *space_transform = use_object_transform ? &space_transform_data : NULL;
+       SpaceTransform *space_transform = (use_object_transform && !use_auto_transform) ? &space_transform_data : NULL;
 
        if (reverse_transfer && ((ID *)(ob_src->data))->lib) {
                /* Do not transfer to linked data, not supported. */
@@ -384,7 +385,8 @@ static int data_transfer_exec(bContext *C, wmOperator *op)
                        if (BKE_object_data_transfer_mesh(
                                scene, ob_src, ob_dst, data_type, use_create,
                                map_vert_mode, map_edge_mode, map_loop_mode, map_poly_mode,
-                               space_transform, max_distance, ray_radius, islands_precision,
+                               space_transform, use_auto_transform,
+                               max_distance, ray_radius, islands_precision,
                                layers_select_src, layers_select_dst,
                                mix_mode, mix_factor, NULL, false, op->reports))
                        {
@@ -428,9 +430,13 @@ static bool data_transfer_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
 
        const char *prop_id = RNA_property_identifier(prop);
        const int data_type = RNA_enum_get(ptr, "data_type");
+       bool use_auto_transform = false;
        bool use_max_distance = false;
        bool use_modifier = false;
 
+       if ((prop_other = RNA_struct_find_property(ptr, "use_auto_transform"))) {
+               use_auto_transform = RNA_property_boolean_get(ptr, prop_other);
+       }
        if ((prop_other = RNA_struct_find_property(ptr, "use_max_distance"))) {
                use_max_distance = RNA_property_boolean_get(ptr, prop_other);
        }
@@ -447,6 +453,9 @@ static bool data_transfer_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop)
                return false;
        }
 
+       if (STREQ(prop_id, "use_object_transform") && use_auto_transform) {
+               return false;
+       }
        if (STREQ(prop_id, "max_distance") && !use_max_distance) {
                return false;
        }
@@ -530,6 +539,9 @@ void OBJECT_OT_data_transfer(wmOperatorType *ot)
                     "Method used to map source faces to destination ones");
 
        /* Mapping options and filtering. */
+       RNA_def_boolean(ot->srna, "use_auto_transform", false, "Auto Transform",
+                       "Automatically compute transformation to get the best possible match between source and "
+                       "destination meshes (WARNING: results will never be as good as manual matching of objects)");
        RNA_def_boolean(ot->srna, "use_object_transform", true, "Object Transform",
                        "Evaluate source and destination meshes in global space");
        RNA_def_boolean(ot->srna, "use_max_distance", false, "Only Neighbor Geometry",
index e133c4785b73c7a32d2b7cf4e491ff95a2f1a72f..85e9b4ee185813127e65d7a2a725bbdd0d3821e6 100644 (file)
@@ -208,7 +208,7 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *der
        /* Note: no islands precision for now here. */
        BKE_object_data_transfer_dm(md->scene, dtmd->ob_source, ob, dm, dtmd->data_types, false,
                             dtmd->vmap_mode, dtmd->emap_mode, dtmd->lmap_mode, dtmd->pmap_mode,
-                            space_transform, max_dist, dtmd->map_ray_radius, 0.0f,
+                            space_transform, false, max_dist, dtmd->map_ray_radius, 0.0f,
                             dtmd->layers_select_src, dtmd->layers_select_dst,
                             dtmd->mix_mode, dtmd->mix_factor, dtmd->defgrp_name, invert_vgroup, &reports);