diff --git a/scripts/startup/bl_ui/space_image.py b/scripts/startup/bl_ui/space_image.py index 477468a64b6..f1d3a729af5 100644 --- a/scripts/startup/bl_ui/space_image.py +++ b/scripts/startup/bl_ui/space_image.py @@ -531,6 +531,16 @@ class IMAGE_MT_uvs_context_menu(Menu): # UV Edit Mode if sima.show_uvedit: + ts = context.tool_settings + if ts.use_uv_select_sync: + is_vert_mode, is_edge_mode, _ = ts.mesh_select_mode + else: + uv_select_mode = ts.uv_select_mode + is_vert_mode = uv_select_mode == 'VERTEX' + is_edge_mode = uv_select_mode == 'EDGE' + # is_face_mode = uv_select_mode == 'FACE' + # is_island_mode = uv_select_mode == 'ISLAND' + # Add layout.operator("uv.unwrap") layout.operator("uv.follow_active_quads") @@ -552,6 +562,16 @@ class IMAGE_MT_uvs_context_menu(Menu): layout.operator_enum("uv.align", "axis") # W, 2/3/4. + layout.operator_context = 'INVOKE_DEFAULT' + + if is_vert_mode: + layout.operator("transform.vert_slide") + + if is_edge_mode: + layout.operator("transform.edge_slide") + + layout.operator_context = 'EXEC_REGION_WIN' + layout.separator() # Remove diff --git a/source/blender/editors/transform/transform.cc b/source/blender/editors/transform/transform.cc index ebbae0d8c0c..81ac200ac84 100644 --- a/source/blender/editors/transform/transform.cc +++ b/source/blender/editors/transform/transform.cc @@ -674,7 +674,7 @@ static bool transform_modal_item_poll(const wmOperator *op, int value) return t->data_type == &TransConvertType_Tracking; } if (value == TFM_MODAL_VERT_EDGE_SLIDE && - (t->data_type != &TransConvertType_Mesh || + (!ELEM(t->data_type, &TransConvertType_Mesh, &TransConvertType_MeshUV) || /* WORKAROUND: Avoid repeated keys in status bar. * * Previously, `Vert/Edge Slide` and `Move` were triggered by the same modal key. @@ -1386,7 +1386,7 @@ int transformEvent(TransInfo *t, const wmEvent *event) break; case EVT_LEFTALTKEY: case EVT_RIGHTALTKEY: - if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D)) { + if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D, SPACE_IMAGE)) { t->flag |= T_ALT_TRANSFORM; t->redraw |= TREDRAW_HARD; handled = true; @@ -1403,8 +1403,8 @@ int transformEvent(TransInfo *t, const wmEvent *event) switch (event->type) { case EVT_LEFTALTKEY: case EVT_RIGHTALTKEY: - /* TODO: Modal Map. */ - if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D)) { + /* TODO: Modal Map */ + if (ELEM(t->spacetype, SPACE_SEQ, SPACE_VIEW3D, SPACE_IMAGE)) { t->flag &= ~T_ALT_TRANSFORM; t->redraw |= TREDRAW_HARD; handled = true; diff --git a/source/blender/editors/transform/transform_convert.hh b/source/blender/editors/transform/transform_convert.hh index 69b027b6a21..5145ad2bc4e 100644 --- a/source/blender/editors/transform/transform_convert.hh +++ b/source/blender/editors/transform/transform_convert.hh @@ -46,6 +46,7 @@ struct TransConvertTypeInfo { /** * Structure used for Edge Slide operation. + * The data is filled based on the 'transform_convert_' type. */ struct TransDataEdgeSlideVert { TransData *td; @@ -61,6 +62,7 @@ struct TransDataEdgeSlideVert { /** * Structure used for Vert Slide operation. + * The data is filled based on the 'transform_convert_' type. */ struct TransDataVertSlideVert { TransData *td; @@ -292,6 +294,14 @@ extern TransConvertTypeInfo TransConvertType_MeshSkin; extern TransConvertTypeInfo TransConvertType_MeshUV; +blender::Array transform_mesh_uv_vert_slide_data_create( + const TransInfo *t, + TransDataContainer *tc, + blender::Vector &r_loc_dst_buffer); + +blender::Array transform_mesh_uv_edge_slide_data_create( + const TransInfo *t, TransDataContainer *tc, int *r_group_len); + /* `transform_convert_mesh_vert_cdata.cc` */ extern TransConvertTypeInfo TransConvertType_MeshVertCData; diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.cc b/source/blender/editors/transform/transform_convert_mesh_uv.cc index c99d29b0d5d..0de16984e7c 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.cc +++ b/source/blender/editors/transform/transform_convert_mesh_uv.cc @@ -9,6 +9,7 @@ #include "MEM_guardedalloc.h" #include "BLI_linklist_stack.h" +#include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" @@ -26,6 +27,8 @@ #include "transform.hh" #include "transform_convert.hh" +using namespace blender; + /* -------------------------------------------------------------------- */ /** \name UVs Transform Creation * \{ */ @@ -35,6 +38,7 @@ static void UVsToTransData(const float aspect[2], const float *center, const float calc_dist, const bool selected, + BMLoop *l, TransData *r_td, TransData2D *r_td2d) { @@ -68,6 +72,7 @@ static void UVsToTransData(const float aspect[2], } unit_m3(r_td->mtx); unit_m3(r_td->smtx); + r_td->extra = l; } /** @@ -380,7 +385,7 @@ static void createTransUVs(bContext *C, TransInfo *t) } luv = (float(*)[2])BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); - UVsToTransData(t->aspect, *luv, center, prop_distance, selected, td++, td2d++); + UVsToTransData(t->aspect, *luv, center, prop_distance, selected, l, td++, td2d++); } } @@ -472,6 +477,675 @@ static void recalcData_uv(TransInfo *t) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name API for Vert and Edge Slide + * \{ */ + +struct UVGroups { + int sd_len; + + private: + Vector groups_offs_buffer; + Vector groups_offs_indices; + + public: + void init(const TransDataContainer *tc, BMesh *bm, const BMUVOffsets &offsets) + { + /* To identify #TransData by the corner, we first need to set all values in `index` to `-1`. */ + BMIter fiter; + BMIter liter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + BMLoop *l; + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + BM_elem_index_set(l, -1); + } + } + + /* Now, count and set the index for the corners being transformed. */ + this->sd_len = 0; + TransData *td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + if (!(td->flag & TD_SELECTED)) { + /* The selected ones are sorted at the beginning. */ + break; + } + this->sd_len++; + + BMLoop *l = static_cast(td->extra); + BM_elem_index_set(l, i); + } + bm->elem_index_dirty |= BM_LOOP; + + /* Create the groups. */ + this->groups_offs_buffer.reserve(this->sd_len); + this->groups_offs_indices.reserve((this->sd_len / 4) + 2); + + td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + BMLoop *l_orig = static_cast(td->extra); + if (BM_elem_index_get(l_orig) == -1) { + /* Already added to a group. */ + continue; + } + + const float2 &uv_orig = BM_ELEM_CD_GET_FLOAT_P(l_orig, offsets.uv); + this->groups_offs_indices.append(this->groups_offs_buffer.size()); + + BMIter liter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &liter, l_orig->v, BM_LOOPS_OF_VERT) { + if (BM_elem_index_get(l_iter) == -1) { + /* Already added to a group or not participating in the transformation. */ + continue; + } + + if (l_orig != l_iter && + !compare_v2v2(uv_orig, BM_ELEM_CD_GET_FLOAT_P(l_iter, offsets.uv), FLT_EPSILON)) + { + /* Non-connected. */ + continue; + } + + this->groups_offs_buffer.append(BM_elem_index_get(l_iter)); + BM_elem_index_set(l_iter, -1); + } + } + this->groups_offs_indices.append(this->groups_offs_buffer.size()); + } + + OffsetIndices groups() const + { + return OffsetIndices(this->groups_offs_indices); + } + + Span td_indices_get(const int group_index) const + { + return this->groups_offs_buffer.as_span().slice(this->groups()[group_index]); + } + + Array sd_array_create_and_init(TransDataContainer *tc) + { + Array r_sv(this->sd_len); + TransDataVertSlideVert *sv = &r_sv[0]; + for (const int group_index : this->groups().index_range()) { + for (int td_index : this->td_indices_get(group_index)) { + TransData *td = &tc->data[td_index]; + sv->td = td; + sv++; + } + } + + return r_sv; + } + + Array sd_array_create_and_init_edge(TransDataContainer *tc) + { + Array r_sv(this->sd_len); + TransDataEdgeSlideVert *sv = &r_sv[0]; + for (const int group_index : this->groups().index_range()) { + for (int td_index : this->td_indices_get(group_index)) { + TransData *td = &tc->data[td_index]; + sv->td = td; + sv->dir_side[0] = float3(0); + sv->dir_side[1] = float3(0); + sv->loop_nr = -1; + sv++; + } + } + + return r_sv; + } + + MutableSpan sd_group_get(MutableSpan sd_array, + const int group_index) + { + return sd_array.slice(this->groups()[group_index]); + } + + MutableSpan sd_group_get(MutableSpan sd_array, + const int group_index) + { + return sd_array.slice(this->groups()[group_index]); + } +}; + +static UVGroups *mesh_uv_groups_get(TransDataContainer *tc, BMesh *bm, const BMUVOffsets &offsets) +{ + UVGroups *uv_groups = static_cast(tc->custom.type.data); + if (uv_groups == nullptr) { + uv_groups = MEM_new(__func__); + uv_groups->init(tc, bm, offsets); + + /* Edge Slide and Vert Slide are often called in sequence, so, to avoid recalculating the + * groups, save them in the #TransDataContainer. */ + + tc->custom.type.data = uv_groups; + tc->custom.type.free_cb = [](TransInfo *, TransDataContainer *, TransCustomData *custom_data) { + UVGroups *data = static_cast(custom_data->data); + MEM_delete(data); + custom_data->data = nullptr; + }; + } + + return uv_groups; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name API for Vert Slide + * \{ */ + +Array transform_mesh_uv_vert_slide_data_create( + const TransInfo *t, TransDataContainer *tc, Vector &r_loc_dst_buffer) +{ + + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BMesh *bm = em->bm; + const BMUVOffsets offsets = BM_uv_map_get_offsets(bm); + + UVGroups *uv_groups = mesh_uv_groups_get(tc, bm, offsets); + + Array r_sv = uv_groups->sd_array_create_and_init(tc); + + r_loc_dst_buffer.reserve(r_sv.size() * 4); + + for (const int group_index : uv_groups->groups().index_range()) { + const int size_prev = r_loc_dst_buffer.size(); + + for (int td_index : uv_groups->td_indices_get(group_index)) { + TransData *td = &tc->data[td_index]; + BMLoop *l = static_cast(td->extra); + + for (BMLoop *l_dst : {l->prev, l->next}) { + const float2 &uv_dest = BM_ELEM_CD_GET_FLOAT_P(l_dst, offsets.uv); + Span uvs_added = r_loc_dst_buffer.as_span().drop_front(size_prev); + + bool skip = std::any_of( + uvs_added.begin(), uvs_added.end(), [&](const float3 &uv_dest_added) { + return compare_v2v2(uv_dest, uv_dest_added, FLT_EPSILON); + }); + + if (!skip) { + r_loc_dst_buffer.append(float3(uv_dest, 0.0f)); + } + } + } + + const int size_new = r_loc_dst_buffer.size() - size_prev; + for (TransDataVertSlideVert &sv : uv_groups->sd_group_get(r_sv, group_index)) { + /* The buffer address may change as the vector is resized. Avoid setting #Span now. */ + // sv.targets = r_loc_dst_buffer.as_span().drop_front(size_prev); + + /* Store the buffer slice temporarily in `target_curr`. */ + sv.co_link_orig_3d = {static_cast(POINTER_FROM_INT(size_prev)), size_new}; + sv.co_link_curr = 0; + } + } + + if (t->aspect[0] != 1.0f || t->aspect[1] != 1.0f) { + for (float3 &dest : r_loc_dst_buffer) { + dest[0] *= t->aspect[0]; + dest[1] *= t->aspect[1]; + } + } + + for (TransDataVertSlideVert &sv : r_sv) { + int start = POINTER_AS_INT(sv.co_link_orig_3d.data()); + sv.co_link_orig_3d = r_loc_dst_buffer.as_span().slice(start, sv.co_link_orig_3d.size()); + } + + return r_sv; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name API for Edge Slide + * \{ */ + +/* Check if the UV group is a vertex between 2 faces. */ +static bool mesh_uv_group_is_inner(const TransDataContainer *tc, + const BMUVOffsets &offsets, + Span group) +{ + if (group.size() == 1) { + return false; + } + if (group.size() > 2) { + return false; + } + + TransData *td_a = &tc->data[group[0]]; + TransData *td_b = &tc->data[group[1]]; + BMLoop *l_a = static_cast(td_a->extra); + BMLoop *l_b = static_cast(td_b->extra); + BMLoop *l_a_prev = l_a->prev; + BMLoop *l_a_next = l_a->next; + BMLoop *l_b_prev = l_b->next; + BMLoop *l_b_next = l_b->prev; + if (l_a_prev->v != l_b_prev->v) { + std::swap(l_b_prev, l_b_next); + if (l_a_prev->v != l_b_prev->v) { + return false; + } + } + + if (l_a_next->v != l_b_next->v) { + return false; + } + + const float2 &uv_a_prev = BM_ELEM_CD_GET_FLOAT_P(l_a_prev, offsets.uv); + const float2 &uv_b_prev = BM_ELEM_CD_GET_FLOAT_P(l_b_prev, offsets.uv); + if (!compare_v2v2(uv_a_prev, uv_b_prev, FLT_EPSILON)) { + return false; + } + + const float2 &uv_a_next = BM_ELEM_CD_GET_FLOAT_P(l_a_next, offsets.uv); + const float2 &uv_b_next = BM_ELEM_CD_GET_FLOAT_P(l_b_next, offsets.uv); + if (!compare_v2v2(uv_a_next, uv_b_next, FLT_EPSILON)) { + return false; + } + + return true; +} + +/** + * Find the closest point on the ngon on the opposite side. + * used to set the edge slide distance for ngons. + */ +static bool bm_loop_uv_calc_opposite_co(const BMLoop *l_tmp, + const float2 &uv_tmp, + const BMUVOffsets &offsets, + const float2 &ray_direction, + float2 &r_co) +{ + /* skip adjacent edges */ + BMLoop *l_first = l_tmp->next; + BMLoop *l_last = l_tmp->prev; + BMLoop *l_iter; + float dist_sq_best = FLT_MAX; + bool found = false; + + l_iter = l_first; + do { + const float2 &uv_iter = BM_ELEM_CD_GET_FLOAT_P(l_iter, offsets.uv); + const float2 &uv_iter_next = BM_ELEM_CD_GET_FLOAT_P(l_iter->next, offsets.uv); + float lambda; + if (isect_ray_seg_v2(uv_tmp, ray_direction, uv_iter, uv_iter_next, &lambda, nullptr) || + isect_ray_seg_v2(uv_tmp, -ray_direction, uv_iter, uv_iter_next, &lambda, nullptr)) + { + float2 isect_co = uv_tmp + ray_direction * lambda; + /* likelihood of multiple intersections per ngon is quite low, + * it would have to loop back on itself, but better support it + * so check for the closest opposite edge */ + const float dist_sq_test = math::distance_squared(uv_tmp, isect_co); + if (dist_sq_test < dist_sq_best) { + r_co = isect_co; + dist_sq_best = dist_sq_test; + found = true; + } + } + } while ((l_iter = l_iter->next) != l_last); + + return found; +} + +static float2 isect_face_dst(const BMLoop *l, + const float2 &uv, + const float2 &aspect, + const BMUVOffsets &offsets) +{ + BMFace *f = l->f; + BMLoop *l_next = l->next; + if (f->len == 4) { + /* we could use code below, but in this case + * sliding diagonally across the quad works well */ + return BM_ELEM_CD_GET_FLOAT_P(l_next->next, offsets.uv); + } + + BMLoop *l_prev = l->prev; + const float2 &uv_prev = BM_ELEM_CD_GET_FLOAT_P(l_prev, offsets.uv); + const float2 &uv_next = BM_ELEM_CD_GET_FLOAT_P(l_next, offsets.uv); + + float2 ray_dir = (uv - uv_prev) + (uv_next - uv); + ray_dir = math::orthogonal(ray_dir * aspect); + ray_dir[0] /= aspect[0]; + ray_dir[1] /= aspect[1]; + + float2 isect_co; + if (!bm_loop_uv_calc_opposite_co(l, uv, offsets, ray_dir, isect_co)) { + /* Rare case. */ + mid_v3_v3v3(isect_co, l->prev->v->co, l_next->v->co); + } + return isect_co; +} + +Array transform_mesh_uv_edge_slide_data_create(const TransInfo *t, + TransDataContainer *tc, + int *r_group_len) +{ + Array r_sv; + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BMesh *bm = em->bm; + const BMUVOffsets offsets = BM_uv_map_get_offsets(bm); + + const bool check_edge = ED_uvedit_select_mode_get(t->scene) == UV_SELECT_EDGE; + + UVGroups *uv_groups = mesh_uv_groups_get(tc, bm, offsets); + Array groups_linked(uv_groups->groups().size(), int2(-1, -1)); + + { + /* Identify the group to which a loop belongs through the element's index value. */ + + /* First we just need to "clean up" the neighboring loops. + * This way we can identify where a group of sliding edges starts and where it ends. */ + TransData *td = tc->data; + for (int i = 0; i < tc->data_len; i++, td++) { + if (!(td->flag & TD_SELECTED)) { + /* The selected ones are sorted at the beginning. */ + break; + } + BMLoop *l = static_cast(td->extra); + BM_elem_index_set(l->prev, -1); + BM_elem_index_set(l->next, -1); + } + + /* Now set the group indexes. */ + for (const int group_index : uv_groups->groups().index_range()) { + for (int td_index : uv_groups->td_indices_get(group_index)) { + TransData *td = &tc->data[td_index]; + BMLoop *l = static_cast(td->extra); + BM_elem_index_set(l, group_index); + } + } + bm->elem_index_dirty |= BM_LOOP; + } + + for (const int group_index : uv_groups->groups().index_range()) { + int2 &group_linked_pair = groups_linked[group_index]; + + for (int td_index : uv_groups->td_indices_get(group_index)) { + TransData *td = &tc->data[td_index]; + BMLoop *l = static_cast(td->extra); + + for (BMLoop *l_dst : {l->prev, l->next}) { + const int group_index_dst = BM_elem_index_get(l_dst); + if (group_index_dst == -1) { + continue; + } + + if (ELEM(group_index_dst, group_linked_pair[0], group_linked_pair[1])) { + continue; + } + + if (check_edge) { + BMLoop *l_edge = l_dst == l->prev ? l_dst : l; + if (!uvedit_edge_select_test_ex(t->settings, l_edge, offsets)) { + continue; + } + } + + if (group_linked_pair[1] != -1) { + /* For Edge Slide, the vertex can only be connected to a maximum of 2 sliding edges. */ + return r_sv; + } + const int slot = int(group_linked_pair[0] != -1); + group_linked_pair[slot] = group_index_dst; + } + } + + if (group_linked_pair[0] == -1) { + /* For Edge Slide, the vertex must be connected to at least 1 sliding edge. */ + return r_sv; + } + } + + /* Alloc and initialize the #TransDataEdgeSlideVert. */ + r_sv = uv_groups->sd_array_create_and_init_edge(tc); + + /* Compute the sliding groups. */ + int loop_nr = 0; + for (int i : r_sv.index_range()) { + if (r_sv[i].loop_nr != -1) { + /* This vertex has already been computed. */ + continue; + } + + BMLoop *l = static_cast(r_sv[i].td->extra); + int group_index = BM_elem_index_get(l); + + /* Start from a vertex connected to just a single edge or any if it doesn't exist. */ + int i_curr = group_index; + int i_prev = groups_linked[group_index][1]; + while (!ELEM(i_prev, -1, group_index)) { + int tmp = groups_linked[i_prev][0] != i_curr ? groups_linked[i_prev][0] : + groups_linked[i_prev][1]; + i_curr = i_prev; + i_prev = tmp; + } + + /** + * We need at least 3 points to calculate the intersection of + * `prev`-`curr` and `next`-`curr` destinations. + * + * | | | + * | | | + * prev ---- curr ---- next + */ + struct SlideTempDataUV { + int i; /* The group index. */ + struct { + BMFace *f; + float2 dst; + } fdata[2]; + bool vert_is_inner; /* In the middle of two faces. */ + /** + * Find the best direction to slide among the ones already computed. + * + * \param curr_prev: prev state of the #SlideTempDataUV where the faces are linked to the + previous edge. + * \param l_src: the source corner in the edge to slide. + * \param l_dst: the current destination corner. + */ + int find_best_dir(const SlideTempDataUV *curr_side_other, + const BMLoop *l_src, + const BMLoop *l_dst, + const float2 &src, + const float2 &dst, + bool *r_do_isect_curr_dirs) const + { + *r_do_isect_curr_dirs = false; + const BMFace *f_curr = l_src->f; + if (curr_side_other->fdata[0].f && + (curr_side_other->fdata[0].f == f_curr || + compare_v2v2(dst, curr_side_other->fdata[0].dst, FLT_EPSILON))) + { + return 0; + } + + if (curr_side_other->fdata[1].f && + (curr_side_other->fdata[1].f == f_curr || + compare_v2v2(dst, curr_side_other->fdata[1].dst, FLT_EPSILON))) + { + return 1; + } + + if (curr_side_other->fdata[0].f || curr_side_other->fdata[1].f) { + /* Find the best direction checking the edges that share faces between them. */ + int best_dir = -1; + const BMLoop *l_edge_dst = l_src->prev == l_dst ? l_src->prev : l_src; + const BMLoop *l_other = l_edge_dst->radial_next; + while (l_other != l_edge_dst) { + if (l_other->f == curr_side_other->fdata[0].f) { + best_dir = 0; + break; + } + if (l_other->f == curr_side_other->fdata[1].f) { + best_dir = 1; + break; + } + l_other = (l_other->v == l_src->v ? l_other->prev : l_other->next)->radial_next; + } + + if (best_dir != -1) { + *r_do_isect_curr_dirs = true; + return best_dir; + } + } + + if (ELEM(nullptr, this->fdata[0].f, this->fdata[1].f)) { + return int(this->fdata[0].f != nullptr); + } + + /* Find the closest direction. */ + *r_do_isect_curr_dirs = true; + + float2 dir_curr = dst - src; + float2 dir0 = math::normalize(this->fdata[0].dst - src); + float2 dir1 = math::normalize(this->fdata[1].dst - src); + float dot0 = math::dot(dir_curr, dir0); + float dot1 = math::dot(dir_curr, dir1); + return int(dot0 < dot1); + } + } prev = {}, curr = {}, next = {}, tmp = {}; + + curr.i = i_curr; + curr.vert_is_inner = mesh_uv_group_is_inner(tc, offsets, uv_groups->td_indices_get(curr.i)); + + /* Do not compute `prev` for now. Let the loop calculate `curr` twice. */ + prev.i = -1; + + while (curr.i != -1) { + int tmp_i = prev.i == -1 ? i_prev : prev.i; + next.i = groups_linked[curr.i][0] != tmp_i ? groups_linked[curr.i][0] : + groups_linked[curr.i][1]; + if (next.i != -1) { + next.vert_is_inner = mesh_uv_group_is_inner( + tc, offsets, uv_groups->td_indices_get(next.i)); + + tmp = curr; + Span td_indices_next = uv_groups->td_indices_get(next.i); + + for (int td_index_curr : uv_groups->td_indices_get(curr.i)) { + BMLoop *l_curr = static_cast(tc->data[td_index_curr].extra); + const float2 &src = BM_ELEM_CD_GET_FLOAT_P(l_curr, offsets.uv); + + for (int td_index_next : td_indices_next) { + BMLoop *l_next = static_cast(tc->data[td_index_next].extra); + if (l_curr->f != l_next->f) { + continue; + } + + BLI_assert(l_curr != l_next); + + BMLoop *l1_dst, *l2_dst; + if (l_curr->next == l_next) { + l1_dst = l_curr->prev; + l2_dst = l_next->next; + } + else { + l1_dst = l_curr->next; + l2_dst = l_next->prev; + } + + const float2 &dst = BM_ELEM_CD_GET_FLOAT_P(l1_dst, offsets.uv); + + /* Sometimes the sliding direction may fork (`isect_curr_dirs` is `true`). + * In this case, the resulting direction is the intersection of the destinations. */ + bool isect_curr_dirs = false; + + /* Identify the slot to slide according to the directions already computed in `curr`. + */ + int best_dir = curr.find_best_dir(&tmp, l_curr, l1_dst, src, dst, &isect_curr_dirs); + + if (curr.fdata[best_dir].f == nullptr) { + curr.fdata[best_dir].f = l_curr->f; + if (curr.vert_is_inner) { + curr.fdata[best_dir].dst = isect_face_dst(l_curr, src, t->aspect, offsets); + } + else { + curr.fdata[best_dir].dst = dst; + } + } + + /* Compute `next`. */ + next.fdata[best_dir].f = l_curr->f; + if (BM_elem_index_get(l2_dst) != -1 || next.vert_is_inner) { + /* Case where the vertex slides over the face. */ + const float2 &src_next = BM_ELEM_CD_GET_FLOAT_P(l_next, offsets.uv); + next.fdata[best_dir].dst = isect_face_dst(l_next, src_next, t->aspect, offsets); + } + else { + /* Case where the vertex slides over an edge. */ + const float2 &dst_next = BM_ELEM_CD_GET_FLOAT_P(l2_dst, offsets.uv); + next.fdata[best_dir].dst = dst_next; + } + + if (isect_curr_dirs) { + /* The `best_dir` can only have one direction. */ + const float2 &dst0 = prev.fdata[best_dir].dst; + const float2 &dst1 = curr.fdata[best_dir].dst; + const float2 &dst2 = dst; + const float2 &dst3 = next.fdata[best_dir].dst; + if (isect_line_line_v2_point(dst0, dst1, dst2, dst3, curr.fdata[best_dir].dst) == + ISECT_LINE_LINE_COLINEAR) + { + curr.fdata[best_dir].dst = math::midpoint(dst1, dst2); + } + } + /* There is only one pair of corners to slide per face, we don't need to keep checking + * `if (f_curr != l_next->f)`. */ + break; + } + } + } + + TransDataEdgeSlideVert *sv_first = nullptr; + for (TransDataEdgeSlideVert &sv : uv_groups->sd_group_get(r_sv, curr.i)) { + if (sv_first) { + TransData *td = sv.td; + sv = *sv_first; + sv.td = td; + } + else { + sv_first = &sv; + float2 iloc = sv.td->iloc; + const float2 &aspect = t->aspect; + if (curr.fdata[0].f) { + float2 dst = curr.fdata[0].dst * aspect; + sv.dir_side[0] = float3(dst - iloc, 0.0f); + } + if (curr.fdata[1].f) { + float2 dst = curr.fdata[1].dst * aspect; + sv.dir_side[1] = float3(dst - iloc, 0.0f); + } + sv.edge_len = math::distance(sv.dir_side[0], sv.dir_side[1]); + sv.loop_nr = loop_nr; + } + } + + if (i_prev != -1 && prev.i == i_prev) { + /* Cycle returned to the beginning. + * The data with index `i_curr` was computed twice to make sure the directions are + * correct the second time. */ + break; + } + + /* Move forward. */ + prev = curr; + curr = next; + next.fdata[0].f = next.fdata[1].f = nullptr; + } + loop_nr++; + } + *r_group_len = loop_nr; + return r_sv; +} + +/** \} */ + TransConvertTypeInfo TransConvertType_MeshUV = { /*flags*/ (T_EDIT | T_POINTS | T_2D_EDIT), /*create_trans_data*/ createTransUVs, diff --git a/source/blender/editors/transform/transform_mode_edge_slide.cc b/source/blender/editors/transform/transform_mode_edge_slide.cc index 496fcc65857..08da69567af 100644 --- a/source/blender/editors/transform/transform_mode_edge_slide.cc +++ b/source/blender/editors/transform/transform_mode_edge_slide.cc @@ -334,7 +334,12 @@ static EdgeSlideData *createEdgeSlideVerts(TransInfo *t, { int group_len; EdgeSlideData *sld = MEM_new("sld"); - sld->sv = transform_mesh_edge_slide_data_create(tc, &group_len); + if (t->data_type == &TransConvertType_MeshUV) { + sld->sv = transform_mesh_uv_edge_slide_data_create(t, tc, &group_len); + } + else { + sld->sv = transform_mesh_edge_slide_data_create(tc, &group_len); + } if (sld->sv.is_empty()) { MEM_delete(sld); diff --git a/source/blender/editors/transform/transform_mode_vert_slide.cc b/source/blender/editors/transform/transform_mode_vert_slide.cc index 02d2c1cc8dd..ade2e33d130 100644 --- a/source/blender/editors/transform/transform_mode_vert_slide.cc +++ b/source/blender/editors/transform/transform_mode_vert_slide.cc @@ -178,7 +178,12 @@ static void calcVertSlideCustomPoints(TransInfo *t) static VertSlideData *createVertSlideVerts(TransInfo *t, TransDataContainer *tc) { VertSlideData *sld = MEM_new(__func__); - sld->sv = transform_mesh_vert_slide_data_create(tc, sld->targets_buffer); + if (t->data_type == &TransConvertType_MeshUV) { + sld->sv = transform_mesh_uv_vert_slide_data_create(t, tc, sld->targets_buffer); + } + else { + sld->sv = transform_mesh_vert_slide_data_create(tc, sld->targets_buffer); + } if (sld->sv.is_empty()) { MEM_delete(sld); diff --git a/source/blender/editors/transform/transform_ops.cc b/source/blender/editors/transform/transform_ops.cc index f82c7db1d27..5c74788f199 100644 --- a/source/blender/editors/transform/transform_ops.cc +++ b/source/blender/editors/transform/transform_ops.cc @@ -1197,7 +1197,7 @@ static void TRANSFORM_OT_edge_slide(wmOperatorType *ot) ot->exec = transform_exec; ot->modal = transform_modal; ot->cancel = transform_cancel; - ot->poll = ED_operator_editmesh_region_view3d; + ot->poll = ED_operator_editmesh; ot->poll_property = transform_poll_property; RNA_def_float_factor(ot->srna, "value", 0, -10.0f, 10.0f, "Factor", "", -1.0f, 1.0f); @@ -1235,7 +1235,7 @@ static void TRANSFORM_OT_vert_slide(wmOperatorType *ot) ot->exec = transform_exec; ot->modal = transform_modal; ot->cancel = transform_cancel; - ot->poll = ED_operator_editmesh_region_view3d; + ot->poll = ED_operator_editmesh; ot->poll_property = transform_poll_property; RNA_def_float_factor(ot->srna, "value", 0, -10.0f, 10.0f, "Factor", "", -1.0f, 1.0f);