This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/uvedit/uvedit_rip.c
Chris Blackbourn aa82f91c92 Cleanup: uvedit_*_select, replace BMEditMesh* with BMesh*
Change `cd_loop_uv_offset` from signed to unsigned, forcing
a crash if passed invalid input.

Differential Revision: https://developer.blender.org/D15722
2022-08-19 13:00:48 +12:00

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(&region->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);
}
/** \} */