UV Island support for vertex & edge slide
authorCampbell Barton <ideasman42@gmail.com>
Wed, 11 Feb 2015 08:40:54 +0000 (19:40 +1100)
committerCampbell Barton <ideasman42@gmail.com>
Wed, 11 Feb 2015 10:30:13 +0000 (21:30 +1100)
This ensures slide with `Correct UVs` enabled, keeps UV's (any loop custom-data) contiguous.

Adds 2 key functions `BM_vert_loop_groups_data_layer_create` and `BM_vert_loop_groups_data_layer_merge`

They work by collecting matching loop custom-data around the vertices loop-fan,
and merging the custom-data after edits are made.

Thanks to @mont29 for review!

source/blender/bmesh/intern/bmesh_interp.c
source/blender/bmesh/intern/bmesh_interp.h
source/blender/editors/transform/transform.c
source/blender/editors/transform/transform.h

index a32f28169f64263d3ac614262c0768243f187c64..45da3ce85bc562c0d60aff933bc9dd74f8c5c350 100644 (file)
@@ -40,6 +40,8 @@
 
 #include "BKE_customdata.h"
 #include "BKE_multires.h"
+#include "BLI_memarena.h"
+#include "BLI_linklist.h"
 
 #include "bmesh.h"
 #include "intern/bmesh_private.h"
@@ -893,3 +895,179 @@ void BM_elem_float_data_set(CustomData *cd, void *element, int type, const float
        float *f = CustomData_bmesh_get(cd, ((BMHeader *)element)->data, type);
        if (f) *f = val;
 }
