tornavis/source/blender/editors/transform/transform_mode_edge_slide.c

1583 lines
43 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2001-2002 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_constraints.h"
#include "transform_convert.h"
#include "transform_mode.h"
#include "transform_snap.h"
/* -------------------------------------------------------------------- */
/** \name Transform (Edge Slide)
* \{ */
typedef struct TransDataEdgeSlideVert {
/** #TransDataGenericSlideVert (header) */
struct BMVert *v;
struct LinkNode **cd_loop_groups;
float v_co_orig[3];
/* end generic */
float edge_len;
struct BMVert *v_side[2];
/* add origvert.co to get the original locations */
float dir_side[2][3];
int loop_nr;
} TransDataEdgeSlideVert;
typedef struct EdgeSlideData {
TransDataEdgeSlideVert *sv;
int totsv;
int mval_start[2], mval_end[2];
int curr_sv_index;
} EdgeSlideData;
typedef struct EdgeSlideParams {
float perc;
/** when un-clamped - use this index: #TransDataEdgeSlideVert.dir_side */
int curr_side_unclamp;
bool use_even;
bool flipped;
} EdgeSlideParams;
/**
* Get the first valid TransDataContainer *.
*
* 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 TransDataContainer *edge_slide_container_first_ok(TransInfo *t)
{
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
if (tc->custom.mode.data) {
return tc;
}
}
BLI_assert_msg(0, "Should never happen, at least one EdgeSlideData should be valid");
return NULL;
}
static EdgeSlideData *edgeSlideFirstGet(TransInfo *t)
{
TransDataContainer *tc = edge_slide_container_first_ok(t);
return tc->custom.mode.data;
}
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;
}
/* Interpolates 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 itself, 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;
}
/* 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;
}
static void edge_slide_projmat_get(TransInfo *t, TransDataContainer *tc, float r_projectMat[4][4])
{
RegionView3D *rv3d = NULL;
if (t->spacetype == SPACE_VIEW3D) {
/* Background mode support. */
rv3d = t->region ? t->region->regiondata : NULL;
}
if (!rv3d) {
/* Ok, let's try to survive this. */
unit_m4(r_projectMat);
}
else {
ED_view3d_ob_project_mat_get(rv3d, tc->obedit, r_projectMat);
}
}
static void edge_slide_pair_project(TransDataEdgeSlideVert *sv,
ARegion *region,
float projectMat[4][4],
float r_sco_a[3],
float r_sco_b[3])
{
BMVert *v = sv->v;
if (sv->v_side[1]) {
ED_view3d_project_float_v3_m4(region, sv->v_side[1]->co, r_sco_b, projectMat);
}
else {
add_v3_v3v3(r_sco_b, v->co, sv->dir_side[1]);
ED_view3d_project_float_v3_m4(region, r_sco_b, r_sco_b, projectMat);
}
if (sv->v_side[0]) {
ED_view3d_project_float_v3_m4(region, sv->v_side[0]->co, r_sco_a, projectMat);
}
else {
add_v3_v3v3(r_sco_a, v->co, sv->dir_side[0]);
ED_view3d_project_float_v3_m4(region, r_sco_a, r_sco_a, projectMat);
}
}
static void edge_slide_data_init_mval(MouseInput *mi, EdgeSlideData *sld, float *mval_dir)
{
/* 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;
}
float mval_start[2], mval_end[2];
/* 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] = mi->imval[0] + mval_start[0];
sld->mval_start[1] = mi->imval[1] + mval_start[1];
sld->mval_end[0] = mi->imval[0] + mval_end[0];
sld->mval_end[1] = mi->imval[1] + mval_end[1];
}
/**
* Calculate screen-space `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 *region = t->region;
View3D *v3d = NULL;
float projectMat[4][4];
BMBVHTree *bmbvh;
/* only for use_calc_direction */
float(*loop_dir)[3] = NULL, *loop_maxdist = NULL;
float mval_dir[3], dist_best_sq;
if (t->spacetype == SPACE_VIEW3D) {
/* background mode support */
v3d = t->area ? t->area->spacedata.first : NULL;
}
edge_slide_projmat_get(t, tc, 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, region, v3d, tc->obedit);
if (!is_visible && !use_calc_direction) {
continue;
}
edge_slide_pair_project(sv, region, projectMat, sco_a, sco_b);
/* 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);
}
edge_slide_data_init_mval(&t->mouse, sld, mval_dir);
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 *region = t->region;
float projectMat[4][4];
int i = 0;
float v_proj[2];
float dist_sq = 0;
float dist_min_sq = FLT_MAX;
edge_slide_projmat_get(t, tc, 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(region, 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 EdgeSlideData *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");
const 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) {
/* Invalid edge selection. */
MEM_freeN(sld);
return NULL;
}
}
}
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 NULL;
}
}
}
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 NULL;
}
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: #40422)
*/
#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) == (uint)sv_tot);
sld->sv = sv_array;
sld->totsv = sv_tot;
/* use for visibility checks */
if (t->spacetype == SPACE_VIEW3D) {
v3d = t->area ? t->area->spacedata.first : NULL;
rv3d = t->region ? t->region->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);
}
MEM_freeN(sv_table);
return sld;
}
/**
* A simple version of #createEdgeSlideVerts_double_side
* Which assumes the longest unselected.
*/
static EdgeSlideData *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");
const 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->area ? t->area->spacedata.first : NULL;
rv3d = t->region ? t->region->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 NULL;
}
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 don't 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->area ? t->area->spacedata.first : NULL;
rv3d = t->region ? t->region->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);
}
MEM_freeN(sv_table);
return sld;
}
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 EVT_EKEY:
if (event->val == KM_PRESS) {
slp->use_even = !slp->use_even;
calcEdgeSlideCustomPoints(t);
return TREDRAW_HARD;
}
break;
case EVT_FKEY:
if (event->val == KM_PRESS) {
slp->flipped = !slp->flipped;
calcEdgeSlideCustomPoints(t);
return TREDRAW_HARD;
}
break;
case EVT_CKEY:
/* use like a modifier key */
if (event->val == KM_PRESS) {
t->flag ^= T_ALT_TRANSFORM;
calcEdgeSlideCustomPoints(t);
return TREDRAW_HARD;
}
break;
case MOUSEMOVE:
calcEdgeSlideCustomPoints(t);
break;
default:
break;
}
}
}
return TREDRAW_NOTHING;
}
void drawEdgeSlide(TransInfo *t)
{
if (t->mode != TFM_EDGE_SLIDE) {
return;
}
EdgeSlideData *sld = edgeSlideFirstGet(t);
if (sld == NULL) {
return;
}
const EdgeSlideParams *slp = t->custom.mode.data;
const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
const float line_size = UI_GetThemeValuef(TH_OUTLINE_WIDTH) + 0.5f;
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);
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) {
/* Even mode */
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 = slp->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(@ideasman42): 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 {
/* Common case. */
TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
const int alpha_shade = -160;
float co_dir[3];
add_v3_v3v3(co_dir, curr_sv->v_co_orig, curr_sv->dir_side[slp->curr_side_unclamp]);
GPU_line_width(line_size);
immUniformThemeColorShadeAlpha(TH_EDGE_SELECT, 80, alpha_shade);
immBeginAtMost(GPU_PRIM_LINES, 2);
immVertex3fv(pos, curr_sv->v_co_orig);
immVertex3fv(pos, co_dir);
immEnd();
}
immUnbindProgram();
GPU_matrix_pop();
GPU_blend(GPU_BLEND_NONE);
GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
}
static void edge_slide_snap_apply(TransInfo *t, float *value)
{
TransDataContainer *tc = edge_slide_container_first_ok(t);
EdgeSlideParams *slp = t->custom.mode.data;
EdgeSlideData *sld_active = tc->custom.mode.data;
TransDataEdgeSlideVert *sv = &sld_active->sv[sld_active->curr_sv_index];
float snap_point[3], co_orig[3], co_dest[2][3], dvec[3];
copy_v3_v3(co_orig, sv->v_co_orig);
add_v3_v3v3(co_dest[0], co_orig, sv->dir_side[0]);
add_v3_v3v3(co_dest[1], co_orig, sv->dir_side[1]);
if (tc->use_local_mat) {
mul_m4_v3(tc->mat, co_orig);
mul_m4_v3(tc->mat, co_dest[0]);
mul_m4_v3(tc->mat, co_dest[1]);
}
getSnapPoint(t, dvec);
sub_v3_v3(dvec, t->tsnap.snap_source);
add_v3_v3v3(snap_point, co_orig, dvec);
float perc = *value;
int side_index;
float t_mid;
if (slp->use_even == false) {
const bool is_clamp = !(t->flag & T_ALT_TRANSFORM);
if (is_clamp) {
side_index = perc < 0.0f;
}
else {
side_index = slp->curr_side_unclamp;
}
}
else {
/* Could be pre-calculated. */
t_mid = line_point_factor_v3((float[3]){0.0f, 0.0f, 0.0f}, sv->dir_side[0], sv->dir_side[1]);
float t_snap = line_point_factor_v3(snap_point, co_dest[0], co_dest[1]);
side_index = t_snap >= t_mid;
}
if (t->tsnap.snapElem & (SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE_RAYCAST)) {
float co_dir[3];
sub_v3_v3v3(co_dir, co_dest[side_index], co_orig);
normalize_v3(co_dir);
if (t->tsnap.snapElem & SCE_SNAP_MODE_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, dvec);
}
perc = line_point_factor_v3(snap_point, co_orig, co_dest[side_index]);
if (slp->use_even == false) {
if (side_index) {
perc *= -1;
}
}
else {
if (!side_index) {
perc = (1.0f - perc) * t_mid;
}
else {
perc = perc * (1.0f - t_mid) + t_mid;
}
if (slp->flipped) {
perc = 1.0f - perc;
}
perc = (2 * perc) - 1.0f;
if (!slp->flipped) {
perc *= -1;
}
}
*value = perc;
}
static void edge_slide_apply_elem(const TransDataEdgeSlideVert *sv,
const float fac,
const float curr_length_fac,
const int curr_side_unclamp,
const bool use_clamp,
const bool use_even,
const bool use_flip,
float r_co[3])
{
copy_v3_v3(r_co, sv->v_co_orig);
if (use_even == false) {
if (use_clamp) {
const int side_index = (fac < 0.0f);
const float fac_final = fabsf(fac);
madd_v3_v3fl(r_co, sv->dir_side[side_index], fac_final);
}
else {
int side_index = curr_side_unclamp;
if (is_zero_v3(sv->dir_side[side_index])) {
side_index = !side_index;
}
const float fac_final = (side_index == (fac < 0.0f) ? fabsf(fac) : -fabsf(fac));
madd_v3_v3fl(r_co, sv->dir_side[side_index], fac_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.
*/
if (sv->edge_len > FLT_EPSILON) {
float co_a[3], co_b[3];
const float fac_final = min_ff(sv->edge_len, curr_length_fac) / 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 (use_flip) {
interp_line_v3_v3v3v3(r_co, co_b, sv->v_co_orig, co_a, fac_final);
}
else {
interp_line_v3_v3v3v3(r_co, co_a, sv->v_co_orig, co_b, fac_final);
}
}
}
}
static void doEdgeSlide(TransInfo *t, float perc)
{
EdgeSlideParams *slp = t->custom.mode.data;
EdgeSlideData *sld_active = edgeSlideFirstGet(t);
slp->perc = perc;
const bool use_clamp = !(t->flag & T_ALT_TRANSFORM);
const bool use_even = slp->use_even;
const bool use_flip = slp->flipped;
const int curr_side_unclamp = slp->curr_side_unclamp;
float curr_length_fac = 0.0f;
if (use_even) {
TransDataEdgeSlideVert *sv_active = &sld_active->sv[sld_active->curr_sv_index];
curr_length_fac = sv_active->edge_len * (((use_flip ? perc : -perc) + 1.0f) / 2.0f);
}
else if (use_clamp) {
slp->curr_side_unclamp = (perc < 0.0f);
}
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++) {
edge_slide_apply_elem(
sv, perc, curr_length_fac, curr_side_unclamp, use_clamp, use_even, use_flip, sv->v->co);
}
}
}
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] + 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, -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_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 */
doEdgeSlide(t, final);
recalcData(t);
ED_area_status_text(t->area, 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;
t->transform_matrix = NULL;
t->tsnap.snap_mode_apply_fn = edge_slide_snap_apply;
t->tsnap.snap_mode_distance_fn = transform_snap_distance_len_squared_fn;
{
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;
}
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
sld = use_double_side ? createEdgeSlideVerts_double_side(t, tc) :
createEdgeSlideVerts_single_side(t, tc);
if (sld) {
tc->custom.mode.data = sld;
tc->custom.mode.free_cb = freeEdgeSlideVerts;
ok = true;
}
}
if (!ok) {
t->state = TRANS_CANCEL;
return;
}
/* 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.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;
t->flag |= T_NO_CONSTRAINT | T_NO_PROJECT;
}
void initEdgeSlide(TransInfo *t)
{
initEdgeSlide_ex(t, true, false, false, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mouse Input Utilities
* \{ */
void transform_mode_edge_slide_reproject_input(TransInfo *t)
{
ARegion *region = t->region;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
EdgeSlideData *sld = tc->custom.mode.data;
if (sld) {
float projectMat[4][4];
edge_slide_projmat_get(t, tc, projectMat);
TransDataEdgeSlideVert *curr_sv = &sld->sv[sld->curr_sv_index];
float mval_dir[3], sco_a[3], sco_b[3];
edge_slide_pair_project(curr_sv, region, projectMat, sco_a, sco_b);
sub_v3_v3v3(mval_dir, sco_b, sco_a);
edge_slide_data_init_mval(&t->mouse, sld, mval_dir);
}
}
EdgeSlideData *sld = edgeSlideFirstGet(t);
setCustomPoints(t, &t->mouse, sld->mval_end, sld->mval_start);
}
/** \} */