tornavis/source/blender/bmesh/operators/bmo_offset_edgeloops.cc

266 lines
6.8 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Simple edge offset functionality.
*
* \note Actual offset is done by edge-slide.
* (this only changes topology)
*/
#include "MEM_guardedalloc.h"
#include "BLI_alloca.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#define USE_CAP_OPTION
#define ELE_NEW (1 << 0)
#ifdef USE_CAP_OPTION
# define ELE_VERT_ENDPOINT (1 << 1)
#endif
/* set for debugging */
#define OFFSET 0.0f
static BMFace *bm_face_split_walk_back(BMesh *bm, BMLoop *l_src, BMLoop **r_l)
{
float(*cos)[3];
BMLoop *l_dst;
BMFace *f;
int num, i;
for (l_dst = l_src->prev, num = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, num++)
{
/* pass */
}
BLI_assert(num != 0);
cos = BLI_array_alloca(cos, num);
for (l_dst = l_src->prev, i = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, i++)
{
copy_v3_v3(cos[num - (i + 1)], l_dst->v->co);
}
f = BM_face_split_n(bm, l_src->f, l_dst->prev, l_src->next, cos, num, r_l, nullptr);
return f;
}
void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op)
{
const int edges_num = BMO_slot_buffer_len(op->slots_in, "edges");
BMVert **verts;
STACK_DECLARE(verts);
int i;
#ifdef USE_CAP_OPTION
bool use_cap_endpoint = BMO_slot_bool_get(op->slots_in, "use_cap_endpoint");
int v_edges_max = 0;
#endif
BMOIter oiter;
/* only so we can detect new verts (index == -1) */
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
/* over alloc */
verts = static_cast<BMVert **>(MEM_mallocN(sizeof(*verts) * (edges_num * 2), __func__));
STACK_INIT(verts, (edges_num * 2));
{
BMEdge *e;
BMO_ITER (e, &oiter, op->slots_in, "edges", BM_EDGE) {
int j;
BM_elem_flag_enable(e, BM_ELEM_TAG);
for (j = 0; j < 2; j++) {
BMVert *v_edge = *(&(e->v1) + j);
if (!BM_elem_flag_test(v_edge, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_edge, BM_ELEM_TAG);
STACK_PUSH(verts, v_edge);
}
}
}
}
/* -------------------------------------------------------------------- */
/* Remove verts only used by tagged edges */
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
int flag = 0;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
flag |= BM_elem_flag_test(e, BM_ELEM_TAG) ? 1 : 2;
if (flag == (1 | 2)) {
break;
}
}
/* only boundary verts are interesting */
if (flag != (1 | 2)) {
STACK_REMOVE(verts, i);
}
}
/* possible but unlikely we have no mixed vertices */
if (UNLIKELY(STACK_SIZE(verts) == 0)) {
MEM_freeN(verts);
return;
}
/* main loop */
for (i = 0; i < STACK_SIZE(verts); i++) {
int v_edges_num = 0;
int v_edges_num_untag = 0;
BMVert *v = verts[i];
BMIter iter;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
BMVert *v_other;
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) {
BM_elem_flag_enable(l->f, BM_ELEM_TAG);
}
v_other = BM_edge_other_vert(e, v);
BM_edge_split(bm, e, v_other, nullptr, 1.0f - OFFSET);
}
else {
v_edges_num_untag += 1;
}
v_edges_num += 1;
}
#ifdef USE_CAP_OPTION
if (v_edges_num_untag == 1) {
BMO_vert_flag_enable(bm, v, ELE_VERT_ENDPOINT);
}
CLAMP_MIN(v_edges_max, v_edges_num);
#endif
}
for (i = 0; i < STACK_SIZE(verts); i++) {
BMVert *v = verts[i];
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (BM_elem_flag_test(l->f, BM_ELEM_TAG) && (l->f->len != 3)) {
BMFace *f_cmp = l->f;
if ((BM_elem_index_get(l->next->v) == -1) && (BM_elem_index_get(l->prev->v) == -1)) {
#ifdef USE_CAP_OPTION
if (use_cap_endpoint || (BMO_vert_flag_test(bm, v, ELE_VERT_ENDPOINT) == 0))
#endif
{
BMLoop *l_new;
BM_face_split(bm, l->f, l->prev, l->next, &l_new, nullptr, true);
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
UNUSED_VARS_NDEBUG(f_cmp);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
}
}
else if (l->f->len > 4) {
if (BM_elem_flag_test(l->e, BM_ELEM_TAG) != BM_elem_flag_test(l->prev->e, BM_ELEM_TAG)) {
if (BM_elem_index_get(l->next->v) == -1) {
if (BM_elem_index_get(l->prev->prev->v) == -1) {
BMLoop *l_new;
BM_face_split(bm, l->f, l->prev->prev, l->next, &l_new, nullptr, true);
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
else {
/* walk backwards */
BMLoop *l_new;
bm_face_split_walk_back(bm, l, &l_new);
do {
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
l_new = l_new->next;
} while (BM_vert_is_edge_pair(l_new->v));
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
}
/* NOTE: instead of duplicate code in alternate direction,
* we can be sure to hit the other vertex, so the code above runs. */
#if 0
else if (BM_elem_index_get(l->prev->v) == -1) {
if (BM_elem_index_get(l->next->next->v) == -1) {
/* pass */
}
}
#endif
}
}
}
}
}
#ifdef USE_CAP_OPTION
if (use_cap_endpoint == false) {
BMVert **varr = BLI_array_alloca(varr, v_edges_max);
STACK_DECLARE(varr);
BMVert *v;
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
BMEdge *e;
v = verts[i];
STACK_INIT(varr, v_edges_max);
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
BMVert *v_other;
v_other = BM_edge_other_vert(e, v);
if (BM_elem_index_get(v_other) == -1) {
if (BM_vert_is_edge_pair(v_other)) {
/* defer bmesh_kernel_join_edge_kill_vert to avoid looping over data we're removing */
v_other->e = e;
STACK_PUSH(varr, v_other);
}
}
}
while ((v = STACK_POP(varr))) {
bmesh_kernel_join_edge_kill_vert(bm, v->e, v, true, false, false, true);
}
}
}
#endif
MEM_freeN(verts);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW);
}