+
+/** \name Loop interpolation functions: BM_vert_loop_groups_data_layer_***
+ *
+ * Handling loop custom-data such as UV's, while keeping contiguous fans is rather tedious.
+ * Especially when a verts loops can have multiple CustomData layers,
+ * and each layer can have multiple (different) contiguous fans.
+ * Said differently, a single vertices loops may span multiple UV islands.
+ *
+ * These functions snapshot vertices loops, storing each contiguous fan in its own group.
+ * The caller can manipulate the loops, then re-combine the CustomData values.
+ *
+ * While these functions don't explicitly handle multiple layers at once,
+ * the caller can simply store its own list.
+ *
+ * \note Currently they are averaged back together (weighted by loop angle)
+ * but we could copy add other methods to re-combine CustomData-Loop-Fans.
+ *
+ * \{ */
+
+struct LoopWalkCtx {
+       /* same for all groups */
+       int type;
+       int cd_layer_offset;
+       MemArena *arena;
+
+       /* --- Per loop fan vars --- */
+
+       /* reference for this contiguous fan */
+       const void *data_ref;
+       int data_len;
+       /* both arrays the size of the 'BM_vert_face_count(v)'
+        * each contiguous fan gets a slide of these arrays */
+       void **data_array;
+       float *weight_array;
+       /* accumulate 'LoopGroupCD.weight' to make unit length */
+       float weight_accum;
+};
+
+/* Store vars to pass into 'CustomData_bmesh_interp' */
+struct LoopGroupCD {
+       /* direct customdata pointer array */
+       void **data;
+       /* weights (aligned with 'data') */
+       float *data_weights;
+       /* number of loops in the fan */
+       int data_len;
+};
+
+static void bm_loop_walk_add(struct LoopWalkCtx *lwc, BMLoop *l)
+{
+       const float w = BM_loop_calc_face_angle(l);
+       BM_elem_flag_enable(l, BM_ELEM_INTERNAL_TAG);
+       lwc->data_array[lwc->data_len] = BM_ELEM_CD_GET_VOID_P(l, lwc->cd_layer_offset);
+       lwc->weight_array[lwc->data_len] = w;
+       lwc->weight_accum += w;
+
+       lwc->data_len += 1;
+}
+
+/**
+ * called recursively, keep stack-usage minimal.
+ *
+ * \note called for fan matching so we're pretty much safe not to break the stack
+ */
+static void bm_loop_walk_data(struct LoopWalkCtx *lwc, BMLoop *l_walk)
+{
+       BLI_assert(CustomData_data_equals(lwc->type, lwc->data_ref, BM_ELEM_CD_GET_VOID_P(l_walk, lwc->cd_layer_offset)));
+       BLI_assert(BM_elem_flag_test(l_walk, BM_ELEM_INTERNAL_TAG) == false);
+
+       bm_loop_walk_add(lwc, l_walk);
+
+#define WALK_LOOP(l_test) \
+{ \
+       BMLoop *l_other = l_test; \
+       if (l_other->v != l_walk->v) { \
+               l_other = l_other->next; \
+       } \
+       BLI_assert(l_other->v == l_walk->v); \
+       if (!BM_elem_flag_test(l_other, BM_ELEM_INTERNAL_TAG)) { \
+               if (CustomData_data_equals(lwc->type, lwc->data_ref, BM_ELEM_CD_GET_VOID_P(l_other, lwc->cd_layer_offset))) { \
+                       bm_loop_walk_data(lwc, l_other); \
+               } \
+       } \
+} (void)0
+
+       if (l_walk->radial_next != l_walk) {
+               WALK_LOOP(l_walk->radial_next);
+       }
+
+       if (l_walk->prev->radial_next != l_walk->prev) {
+               WALK_LOOP(l_walk->prev->radial_next);
+       }
+}
+
+LinkNode *BM_vert_loop_groups_data_layer_create(BMesh *bm, BMVert *v, int layer_n, MemArena *arena)
+{
+       struct LoopWalkCtx lwc;
+       LinkNode *groups = NULL;
+       BMLoop *l;
+       BMIter liter;
+       int loop_num;
+
+
+       lwc.type = bm->ldata.layers[layer_n].type;
+       lwc.cd_layer_offset = bm->ldata.layers[layer_n].offset;
+       lwc.arena = arena;
+
+       loop_num = 0;
+       BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
+               BM_elem_flag_disable(l, BM_ELEM_INTERNAL_TAG);
+               loop_num++;
+       }
+
+       lwc.data_len = 0;
+       lwc.data_array = BLI_memarena_alloc(lwc.arena, sizeof(void *) * loop_num);
+       lwc.weight_array = BLI_memarena_alloc(lwc.arena, sizeof(float) * loop_num);
+
+       BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
+               if (!BM_elem_flag_test(l, BM_ELEM_INTERNAL_TAG)) {
+                       struct LoopGroupCD *lf = BLI_memarena_alloc(lwc.arena, sizeof(*lf));
+                       int len_prev = lwc.data_len;
+
+                       lwc.data_ref = BM_ELEM_CD_GET_VOID_P(l, lwc.cd_layer_offset);
+
+                       /* assign len-last */
+                       lf->data         = &lwc.data_array[lwc.data_len];
+                       lf->data_weights = &lwc.weight_array[lwc.data_len];
+                       lwc.weight_accum = 0.0f;
+
+                       /* new group */
+                       bm_loop_walk_data(&lwc, l);
+                       lf->data_len = lwc.data_len - len_prev;
+
+                       if (LIKELY(lwc.weight_accum != 0.0f)) {
+                               mul_vn_fl(lf->data_weights, lf->data_len, 1.0f / lwc.weight_accum);
+                       }
+                       else {
+                               fill_vn_fl(lf->data_weights, lf->data_len, 1.0f / (float)lf->data_len);
+                       }
+
+                       BLI_linklist_prepend_arena(&groups, lf, lwc.arena);
+               }
+       }
+
+       BLI_assert(lwc.data_len == loop_num);
+
+       return groups;
+}
+
+static void bm_vert_loop_groups_data_layer_merge__single(BMesh *bm, void *lf_p, void *data, int type)
+{
+       struct LoopGroupCD *lf = lf_p;
+       int i;
+
+       CustomData_bmesh_interp(&bm->ldata, lf->data, lf->data_weights, NULL, lf->data_len, data);
+
+       for (i = 0; i < lf->data_len; i++) {
+               CustomData_copy_elements(type, data, lf->data[i], 1);
+       }
+}
+
+/**
+ * Take existing custom data and merge each fan's data.
+ */
+void BM_vert_loop_groups_data_layer_merge(BMesh *bm, LinkNode *groups, int layer_n)
+{
+       int type = bm->ldata.layers[layer_n].type;
+       int size = CustomData_sizeof(type);
+       void *data = alloca(size);
+
+       do {
+               bm_vert_loop_groups_data_layer_merge__single(bm, groups->link, data, type);
+       } while ((groups = groups->next));
+}
+
+/** \} */
\ No newline at end of file
index c605ad31ae7bf26c50c6f975106970350af89785..cd6d5e2731d86d4e785e36cac26260eeef590074 100644 (file)
@@ -27,6 +27,9 @@
  *  \ingroup bmesh
  */
 
