/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edtransform */ #include #include "MEM_guardedalloc.h" #include "BLI_math_matrix.h" #include "BLI_string.h" #include "BKE_context.hh" #include "BKE_editmesh.hh" #include "BKE_unit.h" #include "GPU_immediate.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "ED_screen.hh" #include "WM_api.hh" #include "WM_types.hh" #include "RNA_access.hh" #include "UI_interface.hh" #include "UI_resources.hh" #include "BLT_translation.h" #include "transform.hh" #include "transform_constraints.hh" #include "transform_convert.hh" #include "transform_mode.hh" #include "transform_snap.hh" using namespace blender; /* -------------------------------------------------------------------- */ /** \name Transform (Vert Slide) * \{ */ struct TransDataVertSlideVert { /** #TransDataGenericSlideVert (header) */ BMVert *v; LinkNode **cd_loop_groups; float co_orig_3d[3]; /* end generic */ float (*co_link_orig_3d)[3]; int co_link_tot; int co_link_curr; }; struct VertSlideData { TransDataVertSlideVert *sv; int totsv; int curr_sv_index; /* result of ED_view3d_ob_project_mat_get */ float proj_mat[4][4]; }; struct VertSlideParams { float perc; bool use_even; bool flipped; }; static void vert_slide_update_input(TransInfo *t) { VertSlideParams *slp = static_cast(t->custom.mode.data); VertSlideData *sld = static_cast( TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data); TransDataVertSlideVert *sv = &sld->sv[sld->curr_sv_index]; const float *co_orig_3d = sv->co_orig_3d; const float *co_curr_3d = sv->co_link_orig_3d[sv->co_link_curr]; float co_curr_2d[2], co_orig_2d[2]; int mval_ofs[2], mval_start[2], mval_end[2]; ED_view3d_project_float_v2_m4(t->region, co_orig_3d, co_orig_2d, sld->proj_mat); ED_view3d_project_float_v2_m4(t->region, co_curr_3d, co_curr_2d, sld->proj_mat); ARRAY_SET_ITEMS(mval_ofs, t->mouse.imval[0] - co_orig_2d[0], t->mouse.imval[1] - co_orig_2d[1]); ARRAY_SET_ITEMS(mval_start, co_orig_2d[0] + mval_ofs[0], co_orig_2d[1] + mval_ofs[1]); ARRAY_SET_ITEMS(mval_end, co_curr_2d[0] + mval_ofs[0], co_curr_2d[1] + mval_ofs[1]); if (slp->flipped && slp->use_even) { setCustomPoints(t, &t->mouse, mval_start, mval_end); } else { setCustomPoints(t, &t->mouse, mval_end, mval_start); } } static void calcVertSlideCustomPoints(TransInfo *t) { vert_slide_update_input(t); /* 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); } /** * Run once when initializing vert slide to find the reference edge */ static void calcVertSlideMouseActiveVert(TransInfo *t, const float2 &mval_fl) { /* Active object may have no selected vertices. */ VertSlideData *sld = static_cast( TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data); TransDataVertSlideVert *sv; /* set the vertex to use as a reference for the mouse direction 'curr_sv_index' */ float dist_sq = 0.0f; float dist_min_sq = FLT_MAX; int i; for (i = 0, sv = sld->sv; i < sld->totsv; i++, sv++) { float co_2d[2]; ED_view3d_project_float_v2_m4(t->region, sv->co_orig_3d, co_2d, sld->proj_mat); dist_sq = len_squared_v2v2(mval_fl, co_2d); if (dist_sq < dist_min_sq) { dist_min_sq = dist_sq; sld->curr_sv_index = i; } } } /** * Run while moving the mouse to slide along the edge matching the mouse direction */ static void calcVertSlideMouseActiveEdges(TransInfo *t, const float2 &mval_fl) { VertSlideData *sld = static_cast( TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data); float dir[3]; TransDataVertSlideVert *sv; int i; /* NOTE: we could save a matrix-multiply for each vertex * by finding the closest edge in local-space. * However this skews the outcome with non-uniform-scale. */ /* First get the direction of the original mouse position. */ sub_v2_v2v2(dir, t->mouse.imval, mval_fl); ED_view3d_win_to_delta(t->region, dir, t->zfac, dir); normalize_v3(dir); for (i = 0, sv = sld->sv; i < sld->totsv; i++, sv++) { if (sv->co_link_tot > 1) { float dir_dot_best = -FLT_MAX; int co_link_curr_best = -1; int j; for (j = 0; j < sv->co_link_tot; j++) { float tdir[3]; float dir_dot; sub_v3_v3v3(tdir, sv->co_orig_3d, sv->co_link_orig_3d[j]); mul_mat3_m4_v3(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->object_to_world, tdir); project_plane_v3_v3v3(tdir, tdir, t->viewinv[2]); normalize_v3(tdir); dir_dot = dot_v3v3(dir, tdir); if (dir_dot > dir_dot_best) { dir_dot_best = dir_dot; co_link_curr_best = j; } } if (co_link_curr_best != -1) { sv->co_link_curr = co_link_curr_best; } } } } static VertSlideData *createVertSlideVerts(TransInfo *t, const TransDataContainer *tc) { BMEditMesh *em = BKE_editmesh_from_object(tc->obedit); BMesh *bm = em->bm; BMIter iter; BMIter eiter; BMEdge *e; BMVert *v; TransDataVertSlideVert *sv_array; VertSlideData *sld = static_cast(MEM_callocN(sizeof(*sld), "sld")); int j; sld->curr_sv_index = 0; j = 0; BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { bool ok = false; if (BM_elem_flag_test(v, BM_ELEM_SELECT) && v->e) { BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { ok = true; break; } } } if (ok) { BM_elem_flag_enable(v, BM_ELEM_TAG); j += 1; } else { BM_elem_flag_disable(v, BM_ELEM_TAG); } } if (!j) { MEM_freeN(sld); return nullptr; } sv_array = static_cast( MEM_callocN(sizeof(TransDataVertSlideVert) * j, "sv_array")); j = 0; BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { if (BM_elem_flag_test(v, BM_ELEM_TAG)) { int k; sv_array[j].v = v; copy_v3_v3(sv_array[j].co_orig_3d, v->co); k = 0; BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { k++; } } sv_array[j].co_link_orig_3d = static_cast( MEM_mallocN(sizeof(*sv_array[j].co_link_orig_3d) * k, __func__)); sv_array[j].co_link_tot = k; k = 0; BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) { if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { BMVert *v_other = BM_edge_other_vert(e, v); copy_v3_v3(sv_array[j].co_link_orig_3d[k], v_other->co); k++; } } j++; } } sld->sv = sv_array; sld->totsv = j; /* most likely will be set below */ unit_m4(sld->proj_mat); if (t->spacetype == SPACE_VIEW3D) { /* view vars */ RegionView3D *rv3d = nullptr; ARegion *region = t->region; rv3d = static_cast(region ? region->regiondata : nullptr); if (rv3d) { ED_view3d_ob_project_mat_get(rv3d, tc->obedit, sld->proj_mat); } } return sld; } static void freeVertSlideVerts(TransInfo * /*t*/, TransDataContainer * /*tc*/, TransCustomData *custom_data) { VertSlideData *sld = static_cast(custom_data->data); if (!sld) { return; } if (sld->totsv > 0) { TransDataVertSlideVert *sv = sld->sv; int i = 0; for (i = 0; i < sld->totsv; i++, sv++) { MEM_freeN(sv->co_link_orig_3d); } } MEM_freeN(sld->sv); MEM_freeN(sld); custom_data->data = nullptr; } static eRedrawFlag handleEventVertSlide(TransInfo *t, const wmEvent *event) { VertSlideParams *slp = static_cast(t->custom.mode.data); if (slp) { switch (event->type) { case EVT_EKEY: if (event->val == KM_PRESS) { slp->use_even = !slp->use_even; if (slp->flipped) { calcVertSlideCustomPoints(t); } return TREDRAW_HARD; } break; case EVT_FKEY: if (event->val == KM_PRESS) { slp->flipped = !slp->flipped; calcVertSlideCustomPoints(t); return TREDRAW_HARD; } break; case EVT_CKEY: /* use like a modifier key */ if (event->val == KM_PRESS) { t->flag ^= T_ALT_TRANSFORM; calcVertSlideCustomPoints(t); return TREDRAW_HARD; } break; case MOUSEMOVE: { /* don't recalculate the best edge */ const bool is_clamp = !(t->flag & T_ALT_TRANSFORM); if (is_clamp) { calcVertSlideMouseActiveEdges(t, float2(event->mval)); } calcVertSlideCustomPoints(t); break; } default: break; } } return TREDRAW_NOTHING; } static void drawVertSlide(TransInfo *t) { if (TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data) { const VertSlideParams *slp = static_cast(t->custom.mode.data); VertSlideData *sld = static_cast( TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data); const bool is_clamp = !(t->flag & T_ALT_TRANSFORM); /* Non-Prop mode */ { TransDataVertSlideVert *curr_sv = &sld->sv[sld->curr_sv_index]; TransDataVertSlideVert *sv; const float ctrl_size = UI_GetThemeValuef(TH_FACEDOT_SIZE) + 1.5f; const float line_size = UI_GetThemeValuef(TH_OUTLINE_WIDTH) + 0.5f; const int alpha_shade = -160; int i; GPU_depth_test(GPU_DEPTH_NONE); GPU_blend(GPU_BLEND_ALPHA); GPU_matrix_push(); GPU_matrix_mul(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->object_to_world); GPU_line_width(line_size); const uint shdr_pos = GPU_vertformat_attr_add( immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); immUniformThemeColorShadeAlpha(TH_EDGE_SELECT, 80, alpha_shade); immBegin(GPU_PRIM_LINES, sld->totsv * 2); if (is_clamp) { sv = sld->sv; for (i = 0; i < sld->totsv; i++, sv++) { immVertex3fv(shdr_pos, sv->co_orig_3d); immVertex3fv(shdr_pos, sv->co_link_orig_3d[sv->co_link_curr]); } } else { sv = sld->sv; for (i = 0; i < sld->totsv; i++, sv++) { float a[3], b[3]; sub_v3_v3v3(a, sv->co_link_orig_3d[sv->co_link_curr], sv->co_orig_3d); mul_v3_fl(a, 100.0f); negate_v3_v3(b, a); add_v3_v3(a, sv->co_orig_3d); add_v3_v3(b, sv->co_orig_3d); immVertex3fv(shdr_pos, a); immVertex3fv(shdr_pos, b); } } immEnd(); GPU_point_size(ctrl_size); immBegin(GPU_PRIM_POINTS, 1); immVertex3fv(shdr_pos, (slp->flipped && slp->use_even) ? curr_sv->co_link_orig_3d[curr_sv->co_link_curr] : curr_sv->co_orig_3d); immEnd(); immUnbindProgram(); /* direction from active vertex! */ if (!compare_v2v2(t->mval, t->mouse.imval, FLT_EPSILON)) { float zfac; float co_orig_3d[3]; float co_dest_3d[3]; float2 xy_delta = t->mval - t->mouse.imval; mul_v3_m4v3(co_orig_3d, TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->object_to_world, curr_sv->co_orig_3d); zfac = ED_view3d_calc_zfac(static_cast(t->region->regiondata), co_orig_3d); ED_view3d_win_to_delta(t->region, xy_delta, zfac, co_dest_3d); invert_m4_m4(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->world_to_object, TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->object_to_world); mul_mat3_m4_v3(TRANS_DATA_CONTAINER_FIRST_OK(t)->obedit->world_to_object, co_dest_3d); add_v3_v3(co_dest_3d, curr_sv->co_orig_3d); GPU_line_width(1.0f); immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR); float viewport_size[4]; GPU_viewport_size_get_f(viewport_size); immUniform2f("viewport_size", viewport_size[2], viewport_size[3]); immUniform1i("colors_len", 0); /* "simple" mode */ immUniformColor4f(1.0f, 1.0f, 1.0f, 1.0f); immUniform1f("dash_width", 6.0f); immUniform1f("udash_factor", 0.5f); immBegin(GPU_PRIM_LINES, 2); immVertex3fv(shdr_pos, curr_sv->co_orig_3d); immVertex3fv(shdr_pos, co_dest_3d); immEnd(); immUnbindProgram(); } GPU_matrix_pop(); GPU_depth_test(GPU_DEPTH_LESS_EQUAL); } } } static void vert_slide_apply_elem(const TransDataVertSlideVert *sv, const float perc, const bool use_even, const bool use_flip, float r_co[3]) { if (use_even == false) { interp_v3_v3v3(r_co, sv->co_orig_3d, sv->co_link_orig_3d[sv->co_link_curr], perc); } else { float dir[3]; sub_v3_v3v3(dir, sv->co_link_orig_3d[sv->co_link_curr], sv->co_orig_3d); float edge_len = normalize_v3(dir); if (edge_len > FLT_EPSILON) { if (use_flip) { madd_v3_v3v3fl(r_co, sv->co_link_orig_3d[sv->co_link_curr], dir, -perc); } else { madd_v3_v3v3fl(r_co, sv->co_orig_3d, dir, perc); } } else { copy_v3_v3(r_co, sv->co_orig_3d); } } } static void doVertSlide(TransInfo *t, float perc) { VertSlideParams *slp = static_cast(t->custom.mode.data); slp->perc = perc; const bool use_even = slp->use_even; FOREACH_TRANS_DATA_CONTAINER (t, tc) { VertSlideData *sld = static_cast(tc->custom.mode.data); if (sld == nullptr) { continue; } float tperc = perc; if (use_even) { TransDataVertSlideVert *sv_curr = &sld->sv[sld->curr_sv_index]; const float edge_len_curr = len_v3v3(sv_curr->co_orig_3d, sv_curr->co_link_orig_3d[sv_curr->co_link_curr]); tperc *= edge_len_curr; } TransDataVertSlideVert *sv = sld->sv; for (int i = 0; i < sld->totsv; i++, sv++) { vert_slide_apply_elem(sv, tperc, use_even, slp->flipped, sv->v->co); } } } static void vert_slide_snap_apply(TransInfo *t, float *value) { TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_OK(t); VertSlideData *sld = static_cast(tc->custom.mode.data); TransDataVertSlideVert *sv = &sld->sv[sld->curr_sv_index]; float snap_point[3], co_orig_3d[3], co_curr_3d[3], dvec[3]; copy_v3_v3(co_orig_3d, sv->co_orig_3d); copy_v3_v3(co_curr_3d, sv->co_link_orig_3d[sv->co_link_curr]); if (tc->use_local_mat) { mul_m4_v3(tc->mat, co_orig_3d); mul_m4_v3(tc->mat, co_curr_3d); } getSnapPoint(t, dvec); sub_v3_v3(dvec, t->tsnap.snap_source); if (t->tsnap.target_type & (SCE_SNAP_TO_EDGE | SCE_SNAP_TO_FACE)) { float co_dir[3]; sub_v3_v3v3(co_dir, co_curr_3d, co_orig_3d); normalize_v3(co_dir); if (t->tsnap.target_type & SCE_SNAP_TO_EDGE) { transform_constraint_snap_axis_to_edge(t, co_dir, dvec); } else { transform_constraint_snap_axis_to_face(t, co_dir, dvec); } } add_v3_v3v3(snap_point, co_orig_3d, dvec); *value = line_point_factor_v3(snap_point, co_orig_3d, co_curr_3d); } static void applyVertSlide(TransInfo *t) { char str[UI_MAX_DRAW_STR]; size_t ofs = 0; float final; VertSlideParams *slp = static_cast(t->custom.mode.data); const bool flipped = slp->flipped; const 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] + t->values_modal_offset[0]; transform_snap_mixed_apply(t, &final); if (!validSnap(t)) { transform_snap_increment(t, &final); } /* only do this so out of range values are not displayed */ if (is_constrained) { CLAMP(final, 0.0f, 1.0f); } applyNumInput(&t->num, &final); t->values_final[0] = final; /* header string */ ofs += BLI_strncpy_rlen(str + ofs, TIP_("Vertex 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_rlen(str + ofs, sizeof(str) - ofs, "%.4f ", final); } ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(E)ven: %s, "), WM_bool_as_string(use_even)); if (use_even) { ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("(F)lipped: %s, "), WM_bool_as_string(flipped)); } ofs += BLI_snprintf_rlen( str + ofs, sizeof(str) - ofs, TIP_("Alt or (C)lamp: %s"), WM_bool_as_string(is_clamp)); /* done with header string */ /* do stuff here */ doVertSlide(t, final); recalc_data(t); ED_area_status_text(t->area, str); } static void vert_slide_transform_matrix_fn(TransInfo *t, float mat_xform[4][4]) { float delta[3], orig_co[3], final_co[3]; VertSlideParams *slp = static_cast(t->custom.mode.data); TransDataContainer *tc = TRANS_DATA_CONTAINER_FIRST_OK(t); VertSlideData *sld_active = static_cast(tc->custom.mode.data); TransDataVertSlideVert *sv_active = &sld_active->sv[sld_active->curr_sv_index]; copy_v3_v3(orig_co, sv_active->co_orig_3d); float tperc = t->values_final[0]; if (slp->use_even) { const float edge_len_curr = len_v3v3(sv_active->co_orig_3d, sv_active->co_link_orig_3d[sv_active->co_link_curr]); tperc *= edge_len_curr; } vert_slide_apply_elem(sv_active, tperc, slp->use_even, slp->flipped, final_co); if (tc->use_local_mat) { mul_m4_v3(tc->mat, orig_co); mul_m4_v3(tc->mat, final_co); } sub_v3_v3v3(delta, final_co, orig_co); add_v3_v3(mat_xform[3], delta); } static void initVertSlide_ex(TransInfo *t, bool use_even, bool flipped, bool use_clamp) { t->mode = TFM_VERT_SLIDE; { VertSlideParams *slp = static_cast(MEM_callocN(sizeof(*slp), __func__)); slp->use_even = use_even; 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; } bool ok = false; FOREACH_TRANS_DATA_CONTAINER (t, tc) { VertSlideData *sld = createVertSlideVerts(t, tc); if (sld) { tc->custom.mode.data = sld; tc->custom.mode.free_cb = freeVertSlideVerts; ok = true; } } if (ok == false) { t->state = TRANS_CANCEL; return; } calcVertSlideMouseActiveVert(t, t->mval); calcVertSlideMouseActiveEdges(t, t->mval); /* set custom point first if you want value to be initialized by init */ calcVertSlideCustomPoints(t); initMouseInputMode(t, &t->mouse, INPUT_CUSTOM_RATIO); t->idx_max = 0; t->num.idx_max = 0; t->snap[0] = 0.1f; t->snap[1] = t->snap[0] * 0.1f; copy_v3_fl(t->num.val_inc, t->snap[0]); t->num.unit_sys = t->scene->unit.system; t->num.unit_type[0] = B_UNIT_NONE; } static void initVertSlide(TransInfo *t, wmOperator *op) { bool use_even = false; bool flipped = false; bool use_clamp = true; if (op) { use_even = RNA_boolean_get(op->ptr, "use_even"); flipped = RNA_boolean_get(op->ptr, "flipped"); use_clamp = RNA_boolean_get(op->ptr, "use_clamp"); } initVertSlide_ex(t, use_even, flipped, use_clamp); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Mouse Input Utilities * \{ */ void transform_mode_vert_slide_reproject_input(TransInfo *t) { if (t->spacetype == SPACE_VIEW3D) { RegionView3D *rv3d = static_cast(t->region->regiondata); FOREACH_TRANS_DATA_CONTAINER (t, tc) { VertSlideData *sld = static_cast(tc->custom.mode.data); ED_view3d_ob_project_mat_get(rv3d, tc->obedit, sld->proj_mat); } } vert_slide_update_input(t); } /** \} */ TransModeInfo TransMode_vertslide = { /*flags*/ T_NO_CONSTRAINT, /*init_fn*/ initVertSlide, /*transform_fn*/ applyVertSlide, /*transform_matrix_fn*/ vert_slide_transform_matrix_fn, /*handle_event_fn*/ handleEventVertSlide, /*snap_distance_fn*/ transform_snap_distance_len_squared_fn, /*snap_apply_fn*/ vert_slide_snap_apply, /*draw_fn*/ drawVertSlide, };