UV: Support Edge and Vert Slide

This commit adapts the existing Edge Slide and Vert Slide to work with
UVs as well.

It addresses [0] for the addition of an edge slide tool to the UV
Editor.

This feature allows users to slide UVs towards adjacent edges,
providing more convenient ways to edit UV maps.

[0]: https://blender.community/c/rightclickselect/66dbbc

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