+struct LinkNode;
+struct MemArena;
+
 void  BM_loop_interp_multires(BMesh *bm, BMLoop *target, BMFace *source);
 void  BM_vert_interp_from_face(BMesh *bm, BMVert *v, BMFace *source);
 
@@ -49,5 +52,7 @@ void  BM_loop_interp_from_face(BMesh *bm, BMLoop *target, BMFace *source,
                                const bool do_vertex, const bool do_multires);
 
 void  BM_face_multires_bounds_smooth(BMesh *bm, BMFace *f);
+struct LinkNode *BM_vert_loop_groups_data_layer_create(BMesh *bm, BMVert *v, int layer_n, struct MemArena *arena);
+void BM_vert_loop_groups_data_layer_merge(BMesh *bm, struct LinkNode *groups, int layer_n);
 
 #endif /* __BMESH_INTERP_H__ */
index 92103c3b29faefa11c9807264a100eb35a589590..7044b75fefd4498ea2afc58547f8c9243585afbc 100644 (file)
@@ -50,6 +50,7 @@
 #include "BLI_listbase.h"
 #include "BLI_string.h"
 #include "BLI_ghash.h"
+#include "BLI_memarena.h"
 
 #include "BKE_nla.h"
 #include "BKE_editmesh_bvh.h"
@@ -5132,7 +5133,8 @@ static void slide_origdata_init_flag(
        if ((t->settings->uvcalc_flag & UVCALC_TRANSFORM_CORRECT) &&
            /* don't do this at all for non-basis shape keys, too easy to
             * accidentally break uv maps or vertex colors then */
-           (bm->shapenr <= 1))
+           (bm->shapenr <= 1) &&
+           CustomData_has_math(&bm->ldata))
        {
                sod->use_origfaces = true;
        }
@@ -5155,26 +5157,89 @@ static void slide_origdata_init_data(
        }
 }
 
-static void slide_origdata_create_date(
+static void slide_origdata_create_data(
         TransInfo *t, SlideOrigData *sod,
-        BMVert **v_pt, unsigned int v_stride, unsigned int v_num)
+        TransDataGenericSlideVert *sv, unsigned int v_stride, unsigned int v_num)
 {
        if (sod->use_origfaces) {
                BMEditMesh *em = BKE_editmesh_from_object(t->obedit);
                BMesh *bm = em->bm;
-
                unsigned int i;
-               for (i = 0; i < v_num; i++, v_pt = (void *)(((char *)v_pt) + v_stride)) {
+
+               const int *layer_math_map;
+               int layer_index_dst;
+               int layer_groups_array_size;
+               int j;
+
+               /* over alloc, only 'math' layers are indexed */
+               sod->layer_math_map = MEM_mallocN(bm->ldata.totlayer * sizeof(int), __func__);
+               layer_index_dst = 0;
+               for (j = 0; j < bm->ldata.totlayer; j++) {
+                       if (CustomData_layer_has_math(&bm->ldata, j)) {
+                               sod->layer_math_map[layer_index_dst++] = j;
+                       }
+               }
+               BLI_assert(layer_index_dst != 0);
+               layer_math_map = sod->layer_math_map;
+               layer_groups_array_size = layer_index_dst * sizeof(void *);
+               sod->layer_math_map_num = layer_index_dst;
+
+               sod->arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+
+               for (i = 0; i < v_num; i++, sv = (void *)(((char *)sv) + v_stride)) {
                        BMIter fiter;
                        BMFace *f;
-                       BMVert *v = *v_pt;
 
-                       BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
+                       /* copy face data */
+                       BM_ITER_ELEM (f, &fiter, sv->v, BM_FACES_OF_VERT) {
                                if (!BLI_ghash_haskey(sod->origfaces, f)) {
                                        BMFace *f_copy = BM_face_copy(sod->bm_origfaces, bm, f, true, true);
                                        BLI_ghash_insert(sod->origfaces, f, f_copy);
                                }
                        }
+
+                       /* store cd_loop_groups */
+                       sv->cd_loop_groups = BLI_memarena_alloc(sod->arena, layer_groups_array_size);
+                       for (j = 0; j < layer_index_dst; j++) {
+                               const int layer_nr = layer_math_map[j];
+                               sv->cd_loop_groups[j] = BM_vert_loop_groups_data_layer_create(bm, sv->v, layer_nr, sod->arena);
+                       }
+               }
+       }
+}
+
+static void slide_origdata_interp_data(
+        TransInfo *t, SlideOrigData *sod,
+        TransDataGenericSlideVert *sv, unsigned int v_stride, unsigned int v_num,
+        bool is_final)
+{
+       if (sod->use_origfaces) {
+               BMEditMesh *em = BKE_editmesh_from_object(t->obedit);
+               unsigned int i;
+
+               const int *layer_math_map = sod->layer_math_map;
+
+               for (i = 0; i < v_num; i++, sv = (void *)(((char *)sv) + v_stride)) {
+                       BMIter fiter;
+                       BMLoop *l;
+                       int j;
+
+                       BM_ITER_ELEM (l, &fiter, sv->v, BM_LOOPS_OF_VERT) {
+                               BMFace *f_copy;  /* the copy of 'f' */
+
+                               f_copy = BLI_ghash_lookup(sod->origfaces, l->f);
+
+                               /* only loop data, no vertex data since that contains shape keys,
+                                * and we do not want to mess up other shape keys */
+                               BM_loop_interp_from_face(em->bm, l, f_copy, false, is_final);
+
+                               /* make sure face-attributes are correct (e.g. MTexPoly) */
+                               BM_elem_attrs_copy(sod->bm_origfaces, em->bm, f_copy, l->f);
+                       }
+
+                       for (j = 0; j < sod->layer_math_map_num; j++) {
+                                BM_vert_loop_groups_data_layer_merge(em->bm, sv->cd_loop_groups[j], layer_math_map[j]);
+                       }
                }
        }
 }
@@ -5192,6 +5257,13 @@ static void slide_origdata_free_date(
                        BLI_ghash_free(sod->origfaces, NULL, NULL);
                        sod->origfaces = NULL;
                }
+
+               if (sod->arena) {
+                       BLI_memarena_free(sod->arena);
+                       sod->arena = NULL;
+               }
+
+               MEM_SAFE_FREE(sod->layer_math_map);
        }
 }
 
