|
|
|
@@ -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,
|
|
|
|
|