Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
265 lines
6.8 KiB
C++
265 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.h"
|
|
|
|
#include "bmesh.h"
|
|
|
|
#include "intern/bmesh_operators_private.h" /* 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);
|
|
}
|