@@ -5847,7 +5919,7 @@ static bool createEdgeSlideVerts(TransInfo *t)
 
        bmesh_edit_begin(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES);
        slide_origdata_init_data(t, &sld->orig_data);
-       slide_origdata_create_date(t, &sld->orig_data, &sld->sv->v, sizeof(*sld->sv), sld->totsv);
+       slide_origdata_create_data(t, &sld->orig_data, (TransDataGenericSlideVert *)sld->sv, sizeof(*sld->sv), sld->totsv);
 
        /*create copies of faces for customdata projection*/
        sv_array = sld->sv;
@@ -5896,147 +5968,12 @@ void projectEdgeSlideData(TransInfo *t, bool is_final)
 {
        EdgeSlideData *sld = t->customData;
        SlideOrigData *sod = &sld->orig_data;
-       TransDataEdgeSlideVert *sv;
-       BMEditMesh *em = sld->em;
-       int i;
 
        if (sod->use_origfaces == false) {
                return;
        }
 
-       for (i = 0, sv = sld->sv; i < sld->totsv; sv++, i++) {
-               BMIter fiter;
-               BMLoop *l;
-
-               BM_ITER_ELEM (l, &fiter, sv->v, BM_LOOPS_OF_VERT) {
-                       BMFace *f_copy;      /* the copy of 'f' */
-                       BMFace *f_copy_flip; /* the copy of 'f' or detect if we need to flip to the shorter side. */
-                       
-                       f_copy = BLI_ghash_lookup(sod->origfaces, l->f);
-                       
-                       /* project onto copied projection face */
-                       f_copy_flip = f_copy;
-
-                       if (BM_elem_flag_test(l->e, BM_ELEM_SELECT) || BM_elem_flag_test(l->prev->e, BM_ELEM_SELECT)) {
-                               /* the loop is attached of the selected edges that are sliding */
-                               BMLoop *l_ed_sel = l;
-
-                               if (!BM_elem_flag_test(l->e, BM_ELEM_SELECT))
-                                       l_ed_sel = l_ed_sel->prev;
-
-                               if (sld->perc < 0.0f) {
-                                       if (BM_vert_in_face(sv->v_b, l_ed_sel->radial_next->f)) {
-                                               f_copy_flip = BLI_ghash_lookup(sod->origfaces, l_ed_sel->radial_next->f);
-                                       }
-                               }
-                               else if (sld->perc > 0.0f) {
-                                       if (BM_vert_in_face(sv->v_a, l_ed_sel->radial_next->f)) {
-                                               f_copy_flip = BLI_ghash_lookup(sod->origfaces, l_ed_sel->radial_next->f);
-                                       }
-                               }
-
-                               BLI_assert(f_copy_flip != NULL);
-                               if (!f_copy_flip) {
-                                       continue;  /* shouldn't happen, but protection */
-                               }
-                       }
-                       else {
-                               /* the loop is attached to only one vertex and not a selected edge,
-                                * this means we have to find a selected edges face going in the right direction
-                                * to copy from else we get bad distortion see: [#31080] */
-                               BMIter eiter;
-                               BMEdge *e_sel;
-
-                               BLI_assert(l->v == sv->v);
-                               BM_ITER_ELEM (e_sel, &eiter, sv->v, BM_EDGES_OF_VERT) {
-                                       if (BM_elem_flag_test(e_sel, BM_ELEM_SELECT)) {
-                                               break;
-                                       }
-                               }
-
-                               if (e_sel) {
-                                       /* warning if the UV's are not contiguous, this will copy from the _wrong_ UVs
-                                        * in fact whenever the face being copied is not 'f_copy' this can happen,
-                                        * we could be a lot smarter about this but would need to deal with every UV channel or
-                                        * add a way to mask out lauers when calling #BM_loop_interp_from_face() */
-
-                                       /*
-                                        *        +    +----------------+
-                                        *         \   |                |
-                                        * (this) l_adj|                |
-                                        *           \ |                |
-                                        *            \|      e_sel     |
-                                        *  +----------+----------------+  <- the edge we are sliding.
-                                        *            /|sv->v           |
-                                        *           / |                |
-                                        *   (or) l_adj|                |
-                                        *         /   |                |
-                                        *        +    +----------------+
-                                        * (above)
-                                        * 'other connected loops', attached to sv->v slide faces.
-                                        *
-                                        * NOTE: The faces connected to the edge may not have contiguous UV's
-                                        *       so step around the loops to find l_adj.
-                                        *       However if the 'other loops' are not cotiguous it will still give problems.
-                                        *
-                                        *       A full solution to this would have to store
-                                        *       per-customdata-layer map of which loops are contiguous
-                                        *       and take this into account when interpolating.
-                                        *
-                                        * NOTE: If l_adj's edge isnt manifold then use then
-                                        *       interpolate the loop from its own face.
-                                        *       Can happen when 'other connected loops' are disconnected from the face-fan.
-                                        */
-
-                                       BMLoop *l_adj = NULL;
-                                       if (sld->perc < 0.0f) {
-                                               if (BM_vert_in_face(sv->v_b, e_sel->l->f)) {
-                                                       l_adj = e_sel->l;
-                                               }
-                                               else if (BM_vert_in_face(sv->v_b, e_sel->l->radial_next->f)) {
-                                                       l_adj = e_sel->l->radial_next;
-                                               }
-                                       }
-                                       else if (sld->perc > 0.0f) {
-                                               if (BM_vert_in_face(sv->v_a, e_sel->l->f)) {
-                                                       l_adj = e_sel->l;
-                                               }
-                                               else if (BM_vert_in_face(sv->v_a, e_sel->l->radial_next->f)) {
-                                                       l_adj = e_sel->l->radial_next;
-                                               }
-                                       }
-
-                                       /* step across to the face */
-                                       if (l_adj) {
-                                               l_adj = BM_loop_other_edge_loop(l_adj, sv->v);
-                                               if (!BM_edge_is_boundary(l_adj->e)) {
-                                                       l_adj = l_adj->radial_next;
-                                               }
-                                               else {
-                                                       /* disconnected face-fan, fallback to self */
-                                                       l_adj = l;
-                                               }
-
-                                               f_copy_flip = BLI_ghash_lookup(sod->origfaces, l_adj->f);
-                                       }
-                               }
-                       }
-
-                       /* only loop data, no vertex data since that contains shape keys,
-                        * and we do not want to mess up other shape keys */
-                       BM_loop_interp_from_face(em->bm, l, f_copy_flip, false, false);
-
-                       if (is_final) {
-                               BM_loop_interp_multires(em->bm, l, f_copy_flip);
-                               if (f_copy != f_copy_flip) {
-                                       BM_loop_interp_multires(em->bm, l, f_copy);
-                               }
-                       }
-                       
-                       /* make sure face-attributes are correct (e.g. MTexPoly) */
-                       BM_elem_attrs_copy(sod->bm_origfaces, em->bm, f_copy, l->f);
-               }
-       }
+       slide_origdata_interp_data(t, sod, (TransDataGenericSlideVert *)sld->sv, sizeof(*sld->sv), sld->totsv, is_final);
 }
 
 void freeEdgeSlideTempFaces(EdgeSlideData *sld)
