From 6a10e69d270bc6866dd0000ffc0b81f6205ba2e9 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 26 Aug 2020 13:13:06 +1000 Subject: UV: match 3D mesh editing behavior for edge-loop select - Cycling between part of the boundary & the entire UV boundary. - Include pole vertices in the selection. Edge loop selection was rewritten to use BMesh connectivity data. --- source/blender/editors/uvedit/uvedit_select.c | 371 ++++++++++++++++---------- 1 file changed, 224 insertions(+), 147 deletions(-) (limited to 'source/blender/editors/uvedit') diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index cb3b32bcfee..2ea78ca5377 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -71,10 +71,14 @@ #include "uvedit_intern.h" static void uv_select_all_perform(Scene *scene, Object *obedit, int action); + +static void uv_select_all_perform_multi_ex( + Scene *scene, Object **objects, const uint objects_len, int action, const Object *ob_exclude); static void uv_select_all_perform_multi(Scene *scene, Object **objects, const uint objects_len, int action); + static void uv_select_flush_from_tag_face(SpaceImage *sima, Scene *scene, Object *obedit, @@ -612,7 +616,7 @@ void uvedit_uv_select_disable(const Scene *scene, } } -static BMLoop *uvedit_loop_find_other_radial_loop_with_visible_face(Scene *scene, +static BMLoop *uvedit_loop_find_other_radial_loop_with_visible_face(const Scene *scene, BMLoop *l_src, const int cd_loop_uv_offset) { @@ -637,6 +641,37 @@ static BMLoop *uvedit_loop_find_other_radial_loop_with_visible_face(Scene *scene return l_other; } +static BMLoop *uvedit_loop_find_other_boundary_loop_with_visible_face(const Scene *scene, + BMLoop *l_edge, + BMVert *v_pivot, + const int cd_loop_uv_offset) +{ + BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_edge, cd_loop_uv_offset) == NULL); + + BMLoop *l_step = l_edge; + l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next; + BMLoop *l_step_last = NULL; + do { + BLI_assert(BM_vert_in_edge(l_step->e, v_pivot)); + l_step_last = l_step; + l_step = uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step, cd_loop_uv_offset); + if (l_step) { + l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next; + } + } while (l_step != NULL); + + BM_elem_flag_set(l_step_last->e, BM_ELEM_SMOOTH, false); + + if (l_step_last != NULL) { + BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step_last, cd_loop_uv_offset) == NULL); + } + + return l_step_last; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -971,200 +1006,235 @@ BMLoop *uv_find_nearest_loop_from_edge(struct Scene *scene, /** \name Edge Loop Select * \{ */ -static void uv_select_edgeloop_vertex_loop_flag(UvMapVert *first) -{ - UvMapVert *iterv; - int count = 0; +/** Mode for selecting edge loops at boundaries. */ +enum eUVEdgeLoopBoundaryMode { + /** Delimit at face corners (don't walk over multiple edges in the same face). */ + UV_EDGE_LOOP_BOUNDARY_LOOP = 1, + /** Don't delimit, walk over the all connected boundary loops. */ + UV_EDGE_LOOP_BOUNDARY_ALL = 2, +}; - for (iterv = first; iterv; iterv = iterv->next) { - if (iterv->separate && iterv != first) { - break; +static BMLoop *bm_select_edgeloop_double_side_next(const Scene *scene, + BMLoop *l_step, + BMVert *v_from, + const int cd_loop_uv_offset) +{ + if (l_step->f->len == 4) { + BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); + BMLoop *l_step_over = (v_from == l_step->v) ? l_step->next : l_step->prev; + l_step_over = uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step_over, cd_loop_uv_offset); + if (l_step_over) { + return (l_step_over->v == v_from_next) ? l_step_over->prev : l_step_over->next; } - - count++; - } - - if (count < 5) { - first->flag = 1; } + return NULL; } -static UvMapVert *uv_select_edgeloop_vertex_map_get(UvVertMap *vmap, BMFace *efa, BMLoop *l) +static BMLoop *bm_select_edgeloop_single_side_next(const Scene *scene, + BMLoop *l_step, + BMVert *v_from, + const int cd_loop_uv_offset) { - UvMapVert *iterv, *first; - first = BM_uv_vert_map_at_index(vmap, BM_elem_index_get(l->v)); + BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); + return uvedit_loop_find_other_boundary_loop_with_visible_face( + scene, l_step, v_from_next, cd_loop_uv_offset); +} - for (iterv = first; iterv; iterv = iterv->next) { - if (iterv->separate) { - first = iterv; - } - if (iterv->poly_index == BM_elem_index_get(efa)) { - return first; +/* TODO(campbell): support this in the BMesh API, as we have for clearing other types. */ +static void bm_loop_tags_clear(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMIter liter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); } } - - return NULL; } -static bool uv_select_edgeloop_edge_tag_faces(BMEditMesh *em, - UvMapVert *first1, - UvMapVert *first2, - int *totface) +/** + * Tag all loops which should be selected, the caller must select. + */ +static void uv_select_edgeloop_double_side_tag(const Scene *scene, + BMEditMesh *em, + BMLoop *l_init_pair[2], + const int cd_loop_uv_offset) { - UvMapVert *iterv1, *iterv2; - BMFace *efa; - int tot = 0; - - /* count number of faces this edge has */ - for (iterv1 = first1; iterv1; iterv1 = iterv1->next) { - if (iterv1->separate && iterv1 != first1) { - break; - } + bm_loop_tags_clear(em->bm); - for (iterv2 = first2; iterv2; iterv2 = iterv2->next) { - if (iterv2->separate && iterv2 != first2) { + for (int side = 0; side < 2; side++) { + BMLoop *l_step_pair[2] = {l_init_pair[0], l_init_pair[1]}; + BMVert *v_from = side ? l_step_pair[0]->e->v1 : l_step_pair[0]->e->v2; + /* Disable since we start from the same edge. */ + BM_elem_flag_disable(l_step_pair[0], BM_ELEM_TAG); + BM_elem_flag_disable(l_step_pair[1], BM_ELEM_TAG); + while ((l_step_pair[0] != NULL) && (l_step_pair[1] != NULL)) { + if (!uvedit_face_visible_test(scene, l_step_pair[0]->f) || + !uvedit_face_visible_test(scene, l_step_pair[1]->f) || + /* Check loops have not diverged. */ + (uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step_pair[0], cd_loop_uv_offset) != l_step_pair[1])) { break; } - if (iterv1->poly_index == iterv2->poly_index) { - /* if face already tagged, don't do this edge */ - efa = BM_face_at_index(em->bm, iterv1->poly_index); - if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { - return false; - } + BLI_assert(l_step_pair[0]->e == l_step_pair[1]->e); + + BM_elem_flag_enable(l_step_pair[0], BM_ELEM_TAG); + BM_elem_flag_enable(l_step_pair[1], BM_ELEM_TAG); + + BMVert *v_from_next = BM_edge_other_vert(l_step_pair[0]->e, v_from); + /* Walk over both sides, ensure they keep on the same edge. */ + for (int i = 0; i < ARRAY_SIZE(l_step_pair); i++) { + l_step_pair[i] = bm_select_edgeloop_double_side_next( + scene, l_step_pair[i], v_from, cd_loop_uv_offset); + } - tot++; + if ((l_step_pair[0] && BM_elem_flag_test(l_step_pair[0], BM_ELEM_TAG)) || + (l_step_pair[1] && BM_elem_flag_test(l_step_pair[1], BM_ELEM_TAG))) { break; } + v_from = v_from_next; } } +} - if (*totface == 0) { /* start edge */ - *totface = tot; - } - else if (tot != *totface) { /* check for same number of faces as start edge */ - return false; +/** + * Tag all loops which should be selected, the caller must select. + * + * \param r_count_by_select: Count the number of unselected and selected loops, + * this is needed to implement cycling between #eUVEdgeLoopBoundaryMode. + */ +static void uv_select_edgeloop_single_side_tag(const Scene *scene, + BMEditMesh *em, + BMLoop *l_init, + const int cd_loop_uv_offset, + enum eUVEdgeLoopBoundaryMode boundary_mode, + int r_count_by_select[2]) +{ + if (r_count_by_select) { + r_count_by_select[0] = r_count_by_select[1] = 0; } - /* tag the faces */ - for (iterv1 = first1; iterv1; iterv1 = iterv1->next) { - if (iterv1->separate && iterv1 != first1) { - break; - } + bm_loop_tags_clear(em->bm); - for (iterv2 = first2; iterv2; iterv2 = iterv2->next) { - if (iterv2->separate && iterv2 != first2) { + for (int side = 0; side < 2; side++) { + BMLoop *l_step = l_init; + BMVert *v_from = side ? l_step->e->v1 : l_step->e->v2; + /* Disable since we start from the same edge. */ + BM_elem_flag_disable(l_step, BM_ELEM_TAG); + while (l_step != NULL) { + + if (!uvedit_face_visible_test(scene, l_step->f) || + /* Check the boundary is still a boundary. */ + (uvedit_loop_find_other_radial_loop_with_visible_face( + scene, l_step, cd_loop_uv_offset) != NULL)) { break; } - if (iterv1->poly_index == iterv2->poly_index) { - efa = BM_face_at_index(em->bm, iterv1->poly_index); - BM_elem_flag_enable(efa, BM_ELEM_TAG); + if (r_count_by_select != NULL) { + r_count_by_select[uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)] += 1; + /* Early exit when mixed could be optional if needed. */ + if (r_count_by_select[0] && r_count_by_select[1]) { + r_count_by_select[0] = r_count_by_select[1] = -1; + break; + } + } + + BM_elem_flag_enable(l_step, BM_ELEM_TAG); + + BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); + BMFace *f_step_prev = l_step->f; + + l_step = bm_select_edgeloop_single_side_next(scene, l_step, v_from, cd_loop_uv_offset); + + if (l_step && BM_elem_flag_test(l_step, BM_ELEM_TAG)) { break; } + if (boundary_mode == UV_EDGE_LOOP_BOUNDARY_LOOP) { + /* Don't allow walking over the the face. */ + if (f_step_prev == l_step->f) { + break; + } + } + v_from = v_from_next; } } - - return true; } -static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) +static int uv_select_edgeloop( + SpaceImage *sima, Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) { BMEditMesh *em = BKE_editmesh_from_object(obedit); - BMFace *efa; - BMIter iter, liter; - BMLoop *l; - UvVertMap *vmap; - UvMapVert *iterv_curr; - UvMapVert *iterv_next; - int starttotf; - bool looking, select; + bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); - /* setup */ - BM_mesh_elem_table_ensure(em->bm, BM_FACE); - vmap = BM_uv_vert_map_create(em->bm, false, false); - - BM_mesh_elem_index_ensure(em->bm, BM_VERT | BM_FACE); - - if (!extend) { - uv_select_all_perform(scene, obedit, SEL_DESELECT); + if (extend) { + select = !(uvedit_uv_select_test(scene, hit->l, cd_loop_uv_offset)); + } + else { + select = true; } - BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); - - /* set flags for first face and verts */ - iterv_curr = uv_select_edgeloop_vertex_map_get(vmap, hit->efa, hit->l); - iterv_next = uv_select_edgeloop_vertex_map_get(vmap, hit->efa, hit->l->next); - uv_select_edgeloop_vertex_loop_flag(iterv_curr); - uv_select_edgeloop_vertex_loop_flag(iterv_next); - - starttotf = 0; - uv_select_edgeloop_edge_tag_faces(em, iterv_curr, iterv_next, &starttotf); - - /* sorry, first edge isn't even ok */ - looking = !(iterv_curr->flag == 0 && iterv_next->flag == 0); - - /* iterate */ - while (looking) { - looking = false; - - /* find correct valence edges which are not tagged yet, but connect to tagged one */ + BMLoop *l_init_pair[2] = { + hit->l, + uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, cd_loop_uv_offset), + }; - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (!BM_elem_flag_test(efa, BM_ELEM_TAG) && uvedit_face_visible_test(scene, efa)) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - /* check face not hidden and not tagged */ - if (!(iterv_curr = uv_select_edgeloop_vertex_map_get(vmap, efa, l))) { - continue; - } - if (!(iterv_next = uv_select_edgeloop_vertex_map_get(vmap, efa, l->next))) { - continue; - } + /* When selecting boundaries, support cycling between selection modes. */ + enum eUVEdgeLoopBoundaryMode boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP; - /* check if vertex is tagged and has right valence */ - if (iterv_curr->flag || iterv_next->flag) { - if (uv_select_edgeloop_edge_tag_faces(em, iterv_curr, iterv_next, &starttotf)) { - looking = true; - BM_elem_flag_enable(efa, BM_ELEM_TAG); + /* Tag all loops that are part of the edge loop (select after). + * This is done so we can */ + if (l_init_pair[1] == NULL) { + int count_by_select[2]; + /* If the loops selected toggle the boundaries. */ + uv_select_edgeloop_single_side_tag( + scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, count_by_select); + if (count_by_select[!select] == 0) { + boundary_mode = UV_EDGE_LOOP_BOUNDARY_ALL; - uv_select_edgeloop_vertex_loop_flag(iterv_curr); - uv_select_edgeloop_vertex_loop_flag(iterv_next); - break; - } - } - } + /* If the boundary is selected, toggle back to the loop. */ + uv_select_edgeloop_single_side_tag( + scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, count_by_select); + if (count_by_select[!select] == 0) { + boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP; } } } - /* do the actual select/deselect */ - iterv_curr = uv_select_edgeloop_vertex_map_get(vmap, hit->efa, hit->l); - iterv_next = uv_select_edgeloop_vertex_map_get(vmap, hit->efa, hit->l->next); - iterv_curr->flag = 1; - iterv_next->flag = 1; - - if (extend) { - select = !(uvedit_uv_select_test(scene, hit->l, cd_loop_uv_offset)); + if (l_init_pair[1] == NULL) { + uv_select_edgeloop_single_side_tag( + scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, NULL); } else { - select = true; + uv_select_edgeloop_double_side_tag(scene, em, l_init_pair, cd_loop_uv_offset); } - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - iterv_curr = uv_select_edgeloop_vertex_map_get(vmap, efa, l); + /* Apply the selection. */ + if (!extend) { + uv_select_all_perform(scene, obedit, SEL_DESELECT); + } - if (iterv_curr->flag) { - uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); + /* Select all tagged loops. */ + { + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BMIter liter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { + if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { + uvedit_edge_select_set_with_sticky( + sima, scene, em, l_iter, select, false, cd_loop_uv_offset); + } } } } - /* cleanup */ - BM_uv_vert_map_free(vmap); - return (select) ? 1 : -1; } @@ -1762,10 +1832,8 @@ static void uv_select_all_perform(Scene *scene, Object *obedit, int action) } } -static void uv_select_all_perform_multi(Scene *scene, - Object **objects, - const uint objects_len, - int action) +static void uv_select_all_perform_multi_ex( + Scene *scene, Object **objects, const uint objects_len, int action, const Object *ob_exclude) { if (action == SEL_TOGGLE) { action = uvedit_select_is_any_selected_multi(scene, objects, objects_len) ? SEL_DESELECT : @@ -1774,10 +1842,21 @@ static void uv_select_all_perform_multi(Scene *scene, for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; + if (ob_exclude && (obedit == ob_exclude)) { + continue; + } uv_select_all_perform(scene, obedit, action); } } +static void uv_select_all_perform_multi(Scene *scene, + Object **objects, + const uint objects_len, + int action) +{ + uv_select_all_perform_multi_ex(scene, objects, objects_len, action, NULL); +} + static int uv_select_all_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); @@ -1923,8 +2002,7 @@ static int uv_mouse_select_multi(bContext *C, /* do selection */ if (selectmode == UV_SELECT_ISLAND) { if (!extend) { - /* TODO(MULTI_EDIT): We only need to de-select non-active */ - uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); + uv_select_all_perform_multi_ex(scene, objects, objects_len, SEL_DESELECT, obedit); } /* Current behavior of 'extend' * is actually toggling, so pass extend flag as 'toggle' here */ @@ -2114,12 +2192,11 @@ static int uv_mouse_select_loop_generic_multi(bContext *C, /* Do selection. */ if (!extend) { - /* TODO(MULTI_EDIT): We only need to de-select non-active */ - uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); + uv_select_all_perform_multi_ex(scene, objects, objects_len, SEL_DESELECT, obedit); } if (loop_type == UV_LOOP_SELECT) { - flush = uv_select_edgeloop(scene, obedit, &hit, extend); + flush = uv_select_edgeloop(sima, scene, obedit, &hit, extend); } else if (loop_type == UV_RING_SELECT) { flush = uv_select_edgering(sima, scene, obedit, &hit, extend); -- cgit v1.2.3