Change `cd_loop_uv_offset` from signed to unsigned, forcing a crash if passed invalid input. Differential Revision: https://developer.blender.org/D15722
976 lines
31 KiB
C
976 lines
31 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup eduv
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_linklist_stack.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_image_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_editmesh.h"
|
|
#include "BKE_layer.h"
|
|
#include "BKE_report.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "ED_screen.h"
|
|
#include "ED_transform.h"
|
|
#include "ED_uvedit.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "UI_view2d.h"
|
|
|
|
#include "uvedit_intern.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Loop Rip Data Struct
|
|
* \{ */
|
|
|
|
/** Unordered loop data, stored in #BMLoop.head.index. */
|
|
typedef struct ULData {
|
|
/** When the specified UV edge is selected. */
|
|
uint is_select_edge : 1;
|
|
/**
|
|
* When only this UV is selected and none of the other UV's
|
|
* around the connected fan are attached to an edge.
|
|
*
|
|
* In this case there is no need to detect contiguous loops,
|
|
* each isolated case is handled on its own, no need to walk over selected edges.
|
|
*
|
|
* \note This flag isn't flushed to other loops which could also have this enabled.
|
|
* Currently it's not necessary since we can start off on any one of these loops,
|
|
* then walk onto the other loops around the uv-fan, without having the flag to be
|
|
* set on all loops.
|
|
*/
|
|
uint is_select_vert_single : 1;
|
|
/** This could be a face-tag. */
|
|
uint is_select_all : 1;
|
|
/** Use when building the rip-pairs stack. */
|
|
uint in_stack : 1;
|
|
/** Set once this has been added into a #UVRipPairs. */
|
|
uint in_rip_pairs : 1;
|
|
/** The side this loop is part of. */
|
|
uint side : 1;
|
|
/**
|
|
* Paranoid check to ensure we don't enter eternal loop swapping sides,
|
|
* this could happen with float precision error, making a swap to measure as slightly better
|
|
* depending on the order of addition.
|
|
*/
|
|
uint side_was_swapped : 1;
|
|
} ULData;
|
|
|
|
/** Ensure this fits in an int (loop index). */
|
|
BLI_STATIC_ASSERT(sizeof(ULData) <= sizeof(int), "");
|
|
|
|
BLI_INLINE ULData *UL(BMLoop *l)
|
|
{
|
|
return (ULData *)&l->head.index;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Utilities
|
|
* \{ */
|
|
|
|
static BMLoop *bm_loop_find_other_radial_loop_with_visible_face(BMLoop *l_src,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
BMLoop *l_other = NULL;
|
|
BMLoop *l_iter = l_src->radial_next;
|
|
if (l_iter != l_src) {
|
|
do {
|
|
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) && UL(l_iter)->is_select_edge &&
|
|
BM_loop_uv_share_edge_check(l_src, l_iter, cd_loop_uv_offset)) {
|
|
/* Check UV's are contiguous. */
|
|
if (l_other == NULL) {
|
|
l_other = l_iter;
|
|
}
|
|
else {
|
|
/* Only use when there is a single alternative. */
|
|
l_other = NULL;
|
|
break;
|
|
}
|
|
}
|
|
} while ((l_iter = l_iter->radial_next) != l_src);
|
|
}
|
|
return l_other;
|
|
}
|
|
|
|
static BMLoop *bm_loop_find_other_fan_loop_with_visible_face(BMLoop *l_src,
|
|
BMVert *v_src,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
BLI_assert(BM_vert_in_edge(l_src->e, v_src));
|
|
BMLoop *l_other = NULL;
|
|
BMLoop *l_iter = l_src->radial_next;
|
|
if (l_iter != l_src) {
|
|
do {
|
|
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) &&
|
|
BM_loop_uv_share_edge_check(l_src, l_iter, cd_loop_uv_offset)) {
|
|
/* Check UV's are contiguous. */
|
|
if (l_other == NULL) {
|
|
l_other = l_iter;
|
|
}
|
|
else {
|
|
/* Only use when there is a single alternative. */
|
|
l_other = NULL;
|
|
break;
|
|
}
|
|
}
|
|
} while ((l_iter = l_iter->radial_next) != l_src);
|
|
}
|
|
if (l_other != NULL) {
|
|
if (l_other->v == v_src) {
|
|
/* do nothing. */
|
|
}
|
|
else if (l_other->next->v == v_src) {
|
|
l_other = l_other->next;
|
|
}
|
|
else if (l_other->prev->v == v_src) {
|
|
l_other = l_other->prev;
|
|
}
|
|
else {
|
|
BLI_assert_unreachable();
|
|
}
|
|
}
|
|
return l_other;
|
|
}
|
|
|
|
/**
|
|
* A version of #BM_vert_step_fan_loop that checks UV's.
|
|
*/
|
|
static BMLoop *bm_vert_step_fan_loop_uv(BMLoop *l, BMEdge **e_step, const int cd_loop_uv_offset)
|
|
{
|
|
BMEdge *e_prev = *e_step;
|
|
BMLoop *l_next;
|
|
if (l->e == e_prev) {
|
|
l_next = l->prev;
|
|
}
|
|
else if (l->prev->e == e_prev) {
|
|
l_next = l;
|
|
}
|
|
else {
|
|
BLI_assert_unreachable();
|
|
return NULL;
|
|
}
|
|
|
|
*e_step = l_next->e;
|
|
|
|
return bm_loop_find_other_fan_loop_with_visible_face(l_next, l->v, cd_loop_uv_offset);
|
|
}
|
|
|
|
static void bm_loop_uv_select_single_vert_validate(BMLoop *l_init, const int cd_loop_uv_offset)
|
|
{
|
|
const MLoopUV *luv_init = BM_ELEM_CD_GET_VOID_P(l_init, cd_loop_uv_offset);
|
|
BMIter liter;
|
|
BMLoop *l;
|
|
bool is_single_vert = true;
|
|
BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (equals_v2v2(luv_init->uv, luv->uv)) {
|
|
if (UL(l->prev)->is_select_edge || UL(l)->is_select_edge) {
|
|
is_single_vert = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (is_single_vert == false) {
|
|
BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
|
|
if (UL(l)->is_select_vert_single) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (equals_v2v2(luv_init->uv, luv->uv)) {
|
|
UL(l)->is_select_vert_single = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The corner return values calculate the angle between both loops,
|
|
* the edge values pick the closest to the either edge (ignoring the center).
|
|
*
|
|
* \param dir: Direction to calculate the angle to (normalized and aspect corrected).
|
|
*/
|
|
static void bm_loop_calc_uv_angle_from_dir(BMLoop *l,
|
|
const float dir[2],
|
|
const float aspect_y,
|
|
const int cd_loop_uv_offset,
|
|
float *r_corner_angle,
|
|
float *r_edge_angle,
|
|
int *r_edge_index)
|
|
{
|
|
/* Calculate 3 directions, return the shortest angle. */
|
|
float dir_test[3][2];
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset);
|
|
const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset);
|
|
|
|
sub_v2_v2v2(dir_test[0], luv->uv, luv_prev->uv);
|
|
sub_v2_v2v2(dir_test[2], luv->uv, luv_next->uv);
|
|
dir_test[0][1] /= aspect_y;
|
|
dir_test[2][1] /= aspect_y;
|
|
|
|
normalize_v2(dir_test[0]);
|
|
normalize_v2(dir_test[2]);
|
|
|
|
/* Calculate the orthogonal line (same as negating one, then adding). */
|
|
sub_v2_v2v2(dir_test[1], dir_test[0], dir_test[2]);
|
|
normalize_v2(dir_test[1]);
|
|
|
|
/* Rotate 90 degrees. */
|
|
SWAP(float, dir_test[1][0], dir_test[1][1]);
|
|
dir_test[1][1] *= -1.0f;
|
|
|
|
if (BM_face_uv_calc_cross(l->f, cd_loop_uv_offset) > 0.0f) {
|
|
negate_v2(dir_test[1]);
|
|
}
|
|
|
|
const float angles[3] = {
|
|
angle_v2v2(dir, dir_test[0]),
|
|
angle_v2v2(dir, dir_test[1]),
|
|
angle_v2v2(dir, dir_test[2]),
|
|
};
|
|
|
|
/* Set the corner values. */
|
|
*r_corner_angle = angles[1];
|
|
|
|
/* Set the edge values. */
|
|
if (angles[0] < angles[2]) {
|
|
*r_edge_angle = angles[0];
|
|
*r_edge_index = -1;
|
|
}
|
|
else {
|
|
*r_edge_angle = angles[2];
|
|
*r_edge_index = 1;
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Rip Single
|
|
* \{ */
|
|
|
|
typedef struct UVRipSingle {
|
|
/** Walk around the selected UV point, store #BMLoop. */
|
|
GSet *loops;
|
|
} UVRipSingle;
|
|
|
|
/**
|
|
* Handle single loop, the following cases:
|
|
*
|
|
* - An isolated fan, without a shared UV edge to other fans which share the same coordinate,
|
|
* in this case we just need to pick the closest fan to \a co.
|
|
*
|
|
* - In the case of contiguous loops (part of the same fan).
|
|
* Rip away the loops connected to the closest edge.
|
|
*
|
|
* - In the case of 2 contiguous loops.
|
|
* Rip the closest loop away.
|
|
*
|
|
* \note This matches the behavior of edit-mesh rip tool.
|
|
*/
|
|
static UVRipSingle *uv_rip_single_from_loop(BMLoop *l_init_orig,
|
|
const float co[2],
|
|
const float aspect_y,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
UVRipSingle *rip = MEM_callocN(sizeof(*rip), __func__);
|
|
const float *co_center =
|
|
(((const MLoopUV *)BM_ELEM_CD_GET_VOID_P(l_init_orig, cd_loop_uv_offset))->uv);
|
|
rip->loops = BLI_gset_ptr_new(__func__);
|
|
|
|
/* Track the closest loop, start walking from this so in the event we have multiple
|
|
* disconnected fans, we can rip away loops connected to this one. */
|
|
BMLoop *l_init = NULL;
|
|
BMLoop *l_init_edge = NULL;
|
|
float corner_angle_best = FLT_MAX;
|
|
float edge_angle_best = FLT_MAX;
|
|
int edge_index_best = 0; /* -1 or +1 (never center). */
|
|
|
|
/* Calculate the direction from the cursor with aspect correction. */
|
|
float dir_co[2];
|
|
sub_v2_v2v2(dir_co, co_center, co);
|
|
dir_co[1] /= aspect_y;
|
|
if (UNLIKELY(normalize_v2(dir_co) == 0.0)) {
|
|
dir_co[1] = 1.0f;
|
|
}
|
|
|
|
int uv_fan_count_all = 0;
|
|
{
|
|
BMIter liter;
|
|
BMLoop *l;
|
|
BM_ITER_ELEM (l, &liter, l_init_orig->v, BM_LOOPS_OF_VERT) {
|
|
if (BM_elem_flag_test(l->f, BM_ELEM_TAG)) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (equals_v2v2(co_center, luv->uv)) {
|
|
uv_fan_count_all += 1;
|
|
/* Clear at the same time. */
|
|
UL(l)->is_select_vert_single = true;
|
|
UL(l)->side = 0;
|
|
BLI_gset_add(rip->loops, l);
|
|
|
|
/* Update `l_init_close` */
|
|
float corner_angle_test;
|
|
float edge_angle_test;
|
|
int edge_index_test;
|
|
bm_loop_calc_uv_angle_from_dir(l,
|
|
dir_co,
|
|
aspect_y,
|
|
cd_loop_uv_offset,
|
|
&corner_angle_test,
|
|
&edge_angle_test,
|
|
&edge_index_test);
|
|
if ((corner_angle_best == FLT_MAX) || (corner_angle_test < corner_angle_best)) {
|
|
corner_angle_best = corner_angle_test;
|
|
l_init = l;
|
|
}
|
|
|
|
/* Trick so we don't consider concave corners further away than they should be. */
|
|
edge_angle_test = min_ff(corner_angle_test, edge_angle_test);
|
|
|
|
if ((edge_angle_best == FLT_MAX) || (edge_angle_test < edge_angle_best)) {
|
|
edge_angle_best = edge_angle_test;
|
|
edge_index_best = edge_index_test;
|
|
l_init_edge = l;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Walk around the `l_init` in both directions of the UV fan. */
|
|
int uv_fan_count_contiguous = 1;
|
|
UL(l_init)->side = 1;
|
|
for (int i = 0; i < 2; i += 1) {
|
|
BMEdge *e_prev = i ? l_init->e : l_init->prev->e;
|
|
BMLoop *l_iter = l_init;
|
|
while (((l_iter = bm_vert_step_fan_loop_uv(l_iter, &e_prev, cd_loop_uv_offset)) != l_init) &&
|
|
(l_iter != NULL) && (UL(l_iter)->side == 0)) {
|
|
uv_fan_count_contiguous += 1;
|
|
/* Keep. */
|
|
UL(l_iter)->side = 1;
|
|
}
|
|
/* May be useful to know if the fan is closed, currently it's not needed. */
|
|
#if 0
|
|
if (l_iter == l_init) {
|
|
is_closed = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (uv_fan_count_contiguous != uv_fan_count_all) {
|
|
/* Simply rip off the current fan, all tagging is done. */
|
|
}
|
|
else {
|
|
GSetIterator gs_iter;
|
|
GSET_ITER (gs_iter, rip->loops) {
|
|
BMLoop *l = BLI_gsetIterator_getKey(&gs_iter);
|
|
UL(l)->side = 0;
|
|
}
|
|
|
|
if (uv_fan_count_contiguous <= 2) {
|
|
/* Simple case, rip away the closest loop. */
|
|
UL(l_init)->side = 1;
|
|
}
|
|
else {
|
|
/* Rip away from the closest edge. */
|
|
BMLoop *l_radial_init = (edge_index_best == -1) ? l_init_edge->prev : l_init_edge;
|
|
BMLoop *l_radial_iter = l_radial_init;
|
|
do {
|
|
if (BM_loop_uv_share_edge_check(l_radial_init, l_radial_iter, cd_loop_uv_offset)) {
|
|
BMLoop *l = (l_radial_iter->v == l_init->v) ? l_radial_iter : l_radial_iter->next;
|
|
BLI_assert(l->v == l_init->v);
|
|
/* Keep. */
|
|
UL(l)->side = 1;
|
|
}
|
|
} while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_init);
|
|
}
|
|
}
|
|
|
|
return rip;
|
|
}
|
|
|
|
static void uv_rip_single_free(UVRipSingle *rip)
|
|
{
|
|
BLI_gset_free(rip->loops, NULL);
|
|
MEM_freeN(rip);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Rip Loop Pairs
|
|
* \{ */
|
|
|
|
typedef struct UVRipPairs {
|
|
/** Walk along the UV selection, store #BMLoop. */
|
|
GSet *loops;
|
|
} UVRipPairs;
|
|
|
|
static void uv_rip_pairs_add(UVRipPairs *rip, BMLoop *l)
|
|
{
|
|
ULData *ul = UL(l);
|
|
BLI_assert(!BLI_gset_haskey(rip->loops, l));
|
|
BLI_assert(ul->in_rip_pairs == false);
|
|
ul->in_rip_pairs = true;
|
|
BLI_gset_add(rip->loops, l);
|
|
}
|
|
|
|
static void uv_rip_pairs_remove(UVRipPairs *rip, BMLoop *l)
|
|
{
|
|
ULData *ul = UL(l);
|
|
BLI_assert(BLI_gset_haskey(rip->loops, l));
|
|
BLI_assert(ul->in_rip_pairs == true);
|
|
ul->in_rip_pairs = false;
|
|
BLI_gset_remove(rip->loops, l, NULL);
|
|
}
|
|
|
|
/**
|
|
* \note While this isn't especially efficient,
|
|
* this is only needed for rip-pairs end-points (only two per contiguous selection loop).
|
|
*/
|
|
static float uv_rip_pairs_calc_uv_angle(BMLoop *l_init,
|
|
uint side,
|
|
const float aspect_y,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
BMIter liter;
|
|
const MLoopUV *luv_init = BM_ELEM_CD_GET_VOID_P(l_init, cd_loop_uv_offset);
|
|
float angle_of_side = 0.0f;
|
|
BMLoop *l;
|
|
BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
|
|
if (UL(l)->in_rip_pairs) {
|
|
if (UL(l)->side == side) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (equals_v2v2(luv_init->uv, luv->uv)) {
|
|
const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset);
|
|
const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset);
|
|
float dir_prev[2], dir_next[2];
|
|
sub_v2_v2v2(dir_prev, luv_prev->uv, luv->uv);
|
|
sub_v2_v2v2(dir_next, luv_next->uv, luv->uv);
|
|
dir_prev[1] /= aspect_y;
|
|
dir_next[1] /= aspect_y;
|
|
const float luv_angle = angle_v2v2(dir_prev, dir_next);
|
|
if (LIKELY(isfinite(luv_angle))) {
|
|
angle_of_side += luv_angle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return angle_of_side;
|
|
}
|
|
|
|
static int uv_rip_pairs_loop_count_on_side(BMLoop *l_init, uint side, const int cd_loop_uv_offset)
|
|
{
|
|
const MLoopUV *luv_init = BM_ELEM_CD_GET_VOID_P(l_init, cd_loop_uv_offset);
|
|
int count = 0;
|
|
BMIter liter;
|
|
BMLoop *l;
|
|
BM_ITER_ELEM (l, &liter, l_init->v, BM_LOOPS_OF_VERT) {
|
|
if (UL(l)->in_rip_pairs) {
|
|
if (UL(l)->side == side) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (equals_v2v2(luv_init->uv, luv->uv)) {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static bool uv_rip_pairs_loop_change_sides_test(BMLoop *l_switch,
|
|
BMLoop *l_target,
|
|
const float aspect_y,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
const int side_a = UL(l_switch)->side;
|
|
const int side_b = UL(l_target)->side;
|
|
|
|
BLI_assert(UL(l_switch)->side != UL(l_target)->side);
|
|
|
|
/* First, check if this is a simple grid topology,
|
|
* in that case always choose the adjacent edge. */
|
|
const int count_a = uv_rip_pairs_loop_count_on_side(l_switch, side_a, cd_loop_uv_offset);
|
|
const int count_b = uv_rip_pairs_loop_count_on_side(l_target, side_b, cd_loop_uv_offset);
|
|
if (count_a + count_b == 4) {
|
|
return count_a > count_b;
|
|
}
|
|
|
|
const float angle_a_before = uv_rip_pairs_calc_uv_angle(
|
|
l_switch, side_a, aspect_y, cd_loop_uv_offset);
|
|
const float angle_b_before = uv_rip_pairs_calc_uv_angle(
|
|
l_target, side_b, aspect_y, cd_loop_uv_offset);
|
|
|
|
UL(l_switch)->side = side_b;
|
|
|
|
const float angle_a_after = uv_rip_pairs_calc_uv_angle(
|
|
l_switch, side_a, aspect_y, cd_loop_uv_offset);
|
|
const float angle_b_after = uv_rip_pairs_calc_uv_angle(
|
|
l_target, side_b, aspect_y, cd_loop_uv_offset);
|
|
|
|
UL(l_switch)->side = side_a;
|
|
|
|
return fabsf(angle_a_before - angle_b_before) > fabsf(angle_a_after - angle_b_after);
|
|
}
|
|
|
|
/**
|
|
* Create 2x sides of a UV rip-pairs, the result is unordered, supporting non-contiguous rails.
|
|
*
|
|
* \param l_init: A loop on a boundary which can be used to initialize flood-filling.
|
|
* This will always be added to the first side. Other loops will be added to the second side.
|
|
*
|
|
* \note We could have more than two sides, however in practice this almost never happens.
|
|
*/
|
|
static UVRipPairs *uv_rip_pairs_from_loop(BMLoop *l_init,
|
|
const float aspect_y,
|
|
const int cd_loop_uv_offset)
|
|
{
|
|
UVRipPairs *rip = MEM_callocN(sizeof(*rip), __func__);
|
|
rip->loops = BLI_gset_ptr_new(__func__);
|
|
|
|
/* We can rely on this stack being small, as we're walking down two sides of an edge loop,
|
|
* so the stack won't be much larger than the total number of fans at any one vertex. */
|
|
BLI_SMALLSTACK_DECLARE(stack, BMLoop *);
|
|
|
|
/* Needed for cases when we walk onto loops which already have a side assigned,
|
|
* in this case we need to pick a better side (see #uv_rip_pairs_loop_change_sides_test)
|
|
* and put the loop back in the stack,
|
|
* which is needed in the case adjacent loops should also switch sides. */
|
|
#define UV_SET_SIDE_AND_REMOVE_FROM_RAIL(loop, side_value) \
|
|
{ \
|
|
BLI_assert(UL(loop)->side_was_swapped == false); \
|
|
BLI_assert(UL(loop)->side != side_value); \
|
|
if (!UL(loop)->in_stack) { \
|
|
BLI_SMALLSTACK_PUSH(stack, loop); \
|
|
UL(loop)->in_stack = true; \
|
|
} \
|
|
if (UL(loop)->in_rip_pairs) { \
|
|
uv_rip_pairs_remove(rip, loop); \
|
|
} \
|
|
UL(loop)->side = side_value; \
|
|
UL(loop)->side_was_swapped = true; \
|
|
}
|
|
|
|
/* Initialize the stack. */
|
|
BLI_SMALLSTACK_PUSH(stack, l_init);
|
|
UL(l_init)->in_stack = true;
|
|
|
|
BMLoop *l_step;
|
|
while ((l_step = BLI_SMALLSTACK_POP(stack))) {
|
|
int side = UL(l_step)->side;
|
|
UL(l_step)->in_stack = false;
|
|
|
|
/* Note that we could add all loops into the rip-pairs when adding into the stack,
|
|
* however this complicates removal, so add into the rip-pairs when popping from the stack. */
|
|
uv_rip_pairs_add(rip, l_step);
|
|
|
|
/* Add to the other side if it exists. */
|
|
if (UL(l_step)->is_select_edge) {
|
|
BMLoop *l_other = bm_loop_find_other_radial_loop_with_visible_face(l_step,
|
|
cd_loop_uv_offset);
|
|
if (l_other != NULL) {
|
|
if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
|
|
BLI_SMALLSTACK_PUSH(stack, l_other);
|
|
UL(l_other)->in_stack = true;
|
|
UL(l_other)->side = !side;
|
|
}
|
|
else {
|
|
if (UL(l_other)->side == side) {
|
|
if (UL(l_other)->side_was_swapped == false) {
|
|
UV_SET_SIDE_AND_REMOVE_FROM_RAIL(l_other, !side);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add the next loop along the edge on the same side. */
|
|
l_other = l_step->next;
|
|
if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
|
|
BLI_SMALLSTACK_PUSH(stack, l_other);
|
|
UL(l_other)->in_stack = true;
|
|
UL(l_other)->side = side;
|
|
}
|
|
else {
|
|
if (UL(l_other)->side != side) {
|
|
if ((UL(l_other)->side_was_swapped == false) &&
|
|
uv_rip_pairs_loop_change_sides_test(l_other, l_step, aspect_y, cd_loop_uv_offset)) {
|
|
UV_SET_SIDE_AND_REMOVE_FROM_RAIL(l_other, side);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Walk over the fan of loops, starting from `l_step` in both directions. */
|
|
for (int i = 0; i < 2; i++) {
|
|
BMLoop *l_radial_first = i ? l_step : l_step->prev;
|
|
if (l_radial_first != l_radial_first->radial_next) {
|
|
BMEdge *e_radial = l_radial_first->e;
|
|
BMLoop *l_radial_iter = l_radial_first->radial_next;
|
|
do {
|
|
/* Not a boundary and visible. */
|
|
if (!UL(l_radial_iter)->is_select_edge &&
|
|
BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) {
|
|
BMLoop *l_other = (l_radial_iter->v == l_step->v) ? l_radial_iter :
|
|
l_radial_iter->next;
|
|
BLI_assert(l_other->v == l_step->v);
|
|
if (BM_edge_uv_share_vert_check(e_radial, l_other, l_step, cd_loop_uv_offset)) {
|
|
if (!UL(l_other)->in_rip_pairs && !UL(l_other)->in_stack) {
|
|
BLI_SMALLSTACK_PUSH(stack, l_other);
|
|
UL(l_other)->in_stack = true;
|
|
UL(l_other)->side = side;
|
|
}
|
|
else {
|
|
if (UL(l_other)->side != side) {
|
|
if ((UL(l_other)->side_was_swapped == false) &&
|
|
uv_rip_pairs_loop_change_sides_test(
|
|
l_other, l_step, aspect_y, cd_loop_uv_offset)) {
|
|
UV_SET_SIDE_AND_REMOVE_FROM_RAIL(l_other, side);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef UV_SET_SIDE_AND_REMOVE_FROM_RAIL
|
|
|
|
return rip;
|
|
}
|
|
|
|
static void uv_rip_pairs_free(UVRipPairs *rip)
|
|
{
|
|
BLI_gset_free(rip->loops, NULL);
|
|
MEM_freeN(rip);
|
|
}
|
|
|
|
/**
|
|
* This is an approximation, it's easily good enough for our purpose.
|
|
*/
|
|
static bool uv_rip_pairs_calc_center_and_direction(UVRipPairs *rip,
|
|
const int cd_loop_uv_offset,
|
|
float r_center[2],
|
|
float r_dir_side[2][2])
|
|
{
|
|
zero_v2(r_center);
|
|
int center_total = 0;
|
|
int side_total[2] = {0, 0};
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
zero_v2(r_dir_side[i]);
|
|
}
|
|
GSetIterator gs_iter;
|
|
GSET_ITER (gs_iter, rip->loops) {
|
|
BMLoop *l = BLI_gsetIterator_getKey(&gs_iter);
|
|
int side = UL(l)->side;
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
add_v2_v2(r_center, luv->uv);
|
|
|
|
float dir[2];
|
|
if (!UL(l)->is_select_edge) {
|
|
const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset);
|
|
sub_v2_v2v2(dir, luv_next->uv, luv->uv);
|
|
add_v2_v2(r_dir_side[side], dir);
|
|
}
|
|
if (!UL(l->prev)->is_select_edge) {
|
|
const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset);
|
|
sub_v2_v2v2(dir, luv_prev->uv, luv->uv);
|
|
add_v2_v2(r_dir_side[side], dir);
|
|
}
|
|
side_total[side] += 1;
|
|
}
|
|
center_total += BLI_gset_len(rip->loops);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
normalize_v2(r_dir_side[i]);
|
|
}
|
|
mul_v2_fl(r_center, 1.0f / center_total);
|
|
|
|
/* If only a single side is selected, don't handle this rip-pairs. */
|
|
return side_total[0] && side_total[1];
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Rip Main Function
|
|
* \{ */
|
|
|
|
/**
|
|
* \return true when a change was made.
|
|
*/
|
|
static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const float aspect_y)
|
|
{
|
|
Mesh *me = (Mesh *)obedit->data;
|
|
BMEditMesh *em = me->edit_mesh;
|
|
BMesh *bm = em->bm;
|
|
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
|
|
|
|
BMFace *efa;
|
|
BMIter iter, liter;
|
|
BMLoop *l;
|
|
|
|
const ULData ul_clear = {0};
|
|
|
|
bool changed = false;
|
|
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
BM_elem_flag_set(efa, BM_ELEM_TAG, uvedit_face_visible_test(scene, efa));
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
ULData *ul = UL(l);
|
|
*ul = ul_clear;
|
|
}
|
|
}
|
|
bm->elem_index_dirty |= BM_LOOP;
|
|
|
|
bool is_select_all_any = false;
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
|
|
bool is_all = true;
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (luv->flag & MLOOPUV_VERTSEL) {
|
|
const MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset);
|
|
if (luv->flag & MLOOPUV_EDGESEL) {
|
|
UL(l)->is_select_edge = true;
|
|
}
|
|
else if ((luv_prev->flag & MLOOPUV_EDGESEL) == 0) {
|
|
/* #bm_loop_uv_select_single_vert_validate validates below. */
|
|
UL(l)->is_select_vert_single = true;
|
|
is_all = false;
|
|
}
|
|
else {
|
|
/* Cases where all vertices of a face are selected but not all edges are selected. */
|
|
is_all = false;
|
|
}
|
|
}
|
|
else {
|
|
is_all = false;
|
|
}
|
|
}
|
|
if (is_all) {
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
UL(l)->is_select_all = true;
|
|
}
|
|
is_select_all_any = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove #ULData.is_select_vert_single when connected to selected edges. */
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
if (UL(l)->is_select_vert_single) {
|
|
bm_loop_uv_select_single_vert_validate(l, cd_loop_uv_offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Special case: if we have selected faces, isolate them.
|
|
* This isn't a rip, however it's useful for users as a quick way
|
|
* to detach the selection.
|
|
*
|
|
* We could also extract an edge loop from the boundary
|
|
* however in practice it's not that useful, see T78751. */
|
|
if (is_select_all_any) {
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
if (!UL(l)->is_select_all) {
|
|
MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
if (luv->flag & MLOOPUV_VERTSEL) {
|
|
luv->flag &= ~MLOOPUV_VERTSEL;
|
|
changed = true;
|
|
}
|
|
if (luv->flag & MLOOPUV_EDGESEL) {
|
|
luv->flag &= ~MLOOPUV_EDGESEL;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* Extract loop pairs or single loops. */
|
|
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
|
|
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
|
|
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
|
|
if (UL(l)->is_select_edge) {
|
|
if (!UL(l)->in_rip_pairs) {
|
|
UVRipPairs *rip = uv_rip_pairs_from_loop(l, aspect_y, cd_loop_uv_offset);
|
|
float center[2];
|
|
float dir_cursor[2];
|
|
float dir_side[2][2];
|
|
int side_from_cursor = -1;
|
|
if (uv_rip_pairs_calc_center_and_direction(rip, cd_loop_uv_offset, center, dir_side)) {
|
|
for (int i = 0; i < 2; i++) {
|
|
sub_v2_v2v2(dir_cursor, center, co);
|
|
normalize_v2(dir_cursor);
|
|
}
|
|
side_from_cursor = (dot_v2v2(dir_side[0], dir_cursor) -
|
|
dot_v2v2(dir_side[1], dir_cursor)) < 0.0f;
|
|
}
|
|
GSetIterator gs_iter;
|
|
GSET_ITER (gs_iter, rip->loops) {
|
|
BMLoop *l_iter = BLI_gsetIterator_getKey(&gs_iter);
|
|
ULData *ul = UL(l_iter);
|
|
if (ul->side == side_from_cursor) {
|
|
uvedit_uv_select_disable(scene, em->bm, l_iter, cd_loop_uv_offset);
|
|
changed = true;
|
|
}
|
|
/* Ensure we don't operate on these again. */
|
|
*ul = ul_clear;
|
|
}
|
|
uv_rip_pairs_free(rip);
|
|
}
|
|
}
|
|
else if (UL(l)->is_select_vert_single) {
|
|
UVRipSingle *rip = uv_rip_single_from_loop(l, co, aspect_y, cd_loop_uv_offset);
|
|
/* We only ever use one side. */
|
|
const int side_from_cursor = 0;
|
|
GSetIterator gs_iter;
|
|
GSET_ITER (gs_iter, rip->loops) {
|
|
BMLoop *l_iter = BLI_gsetIterator_getKey(&gs_iter);
|
|
ULData *ul = UL(l_iter);
|
|
if (ul->side == side_from_cursor) {
|
|
uvedit_uv_select_disable(scene, em->bm, l_iter, cd_loop_uv_offset);
|
|
changed = true;
|
|
}
|
|
/* Ensure we don't operate on these again. */
|
|
*ul = ul_clear;
|
|
}
|
|
uv_rip_single_free(rip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (changed) {
|
|
uvedit_deselect_flush(scene, em);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UV Rip Operator
|
|
* \{ */
|
|
|
|
static int uv_rip_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceImage *sima = CTX_wm_space_image(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
|
|
bool changed_multi = false;
|
|
|
|
float co[2];
|
|
RNA_float_get_array(op->ptr, "location", co);
|
|
|
|
float aspx, aspy;
|
|
{
|
|
/* Note that we only want to run this on the. */
|
|
Object *obedit = CTX_data_edit_object(C);
|
|
ED_uvedit_get_aspect(obedit, &aspx, &aspy);
|
|
}
|
|
const float aspect_y = aspx / aspy;
|
|
|
|
uint objects_len = 0;
|
|
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
|
|
view_layer, ((View3D *)NULL), &objects_len);
|
|
|
|
for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
|
|
Object *obedit = objects[ob_index];
|
|
|
|
if (uv_rip_object(scene, obedit, co, aspect_y)) {
|
|
changed_multi = true;
|
|
uvedit_live_unwrap_update(sima, scene, obedit);
|
|
DEG_id_tag_update(obedit->data, 0);
|
|
WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit->data);
|
|
}
|
|
}
|
|
MEM_freeN(objects);
|
|
|
|
if (!changed_multi) {
|
|
BKE_report(op->reports, RPT_ERROR, "Rip failed");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int uv_rip_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
ARegion *region = CTX_wm_region(C);
|
|
float co[2];
|
|
|
|
UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
|
|
RNA_float_set_array(op->ptr, "location", co);
|
|
|
|
return uv_rip_exec(C, op);
|
|
}
|
|
|
|
void UV_OT_rip(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "UV Rip";
|
|
ot->description = "Rip selected vertices or a selected region";
|
|
ot->idname = "UV_OT_rip";
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
|
|
|
|
/* api callbacks */
|
|
ot->exec = uv_rip_exec;
|
|
ot->invoke = uv_rip_invoke;
|
|
ot->poll = ED_operator_uvedit;
|
|
|
|
/* translation data */
|
|
Transform_Properties(ot, P_MIRROR_DUMMY);
|
|
|
|
/* properties */
|
|
RNA_def_float_vector(
|
|
ot->srna,
|
|
"location",
|
|
2,
|
|
NULL,
|
|
-FLT_MAX,
|
|
FLT_MAX,
|
|
"Location",
|
|
"Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
|
|
-100.0f,
|
|
100.0f);
|
|
}
|
|
|
|
/** \} */
|