@@ -6544,7 +6481,7 @@ static bool createVertSlideVerts(TransInfo *t)
 
        bmesh_edit_begin(bm, BMO_OPTYPE_FLAG_UNTAN_MULTIRES);
        slide_origdata_init_data(t, &sld->orig_data);
-       slide_origdata_create_date(t, &sld->orig_data, &sld->sv->v, sizeof(*sld->sv), sld->totsv);
+       slide_origdata_create_data(t, &sld->orig_data, (TransDataGenericSlideVert *)sld->sv, sizeof(*sld->sv), sld->totsv);
 
        sld->em = em;
 
@@ -6564,42 +6501,12 @@ void projectVertSlideData(TransInfo *t, bool is_final)
 {
        VertSlideData *sld = t->customData;
        SlideOrigData *sod = &sld->orig_data;
-       TransDataVertSlideVert *sv;
-       BMEditMesh *em = sld->em;
-       int i;
 
        if (sod->use_origfaces == false) {
                return;
        }
 
-       for (i = 0, sv = sld->sv; i < sld->totsv; sv++, i++) {
-               BMIter fiter;
-               BMLoop *l;
-
-               BM_ITER_ELEM (l, &fiter, sv->v, BM_LOOPS_OF_VERT) {
-                       BMFace *f_copy;      /* the copy of 'f' */
-                       BMFace *f_copy_flip; /* the copy of 'f' or detect if we need to flip to the shorter side. */
-
-                       f_copy = BLI_ghash_lookup(sod->origfaces, l->f);
-
-                       /* project onto copied projection face */
-                       f_copy_flip = f_copy;
-
-                       /* only loop data, no vertex data since that contains shape keys,
-                        * and we do not want to mess up other shape keys */
-                       BM_loop_interp_from_face(em->bm, l, f_copy_flip, false, false);
-
-                       if (is_final) {
-                               BM_loop_interp_multires(em->bm, l, f_copy_flip);
-                               if (f_copy != f_copy_flip) {
-                                       BM_loop_interp_multires(em->bm, l, f_copy);
-                               }
-                       }
-
-                       /* make sure face-attributes are correct (e.g. MTexPoly) */
-                       BM_elem_attrs_copy(sod->bm_origfaces, em->bm, f_copy, l->f);
-               }
-       }
+       slide_origdata_interp_data(t, sod, (TransDataGenericSlideVert *)sld->sv, sizeof(*sld->sv), sld->totsv, is_final);
 }
 
 void freeVertSlideTempFaces(VertSlideData *sld)
