diff options
author | mano-wii <germano.costa@ig.com.br> | 2020-02-14 14:42:17 +0300 |
---|---|---|
committer | mano-wii <germano.costa@ig.com.br> | 2020-02-14 14:42:59 +0300 |
commit | e277e8d085037414f34b27f9c1a26cbbf2507c3e (patch) | |
tree | cb37b0a9332a23e09a6717d68894f481542f3d67 /source/blender/editors/transform/transform_mode_edge_slide.c | |
parent | 897f943ca0c93a46bdb767e4c47c4bddfa7149c2 (diff) |
Cleanup: Split transform.c in multiple files
Differential Revision: https://developer.blender.org/D5819
Diffstat (limited to 'source/blender/editors/transform/transform_mode_edge_slide.c')
-rw-r--r-- | source/blender/editors/transform/transform_mode_edge_slide.c | 1463 |
1 files changed, 1463 insertions, 0 deletions
diff --git a/source/blender/editors/transform/transform_mode_edge_slide.c b/source/blender/editors/transform/transform_mode_edge_slide.c new file mode 100644 index 00000000000..70f4c94876c --- /dev/null +++ b/source/blender/editors/transform/transform_mode_edge_slide.c @@ -0,0 +1,1463 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup edtransform + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_utildefines_stack.h" + +#include "BKE_context.h" +#include "BKE_editmesh.h" +#include "BKE_editmesh_bvh.h" +#include "BKE_unit.h" + +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "ED_mesh.h" +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BLT_translation.h" + +#include "transform.h" +#include "transform_convert.h" +#include "transform_snap.h" +#include "transform_mode.h" + +/* -------------------------------------------------------------------- */ +/* Transform (Edge Slide) */ + +/** \name Transform Edge Slide + * \{ */ + +/** + * Get the first valid EdgeSlideData. + * + * Note we cannot trust TRANS_DATA_CONTAINER_FIRST_OK because of multi-object that + * may leave items with invalid custom data in the transform data container. + */ +static EdgeSlideData *edgeSlideFirstGet(TransInfo *t) +{ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + if (sld == NULL) { + continue; + } + return sld; + } + BLI_assert(!"Should never happen, at least one EdgeSlideData should be valid"); + return NULL; +} + +static void calcEdgeSlideCustomPoints(struct TransInfo *t) +{ + EdgeSlideData *sld = edgeSlideFirstGet(t); + + setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start); + + /* setCustomPoints isn't normally changing as the mouse moves, + * in this case apply mouse input immediately so we don't refresh + * with the value from the previous points */ + applyMouseInput(t, &t->mouse, t->mval, t->values); +} + +static BMEdge *get_other_edge(BMVert *v, BMEdge *e) +{ + BMIter iter; + BMEdge *e_iter; + + BM_ITER_ELEM (e_iter, &iter, v, BM_EDGES_OF_VERT) { + if (BM_elem_flag_test(e_iter, BM_ELEM_SELECT) && e_iter != e) { + return e_iter; + } + } + + return NULL; +} + +/* interpoaltes along a line made up of 2 segments (used for edge slide) */ +static void interp_line_v3_v3v3v3( + float p[3], const float v1[3], const float v2[3], const float v3[3], float t) +{ + float t_mid, t_delta; + + /* could be pre-calculated */ + t_mid = line_point_factor_v3(v2, v1, v3); + + t_delta = t - t_mid; + if (t_delta < 0.0f) { + if (UNLIKELY(fabsf(t_mid) < FLT_EPSILON)) { + copy_v3_v3(p, v2); + } + else { + interp_v3_v3v3(p, v1, v2, t / t_mid); + } + } + else { + t = t - t_mid; + t_mid = 1.0f - t_mid; + + if (UNLIKELY(fabsf(t_mid) < FLT_EPSILON)) { + copy_v3_v3(p, v3); + } + else { + interp_v3_v3v3(p, v2, v3, t / t_mid); + } + } +} + +/** + * Find the closest point on the ngon on the opposite side. + * used to set the edge slide distance for ngons. + */ +static bool bm_loop_calc_opposite_co(BMLoop *l_tmp, const float plane_no[3], float r_co[3]) +{ + /* skip adjacent edges */ + BMLoop *l_first = l_tmp->next; + BMLoop *l_last = l_tmp->prev; + BMLoop *l_iter; + float dist = FLT_MAX; + bool found = false; + + l_iter = l_first; + do { + float tvec[3]; + if (isect_line_plane_v3(tvec, l_iter->v->co, l_iter->next->v->co, l_tmp->v->co, plane_no)) { + const float fac = line_point_factor_v3(tvec, l_iter->v->co, l_iter->next->v->co); + /* allow some overlap to avoid missing the intersection because of float precision */ + if ((fac > -FLT_EPSILON) && (fac < 1.0f + FLT_EPSILON)) { + /* likelihood of multiple intersections per ngon is quite low, + * it would have to loop back on its self, but better support it + * so check for the closest opposite edge */ + const float tdist = len_v3v3(l_tmp->v->co, tvec); + if (tdist < dist) { + copy_v3_v3(r_co, tvec); + dist = tdist; + found = true; + } + } + } + } while ((l_iter = l_iter->next) != l_last); + + return found; +} + +/** + * Given 2 edges and a loop, step over the loops + * and calculate a direction to slide along. + * + * \param r_slide_vec: the direction to slide, + * the length of the vector defines the slide distance. + */ +static BMLoop *get_next_loop( + BMVert *v, BMLoop *l, BMEdge *e_prev, BMEdge *e_next, float r_slide_vec[3]) +{ + BMLoop *l_first; + float vec_accum[3] = {0.0f, 0.0f, 0.0f}; + float vec_accum_len = 0.0f; + int i = 0; + + BLI_assert(BM_edge_share_vert(e_prev, e_next) == v); + BLI_assert(BM_vert_in_edge(l->e, v)); + + l_first = l; + do { + l = BM_loop_other_edge_loop(l, v); + + if (l->e == e_next) { + if (i) { + normalize_v3_length(vec_accum, vec_accum_len / (float)i); + } + else { + /* When there is no edge to slide along, + * we must slide along the vector defined by the face we're attach to */ + BMLoop *l_tmp = BM_face_vert_share_loop(l_first->f, v); + + BLI_assert(ELEM(l_tmp->e, e_prev, e_next) && ELEM(l_tmp->prev->e, e_prev, e_next)); + + if (l_tmp->f->len == 4) { + /* we could use code below, but in this case + * sliding diagonally across the quad works well */ + sub_v3_v3v3(vec_accum, l_tmp->next->next->v->co, v->co); + } + else { + float tdir[3]; + BM_loop_calc_face_direction(l_tmp, tdir); + cross_v3_v3v3(vec_accum, l_tmp->f->no, tdir); +#if 0 + /* rough guess, we can do better! */ + normalize_v3_length(vec_accum, + (BM_edge_calc_length(e_prev) + BM_edge_calc_length(e_next)) / 2.0f); +#else + /* be clever, check the opposite ngon edge to slide into. + * this gives best results */ + { + float tvec[3]; + float dist; + + if (bm_loop_calc_opposite_co(l_tmp, tdir, tvec)) { + dist = len_v3v3(l_tmp->v->co, tvec); + } + else { + dist = (BM_edge_calc_length(e_prev) + BM_edge_calc_length(e_next)) / 2.0f; + } + + normalize_v3_length(vec_accum, dist); + } +#endif + } + } + + copy_v3_v3(r_slide_vec, vec_accum); + return l; + } + else { + /* accumulate the normalized edge vector, + * normalize so some edges don't skew the result */ + float tvec[3]; + sub_v3_v3v3(tvec, BM_edge_other_vert(l->e, v)->co, v->co); + vec_accum_len += normalize_v3(tvec); + add_v3_v3(vec_accum, tvec); + i += 1; + } + + if (BM_loop_other_edge_loop(l, v)->e == e_next) { + if (i) { + normalize_v3_length(vec_accum, vec_accum_len / (float)i); + } + + copy_v3_v3(r_slide_vec, vec_accum); + return BM_loop_other_edge_loop(l, v); + } + + } while ((l != l->radial_next) && ((l = l->radial_next) != l_first)); + + if (i) { + normalize_v3_length(vec_accum, vec_accum_len / (float)i); + } + + copy_v3_v3(r_slide_vec, vec_accum); + + return NULL; +} + +/** + * Calculate screenspace `mval_start` / `mval_end`, optionally slide direction. + */ +static void calcEdgeSlide_mval_range(TransInfo *t, + TransDataContainer *tc, + EdgeSlideData *sld, + const int *sv_table, + const int loop_nr, + const float mval[2], + const bool use_occlude_geometry, + const bool use_calc_direction) +{ + TransDataEdgeSlideVert *sv; + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + ARegion *ar = t->ar; + View3D *v3d = NULL; + RegionView3D *rv3d = NULL; + float projectMat[4][4]; + BMBVHTree *bmbvh; + + /* only for use_calc_direction */ + float(*loop_dir)[3] = NULL, *loop_maxdist = NULL; + + float mval_start[2], mval_end[2]; + float mval_dir[3], dist_best_sq; + + if (t->spacetype == SPACE_VIEW3D) { + /* background mode support */ + v3d = t->sa ? t->sa->spacedata.first : NULL; + rv3d = t->ar ? t->ar->regiondata : NULL; + } + + if (!rv3d) { + /* ok, let's try to survive this */ + unit_m4(projectMat); + } + else { + ED_view3d_ob_project_mat_get(rv3d, tc->obedit, projectMat); + } + + if (use_occlude_geometry) { + bmbvh = BKE_bmbvh_new_from_editmesh(em, BMBVH_RESPECT_HIDDEN, NULL, false); + } + else { + bmbvh = NULL; + } + + /* find mouse vectors, the global one, and one per loop in case we have + * multiple loops selected, in case they are oriented different */ + zero_v3(mval_dir); + dist_best_sq = -1.0f; + + if (use_calc_direction) { + loop_dir = MEM_callocN(sizeof(float[3]) * loop_nr, "sv loop_dir"); + loop_maxdist = MEM_mallocN(sizeof(float) * loop_nr, "sv loop_maxdist"); + copy_vn_fl(loop_maxdist, loop_nr, -1.0f); + } + + sv = &sld->sv[0]; + for (int i = 0; i < sld->totsv; i++, sv++) { + BMIter iter_other; + BMEdge *e; + BMVert *v = sv->v; + + UNUSED_VARS_NDEBUG(sv_table); /* silence warning */ + BLI_assert(i == sv_table[BM_elem_index_get(v)]); + + /* search cross edges for visible edge to the mouse cursor, + * then use the shared vertex to calculate screen vector*/ + BM_ITER_ELEM (e, &iter_other, v, BM_EDGES_OF_VERT) { + /* screen-space coords */ + float sco_a[3], sco_b[3]; + float dist_sq; + int l_nr; + + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + continue; + } + + /* This test is only relevant if object is not wire-drawn! See [#32068]. */ + bool is_visible = !use_occlude_geometry || + BMBVH_EdgeVisible(bmbvh, e, t->depsgraph, ar, v3d, tc->obedit); + + if (!is_visible && !use_calc_direction) { + continue; + } + + if (sv->v_side[1]) { + ED_view3d_project_float_v3_m4(ar, sv->v_side[1]->co, sco_b, projectMat); + } + else { + add_v3_v3v3(sco_b, v->co, sv->dir_side[1]); + ED_view3d_project_float_v3_m4(ar, sco_b, sco_b, projectMat); + } + + if (sv->v_side[0]) { + ED_view3d_project_float_v3_m4(ar, sv->v_side[0]->co, sco_a, projectMat); + } + else { + add_v3_v3v3(sco_a, v->co, sv->dir_side[0]); + ED_view3d_project_float_v3_m4(ar, sco_a, sco_a, projectMat); + } + + /* global direction */ + dist_sq = dist_squared_to_line_segment_v2(mval, sco_b, sco_a); + if (is_visible) { + if ((dist_best_sq == -1.0f) || + /* intentionally use 2d size on 3d vector */ + (dist_sq < dist_best_sq && (len_squared_v2v2(sco_b, sco_a) > 0.1f))) { + dist_best_sq = dist_sq; + sub_v3_v3v3(mval_dir, sco_b, sco_a); + } + } + + if (use_calc_direction) { + /* per loop direction */ + l_nr = sv->loop_nr; + if (loop_maxdist[l_nr] == -1.0f || dist_sq < loop_maxdist[l_nr]) { + loop_maxdist[l_nr] = dist_sq; + sub_v3_v3v3(loop_dir[l_nr], sco_b, sco_a); + } + } + } + } + + if (use_calc_direction) { + int i; + sv = &sld->sv[0]; + for (i = 0; i < sld->totsv; i++, sv++) { + /* switch a/b if loop direction is different from global direction */ + int l_nr = sv->loop_nr; + if (dot_v3v3(loop_dir[l_nr], mval_dir) < 0.0f) { + swap_v3_v3(sv->dir_side[0], sv->dir_side[1]); + SWAP(BMVert *, sv->v_side[0], sv->v_side[1]); + } + } + + MEM_freeN(loop_dir); + MEM_freeN(loop_maxdist); + } + + /* possible all of the edge loops are pointing directly at the view */ + if (UNLIKELY(len_squared_v2(mval_dir) < 0.1f)) { + mval_dir[0] = 0.0f; + mval_dir[1] = 100.0f; + } + + /* zero out start */ + zero_v2(mval_start); + + /* dir holds a vector along edge loop */ + copy_v2_v2(mval_end, mval_dir); + mul_v2_fl(mval_end, 0.5f); + + sld->mval_start[0] = t->mval[0] + mval_start[0]; + sld->mval_start[1] = t->mval[1] + mval_start[1]; + + sld->mval_end[0] = t->mval[0] + mval_end[0]; + sld->mval_end[1] = t->mval[1] + mval_end[1]; + + if (bmbvh) { + BKE_bmbvh_free(bmbvh); + } +} + +static void calcEdgeSlide_even(TransInfo *t, + TransDataContainer *tc, + EdgeSlideData *sld, + const float mval[2]) +{ + TransDataEdgeSlideVert *sv = sld->sv; + + if (sld->totsv > 0) { + ARegion *ar = t->ar; + RegionView3D *rv3d = NULL; + float projectMat[4][4]; + + int i = 0; + + float v_proj[2]; + float dist_sq = 0; + float dist_min_sq = FLT_MAX; + + if (t->spacetype == SPACE_VIEW3D) { + /* background mode support */ + rv3d = t->ar ? t->ar->regiondata : NULL; + } + + if (!rv3d) { + /* ok, let's try to survive this */ + unit_m4(projectMat); + } + else { + ED_view3d_ob_project_mat_get(rv3d, tc->obedit, projectMat); + } + + for (i = 0; i < sld->totsv; i++, sv++) { + /* Set length */ + sv->edge_len = len_v3v3(sv->dir_side[0], sv->dir_side[1]); + + ED_view3d_project_float_v2_m4(ar, sv->v->co, v_proj, projectMat); + dist_sq = len_squared_v2v2(mval, v_proj); + if (dist_sq < dist_min_sq) { + dist_min_sq = dist_sq; + sld->curr_sv_index = i; + } + } + } + else { + sld->curr_sv_index = 0; + } +} + +static bool createEdgeSlideVerts_double_side(TransInfo *t, TransDataContainer *tc) +{ + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BMesh *bm = em->bm; + BMIter iter; + BMEdge *e; + BMVert *v; + TransDataEdgeSlideVert *sv_array; + int sv_tot; + int *sv_table; /* BMVert -> sv_array index */ + EdgeSlideData *sld = MEM_callocN(sizeof(*sld), "sld"); + float mval[2] = {(float)t->mval[0], (float)t->mval[1]}; + int numsel, i, loop_nr; + bool use_occlude_geometry = false; + View3D *v3d = NULL; + RegionView3D *rv3d = NULL; + + sld->curr_sv_index = 0; + + /*ensure valid selection*/ + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + BMIter iter2; + numsel = 0; + BM_ITER_ELEM (e, &iter2, v, BM_EDGES_OF_VERT) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + /* BMESH_TODO: this is probably very evil, + * set v->e to a selected edge*/ + v->e = e; + + numsel++; + } + } + + if (numsel == 0 || numsel > 2) { + MEM_freeN(sld); + return false; /* invalid edge selection */ + } + } + } + + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + /* note, any edge with loops can work, but we won't get predictable results, so bail out */ + if (!BM_edge_is_manifold(e) && !BM_edge_is_boundary(e)) { + /* can edges with at least once face user */ + MEM_freeN(sld); + return false; + } + } + } + + sv_table = MEM_mallocN(sizeof(*sv_table) * bm->totvert, __func__); + +#define INDEX_UNSET -1 +#define INDEX_INVALID -2 + + { + int j = 0; + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + BM_elem_flag_enable(v, BM_ELEM_TAG); + sv_table[i] = INDEX_UNSET; + j += 1; + } + else { + BM_elem_flag_disable(v, BM_ELEM_TAG); + sv_table[i] = INDEX_INVALID; + } + BM_elem_index_set(v, i); /* set_inline */ + } + bm->elem_index_dirty &= ~BM_VERT; + + if (!j) { + MEM_freeN(sld); + MEM_freeN(sv_table); + return false; + } + sv_tot = j; + } + + sv_array = MEM_callocN(sizeof(TransDataEdgeSlideVert) * sv_tot, "sv_array"); + loop_nr = 0; + + STACK_DECLARE(sv_array); + STACK_INIT(sv_array, sv_tot); + + while (1) { + float vec_a[3], vec_b[3]; + BMLoop *l_a, *l_b; + BMLoop *l_a_prev, *l_b_prev; + BMVert *v_first; + /* If this succeeds call get_next_loop() + * which calculates the direction to slide based on clever checks. + * + * otherwise we simply use 'e_dir' as an edge-rail. + * (which is better when the attached edge is a boundary, see: T40422) + */ +#define EDGESLIDE_VERT_IS_INNER(v, e_dir) \ + ((BM_edge_is_boundary(e_dir) == false) && (BM_vert_edge_count_nonwire(v) == 2)) + + v = NULL; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + break; + } + } + + if (!v) { + break; + } + + if (!v->e) { + continue; + } + + v_first = v; + + /*walk along the edge loop*/ + e = v->e; + + /*first, rewind*/ + do { + e = get_other_edge(v, e); + if (!e) { + e = v->e; + break; + } + + if (!BM_elem_flag_test(BM_edge_other_vert(e, v), BM_ELEM_TAG)) { + break; + } + + v = BM_edge_other_vert(e, v); + } while (e != v_first->e); + + BM_elem_flag_disable(v, BM_ELEM_TAG); + + l_a = e->l; + l_b = e->l->radial_next; + + /* regarding e_next, use get_next_loop()'s improved interpolation where possible */ + { + BMEdge *e_next = get_other_edge(v, e); + if (e_next) { + get_next_loop(v, l_a, e, e_next, vec_a); + } + else { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_a, v); + if (EDGESLIDE_VERT_IS_INNER(v, l_tmp->e)) { + get_next_loop(v, l_a, e, l_tmp->e, vec_a); + } + else { + sub_v3_v3v3(vec_a, BM_edge_other_vert(l_tmp->e, v)->co, v->co); + } + } + } + + /* !BM_edge_is_boundary(e); */ + if (l_b != l_a) { + BMEdge *e_next = get_other_edge(v, e); + if (e_next) { + get_next_loop(v, l_b, e, e_next, vec_b); + } + else { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_b, v); + if (EDGESLIDE_VERT_IS_INNER(v, l_tmp->e)) { + get_next_loop(v, l_b, e, l_tmp->e, vec_b); + } + else { + sub_v3_v3v3(vec_b, BM_edge_other_vert(l_tmp->e, v)->co, v->co); + } + } + } + else { + l_b = NULL; + } + + l_a_prev = NULL; + l_b_prev = NULL; + +#define SV_FROM_VERT(v) \ + ((sv_table[BM_elem_index_get(v)] == INDEX_UNSET) ? \ + ((void)(sv_table[BM_elem_index_get(v)] = STACK_SIZE(sv_array)), \ + STACK_PUSH_RET_PTR(sv_array)) : \ + (&sv_array[sv_table[BM_elem_index_get(v)]])) + + /*iterate over the loop*/ + v_first = v; + do { + bool l_a_ok_prev; + bool l_b_ok_prev; + TransDataEdgeSlideVert *sv; + BMVert *v_prev; + BMEdge *e_prev; + + /* XXX, 'sv' will initialize multiple times, this is suspicious. see [#34024] */ + BLI_assert(v != NULL); + BLI_assert(sv_table[BM_elem_index_get(v)] != INDEX_INVALID); + sv = SV_FROM_VERT(v); + sv->v = v; + copy_v3_v3(sv->v_co_orig, v->co); + sv->loop_nr = loop_nr; + + if (l_a || l_a_prev) { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_a ? l_a : l_a_prev, v); + sv->v_side[0] = BM_edge_other_vert(l_tmp->e, v); + copy_v3_v3(sv->dir_side[0], vec_a); + } + + if (l_b || l_b_prev) { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_b ? l_b : l_b_prev, v); + sv->v_side[1] = BM_edge_other_vert(l_tmp->e, v); + copy_v3_v3(sv->dir_side[1], vec_b); + } + + v_prev = v; + v = BM_edge_other_vert(e, v); + + e_prev = e; + e = get_other_edge(v, e); + + if (!e) { + BLI_assert(v != NULL); + + BLI_assert(sv_table[BM_elem_index_get(v)] != INDEX_INVALID); + sv = SV_FROM_VERT(v); + + sv->v = v; + copy_v3_v3(sv->v_co_orig, v->co); + sv->loop_nr = loop_nr; + + if (l_a) { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_a, v); + sv->v_side[0] = BM_edge_other_vert(l_tmp->e, v); + if (EDGESLIDE_VERT_IS_INNER(v, l_tmp->e)) { + get_next_loop(v, l_a, e_prev, l_tmp->e, sv->dir_side[0]); + } + else { + sub_v3_v3v3(sv->dir_side[0], sv->v_side[0]->co, v->co); + } + } + + if (l_b) { + BMLoop *l_tmp = BM_loop_other_edge_loop(l_b, v); + sv->v_side[1] = BM_edge_other_vert(l_tmp->e, v); + if (EDGESLIDE_VERT_IS_INNER(v, l_tmp->e)) { + get_next_loop(v, l_b, e_prev, l_tmp->e, sv->dir_side[1]); + } + else { + sub_v3_v3v3(sv->dir_side[1], sv->v_side[1]->co, v->co); + } + } + + BM_elem_flag_disable(v, BM_ELEM_TAG); + BM_elem_flag_disable(v_prev, BM_ELEM_TAG); + + break; + } + l_a_ok_prev = (l_a != NULL); + l_b_ok_prev = (l_b != NULL); + + l_a_prev = l_a; + l_b_prev = l_b; + + if (l_a) { + l_a = get_next_loop(v, l_a, e_prev, e, vec_a); + } + else { + zero_v3(vec_a); + } + + if (l_b) { + l_b = get_next_loop(v, l_b, e_prev, e, vec_b); + } + else { + zero_v3(vec_b); + } + + if (l_a && l_b) { + /* pass */ + } + else { + if (l_a || l_b) { + /* find the opposite loop if it was missing previously */ + if (l_a == NULL && l_b && (l_b->radial_next != l_b)) { + l_a = l_b->radial_next; + } + else if (l_b == NULL && l_a && (l_a->radial_next != l_a)) { + l_b = l_a->radial_next; + } + } + else if (e->l != NULL) { + /* if there are non-contiguous faces, we can still recover + * the loops of the new edges faces */ + + /* note!, the behavior in this case means edges may move in opposite directions, + * this could be made to work more usefully. */ + + if (l_a_ok_prev) { + l_a = e->l; + l_b = (l_a->radial_next != l_a) ? l_a->radial_next : NULL; + } + else if (l_b_ok_prev) { + l_b = e->l; + l_a = (l_b->radial_next != l_b) ? l_b->radial_next : NULL; + } + } + + if (!l_a_ok_prev && l_a) { + get_next_loop(v, l_a, e, e_prev, vec_a); + } + if (!l_b_ok_prev && l_b) { + get_next_loop(v, l_b, e, e_prev, vec_b); + } + } + + BM_elem_flag_disable(v, BM_ELEM_TAG); + BM_elem_flag_disable(v_prev, BM_ELEM_TAG); + } while ((e != v_first->e) && (l_a || l_b)); + +#undef SV_FROM_VERT +#undef INDEX_UNSET +#undef INDEX_INVALID + + loop_nr++; + +#undef EDGESLIDE_VERT_IS_INNER + } + + /* EDBM_flag_disable_all(em, BM_ELEM_SELECT); */ + + BLI_assert(STACK_SIZE(sv_array) == sv_tot); + + sld->sv = sv_array; + sld->totsv = sv_tot; + + /* use for visibility checks */ + if (t->spacetype == SPACE_VIEW3D) { + v3d = t->sa ? t->sa->spacedata.first : NULL; + rv3d = t->ar ? t->ar->regiondata : NULL; + use_occlude_geometry = (v3d && TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->dt > OB_WIRE && + !XRAY_ENABLED(v3d)); + } + + calcEdgeSlide_mval_range(t, tc, sld, sv_table, loop_nr, mval, use_occlude_geometry, true); + + if (rv3d) { + calcEdgeSlide_even(t, tc, sld, mval); + } + + tc->custom.mode.data = sld; + + MEM_freeN(sv_table); + + return true; +} + +/** + * A simple version of #createEdgeSlideVerts_double_side + * Which assumes the longest unselected. + */ +static bool createEdgeSlideVerts_single_side(TransInfo *t, TransDataContainer *tc) +{ + BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); + BMesh *bm = em->bm; + BMIter iter; + BMEdge *e; + TransDataEdgeSlideVert *sv_array; + int sv_tot; + int *sv_table; /* BMVert -> sv_array index */ + EdgeSlideData *sld = MEM_callocN(sizeof(*sld), "sld"); + float mval[2] = {(float)t->mval[0], (float)t->mval[1]}; + int loop_nr; + bool use_occlude_geometry = false; + View3D *v3d = NULL; + RegionView3D *rv3d = NULL; + + if (t->spacetype == SPACE_VIEW3D) { + /* background mode support */ + v3d = t->sa ? t->sa->spacedata.first : NULL; + rv3d = t->ar ? t->ar->regiondata : NULL; + } + + sld->curr_sv_index = 0; + /* ensure valid selection */ + { + int i = 0, j = 0; + BMVert *v; + + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + float len_sq_max = -1.0f; + BMIter iter2; + BM_ITER_ELEM (e, &iter2, v, BM_EDGES_OF_VERT) { + if (!BM_elem_flag_test(e, BM_ELEM_SELECT)) { + float len_sq = BM_edge_calc_length_squared(e); + if (len_sq > len_sq_max) { + len_sq_max = len_sq; + v->e = e; + } + } + } + + if (len_sq_max != -1.0f) { + j++; + } + } + BM_elem_index_set(v, i); /* set_inline */ + } + bm->elem_index_dirty &= ~BM_VERT; + + if (!j) { + MEM_freeN(sld); + return false; + } + + sv_tot = j; + } + + BLI_assert(sv_tot != 0); + /* over alloc */ + sv_array = MEM_callocN(sizeof(TransDataEdgeSlideVert) * bm->totvertsel, "sv_array"); + + /* same loop for all loops, weak but we dont connect loops in this case */ + loop_nr = 1; + + sv_table = MEM_mallocN(sizeof(*sv_table) * bm->totvert, __func__); + + { + int i = 0, j = 0; + BMVert *v; + + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + sv_table[i] = -1; + if ((v->e != NULL) && (BM_elem_flag_test(v, BM_ELEM_SELECT))) { + if (BM_elem_flag_test(v->e, BM_ELEM_SELECT) == 0) { + TransDataEdgeSlideVert *sv; + sv = &sv_array[j]; + sv->v = v; + copy_v3_v3(sv->v_co_orig, v->co); + sv->v_side[0] = BM_edge_other_vert(v->e, v); + sub_v3_v3v3(sv->dir_side[0], sv->v_side[0]->co, v->co); + sv->loop_nr = 0; + sv_table[i] = j; + j += 1; + } + } + } + } + + /* check for wire vertices, + * interpolate the directions of wire verts between non-wire verts */ + if (sv_tot != bm->totvert) { + const int sv_tot_nowire = sv_tot; + TransDataEdgeSlideVert *sv_iter = sv_array; + + for (int i = 0; i < sv_tot_nowire; i++, sv_iter++) { + BMIter eiter; + BM_ITER_ELEM (e, &eiter, sv_iter->v, BM_EDGES_OF_VERT) { + /* walk over wire */ + TransDataEdgeSlideVert *sv_end = NULL; + BMEdge *e_step = e; + BMVert *v = sv_iter->v; + int j; + + j = sv_tot; + + while (1) { + BMVert *v_other = BM_edge_other_vert(e_step, v); + int endpoint = ((sv_table[BM_elem_index_get(v_other)] != -1) + + (BM_vert_is_edge_pair(v_other) == false)); + + if ((BM_elem_flag_test(e_step, BM_ELEM_SELECT) && + BM_elem_flag_test(v_other, BM_ELEM_SELECT)) && + (endpoint == 0)) { + /* scan down the list */ + TransDataEdgeSlideVert *sv; + BLI_assert(sv_table[BM_elem_index_get(v_other)] == -1); + sv_table[BM_elem_index_get(v_other)] = j; + sv = &sv_array[j]; + sv->v = v_other; + copy_v3_v3(sv->v_co_orig, v_other->co); + copy_v3_v3(sv->dir_side[0], sv_iter->dir_side[0]); + j++; + + /* advance! */ + v = v_other; + e_step = BM_DISK_EDGE_NEXT(e_step, v_other); + } + else { + if ((endpoint == 2) && (sv_tot != j)) { + BLI_assert(BM_elem_index_get(v_other) != -1); + sv_end = &sv_array[sv_table[BM_elem_index_get(v_other)]]; + } + break; + } + } + + if (sv_end) { + int sv_tot_prev = sv_tot; + const float *co_src = sv_iter->v->co; + const float *co_dst = sv_end->v->co; + const float *dir_src = sv_iter->dir_side[0]; + const float *dir_dst = sv_end->dir_side[0]; + sv_tot = j; + + while (j-- != sv_tot_prev) { + float factor; + factor = line_point_factor_v3(sv_array[j].v->co, co_src, co_dst); + interp_v3_v3v3(sv_array[j].dir_side[0], dir_src, dir_dst, factor); + } + } + } + } + } + + /* EDBM_flag_disable_all(em, BM_ELEM_SELECT); */ + + sld->sv = sv_array; + sld->totsv = sv_tot; + + /* use for visibility checks */ + if (t->spacetype == SPACE_VIEW3D) { + v3d = t->sa ? t->sa->spacedata.first : NULL; + rv3d = t->ar ? t->ar->regiondata : NULL; + use_occlude_geometry = (v3d && TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->dt > OB_WIRE && + !XRAY_ENABLED(v3d)); + } + + calcEdgeSlide_mval_range(t, tc, sld, sv_table, loop_nr, mval, use_occlude_geometry, false); + + if (rv3d) { + calcEdgeSlide_even(t, tc, sld, mval); + } + + tc->custom.mode.data = sld; + + MEM_freeN(sv_table); + + return true; +} + +void projectEdgeSlideData(TransInfo *t, bool is_final) +{ + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + + if (sld == NULL) { + continue; + } + + trans_mesh_customdata_correction_apply(tc, is_final); + } +} + +static void freeEdgeSlideVerts(TransInfo *UNUSED(t), + TransDataContainer *UNUSED(tc), + TransCustomData *custom_data) +{ + EdgeSlideData *sld = custom_data->data; + + if (sld == NULL) { + return; + } + + MEM_freeN(sld->sv); + MEM_freeN(sld); + + custom_data->data = NULL; +} + +static eRedrawFlag handleEventEdgeSlide(struct TransInfo *t, const struct wmEvent *event) +{ + if (t->mode == TFM_EDGE_SLIDE) { + EdgeSlideParams *slp = t->custom.mode.data; + + if (slp) { + switch (event->type) { + case EKEY: + if (event->val == KM_PRESS) { + slp->use_even = !slp->use_even; + calcEdgeSlideCustomPoints(t); + return TREDRAW_HARD; + } + break; + case FKEY: + if (event->val == KM_PRESS) { + slp->flipped = !slp->flipped; + calcEdgeSlideCustomPoints(t); + return TREDRAW_HARD; + } + break; + case CKEY: + /* use like a modifier key */ + if (event->val == KM_PRESS) { + t->flag ^= T_ALT_TRANSFORM; + calcEdgeSlideCustomPoints(t); + return TREDRAW_HARD; + } + break; + case EVT_MODAL_MAP: +#if 0 + switch (event->val) { + case TFM_MODAL_EDGESLIDE_DOWN: + sld->curr_sv_index = ((sld->curr_sv_index - 1) + sld->totsv) % sld->totsv; + return TREDRAW_HARD; + case TFM_MODAL_EDGESLIDE_UP: + sld->curr_sv_index = (sld->curr_sv_index + 1) % sld->totsv; + return TREDRAW_HARD; + } +#endif + break; + case MOUSEMOVE: + calcEdgeSlideCustomPoints(t); + break; + default: + break; + } + } + } + return TREDRAW_NOTHING; +} + +void drawEdgeSlide(TransInfo *t) +{ + if ((t->mode == TFM_EDGE_SLIDE) && edgeSlideFirstGet(t)) { + const EdgeSlideParams *slp = t->custom.mode.data; + EdgeSlideData *sld = edgeSlideFirstGet(t); + const bool is_clamp = !(t->flag & T_ALT_TRANSFORM); + + /* Even mode */ + if ((slp->use_even == true) || (is_clamp == false)) { + const float line_size = UI_GetThemeValuef(TH_OUTLINE_WIDTH) + 0.5f; + + GPU_depth_test(false); + + GPU_blend(true); + GPU_blend_set_func_separate( + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA); + + GPU_matrix_push(); + GPU_matrix_mul(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->obmat); + + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + + if (slp->use_even == true) { + float co_a[3], co_b[3], co_mark[3]; + TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index]; + const float fac = (slp->perc + 1.0f) / 2.0f; + const float ctrl_size = UI_GetThemeValuef(TH_FACEDOT_SIZE) + 1.5f; + const float guide_size = ctrl_size - 0.5f; + const int alpha_shade = -30; + + add_v3_v3v3(co_a, curr_sv->v_co_orig, curr_sv->dir_side[0]); + add_v3_v3v3(co_b, curr_sv->v_co_orig, curr_sv->dir_side[1]); + + GPU_line_width(line_size); + immUniformThemeColorShadeAlpha(TH_EDGE_SELECT, 80, alpha_shade); + immBeginAtMost(GPU_PRIM_LINES, 4); + if (curr_sv->v_side[0]) { + immVertex3fv(pos, curr_sv->v_side[0]->co); + immVertex3fv(pos, curr_sv->v_co_orig); + } + if (curr_sv->v_side[1]) { + immVertex3fv(pos, curr_sv->v_side[1]->co); + immVertex3fv(pos, curr_sv->v_co_orig); + } + immEnd(); + + { + float *co_test = NULL; + if (slp->flipped) { + if (curr_sv->v_side[1]) { + co_test = curr_sv->v_side[1]->co; + } + } + else { + if (curr_sv->v_side[0]) { + co_test = curr_sv->v_side[0]->co; + } + } + + if (co_test != NULL) { + immUniformThemeColorShadeAlpha(TH_SELECT, -30, alpha_shade); + GPU_point_size(ctrl_size); + immBegin(GPU_PRIM_POINTS, 1); + immVertex3fv(pos, co_test); + immEnd(); + } + } + + immUniformThemeColorShadeAlpha(TH_SELECT, 255, alpha_shade); + GPU_point_size(guide_size); + immBegin(GPU_PRIM_POINTS, 1); + interp_line_v3_v3v3v3(co_mark, co_b, curr_sv->v_co_orig, co_a, fac); + immVertex3fv(pos, co_mark); + immEnd(); + } + else { + if (is_clamp == false) { + const int side_index = sld->curr_side_unclamp; + TransDataEdgeSlideVert *sv; + int i; + const int alpha_shade = -160; + + GPU_line_width(line_size); + immUniformThemeColorShadeAlpha(TH_EDGE_SELECT, 80, alpha_shade); + immBegin(GPU_PRIM_LINES, sld->totsv * 2); + + /* TODO(campbell): Loop over all verts */ + sv = sld->sv; + for (i = 0; i < sld->totsv; i++, sv++) { + float a[3], b[3]; + + if (!is_zero_v3(sv->dir_side[side_index])) { + copy_v3_v3(a, sv->dir_side[side_index]); + } + else { + copy_v3_v3(a, sv->dir_side[!side_index]); + } + + mul_v3_fl(a, 100.0f); + negate_v3_v3(b, a); + add_v3_v3(a, sv->v_co_orig); + add_v3_v3(b, sv->v_co_orig); + + immVertex3fv(pos, a); + immVertex3fv(pos, b); + } + immEnd(); + } + else { + BLI_assert(0); + } + } + + immUnbindProgram(); + + GPU_matrix_pop(); + + GPU_blend(false); + + GPU_depth_test(true); + } + } +} + +void doEdgeSlide(TransInfo *t, float perc) +{ + EdgeSlideParams *slp = t->custom.mode.data; + EdgeSlideData *sld_active = edgeSlideFirstGet(t); + + slp->perc = perc; + + if (slp->use_even == false) { + const bool is_clamp = !(t->flag & T_ALT_TRANSFORM); + if (is_clamp) { + const int side_index = (perc < 0.0f); + const float perc_final = fabsf(perc); + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + + if (sld == NULL) { + continue; + } + + TransDataEdgeSlideVert *sv = sld->sv; + for (int i = 0; i < sld->totsv; i++, sv++) { + madd_v3_v3v3fl(sv->v->co, sv->v_co_orig, sv->dir_side[side_index], perc_final); + } + sld->curr_side_unclamp = side_index; + } + } + else { + const float perc_init = fabsf(perc) * + ((sld_active->curr_side_unclamp == (perc < 0.0f)) ? 1 : -1); + const int side_index = sld_active->curr_side_unclamp; + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + + if (sld == NULL) { + continue; + } + + TransDataEdgeSlideVert *sv = sld->sv; + for (int i = 0; i < sld->totsv; i++, sv++) { + float dir_flip[3]; + float perc_final = perc_init; + if (!is_zero_v3(sv->dir_side[side_index])) { + copy_v3_v3(dir_flip, sv->dir_side[side_index]); + } + else { + copy_v3_v3(dir_flip, sv->dir_side[!side_index]); + perc_final *= -1; + } + madd_v3_v3v3fl(sv->v->co, sv->v_co_orig, dir_flip, perc_final); + } + } + } + } + else { + /** + * Implementation note, even mode ignores the starting positions and uses + * only the a/b verts, this could be changed/improved so the distance is + * still met but the verts are moved along their original path (which may not be straight), + * however how it works now is OK and matches 2.4x - Campbell + * + * \note `len_v3v3(curr_sv->dir_side[0], curr_sv->dir_side[1])` + * is the same as the distance between the original vert locations, + * same goes for the lines below. + */ + TransDataEdgeSlideVert *curr_sv = &sld_active->sv[sld_active->curr_sv_index]; + const float curr_length_perc = curr_sv->edge_len * + (((slp->flipped ? perc : -perc) + 1.0f) / 2.0f); + + float co_a[3]; + float co_b[3]; + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + EdgeSlideData *sld = tc->custom.mode.data; + + if (sld == NULL) { + continue; + } + + TransDataEdgeSlideVert *sv = sld->sv; + for (int i = 0; i < sld->totsv; i++, sv++) { + if (sv->edge_len > FLT_EPSILON) { + const float fac = min_ff(sv->edge_len, curr_length_perc) / sv->edge_len; + + add_v3_v3v3(co_a, sv->v_co_orig, sv->dir_side[0]); + add_v3_v3v3(co_b, sv->v_co_orig, sv->dir_side[1]); + + if (slp->flipped) { + interp_line_v3_v3v3v3(sv->v->co, co_b, sv->v_co_orig, co_a, fac); + } + else { + interp_line_v3_v3v3v3(sv->v->co, co_a, sv->v_co_orig, co_b, fac); + } + } + } + } + } +} + +static void applyEdgeSlide(TransInfo *t, const int UNUSED(mval[2])) +{ + char str[UI_MAX_DRAW_STR]; + size_t ofs = 0; + float final; + EdgeSlideParams *slp = t->custom.mode.data; + bool flipped = slp->flipped; + bool use_even = slp->use_even; + const bool is_clamp = !(t->flag & T_ALT_TRANSFORM); + const bool is_constrained = !(is_clamp == false || hasNumInput(&t->num)); + + final = t->values[0]; + + snapGridIncrement(t, &final); + + /* only do this so out of range values are not displayed */ + if (is_constrained) { + CLAMP(final, -1.0f, 1.0f); + } + + applyNumInput(&t->num, &final); + + t->values_final[0] = final; + + /* header string */ + ofs += BLI_strncpy_rlen(str + ofs, TIP_("Edge Slide: "), sizeof(str) - ofs); + if (hasNumInput(&t->num)) { + char c[NUM_STR_REP_LEN]; + outputNumInput(&(t->num), c, &t->scene->unit); + ofs += BLI_strncpy_rlen(str + ofs, &c[0], sizeof(str) - ofs); + } + else { + ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, "%.4f ", final); + } + ofs += BLI_snprintf( + str + ofs, sizeof(str) - ofs, TIP_("(E)ven: %s, "), WM_bool_as_string(use_even)); + if (use_even) { + ofs += BLI_snprintf( + str + ofs, sizeof(str) - ofs, TIP_("(F)lipped: %s, "), WM_bool_as_string(flipped)); + } + ofs += BLI_snprintf( + str + ofs, sizeof(str) - ofs, TIP_("Alt or (C)lamp: %s"), WM_bool_as_string(is_clamp)); + /* done with header string */ + + /* do stuff here */ + doEdgeSlide(t, final); + + recalcData(t); + + ED_area_status_text(t->sa, str); +} + +void initEdgeSlide_ex( + TransInfo *t, bool use_double_side, bool use_even, bool flipped, bool use_clamp) +{ + EdgeSlideData *sld; + bool ok = false; + + t->mode = TFM_EDGE_SLIDE; + t->transform = applyEdgeSlide; + t->handleEvent = handleEventEdgeSlide; + + { + EdgeSlideParams *slp = MEM_callocN(sizeof(*slp), __func__); + slp->use_even = use_even; + slp->flipped = flipped; + /* happens to be best for single-sided */ + if (use_double_side == false) { + slp->flipped = !flipped; + } + slp->perc = 0.0f; + + if (!use_clamp) { + t->flag |= T_ALT_TRANSFORM; + } + + t->custom.mode.data = slp; + t->custom.mode.use_free = true; + } + + if (use_double_side) { + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + ok |= createEdgeSlideVerts_double_side(t, tc); + } + } + else { + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + ok |= createEdgeSlideVerts_single_side(t, tc); + } + } + + if (!ok) { + t->state = TRANS_CANCEL; + return; + } + + FOREACH_TRANS_DATA_CONTAINER (t, tc) { + sld = tc->custom.mode.data; + if (!sld) { + continue; + } + tc->custom.mode.free_cb = freeEdgeSlideVerts; + } + + trans_mesh_customdata_correction_init(t); + + /* set custom point first if you want value to be initialized by init */ + calcEdgeSlideCustomPoints(t); + initMouseInputMode(t, &t->mouse, INPUT_CUSTOM_RATIO_FLIP); + + t->idx_max = 0; + t->num.idx_max = 0; + t->snap[0] = 0.0f; + t->snap[1] = 0.1f; + t->snap[2] = t->snap[1] * 0.1f; + + copy_v3_fl(t->num.val_inc, t->snap[1]); + t->num.unit_sys = t->scene->unit.system; + t->num.unit_type[0] = B_UNIT_NONE; + + t->flag |= T_NO_CONSTRAINT | T_NO_PROJECT; +} + +void initEdgeSlide(TransInfo *t) +{ + initEdgeSlide_ex(t, true, false, false, true); +} +/** \} */ |