index 2330ec6e79e2579423be92a80f0e515a67b1a705..84a81e8b3d82406c437617998fcaa75ef0af61bb 100644 (file)
@@ -200,9 +200,19 @@ typedef struct TransDataNla {
 struct LinkNode;
 struct GHash;
 
+/* header of TransDataEdgeSlideVert, TransDataEdgeSlideEdge */
+typedef struct TransDataGenericSlideVert {
+       struct BMVert *v;
+       struct LinkNode **cd_loop_groups;
+} TransDataGenericSlideVert;
+
 typedef struct TransDataEdgeSlideVert {
-       struct BMVert *v_a, *v_b;
+       /* TransDataGenericSlideVert */
        struct BMVert *v;
+       struct LinkNode **cd_loop_groups;
+       /* end generic */
+
+       struct BMVert *v_a, *v_b;
        float v_co_orig[3];
 
        float edge_len;
@@ -220,6 +230,13 @@ typedef struct SlideOrigData {
        bool use_origfaces;
        struct GHash    *origfaces;
        struct BMesh *bm_origfaces;
+
+       struct MemArena *arena;
+       /* number of math BMLoop layers */
+       int  layer_math_map_num;
+       /* array size of 'layer_math_map_num'
+        * maps TransDataVertSlideVert.cd_group index to absolute CustomData layer index */
+       int *layer_math_map;
 } SlideOrigData;
 
 typedef struct EdgeSlideData {
@@ -241,7 +258,11 @@ typedef struct EdgeSlideData {
 
 
 typedef struct TransDataVertSlideVert {
+       /* TransDataGenericSlideVert */
        BMVert *v;
+       struct LinkNode **cd_loop_groups;
+       /* end generic */
+
        float   co_orig_3d[3];
        float   co_orig_2d[2];
        float (*co_link_orig_3d)[3];