Files
test/source/blender/bmesh/tools/bmesh_decimate_unsubdivide.cc
Campbell Barton e955c94ed3 License Headers: Set copyright to "Blender Authors", add AUTHORS
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.
2023-08-16 00:20:26 +10:00

339 lines
8.9 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh decimator that uses a grid un-subdivide method.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.h"
#include "bmesh_decimate.h" /* own include */
static bool bm_vert_dissolve_fan_test(BMVert *v)
{
/* check if we should walk over these verts */
BMIter iter;
BMEdge *e;
BMVert *varr[4];
uint tot_edge = 0;
uint tot_edge_boundary = 0;
uint tot_edge_manifold = 0;
uint tot_edge_wire = 0;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
tot_edge_boundary++;
}
else if (BM_edge_is_manifold(e)) {
tot_edge_manifold++;
}
else if (BM_edge_is_wire(e)) {
tot_edge_wire++;
}
/* bail out early */
if (tot_edge == 4) {
return false;
}
/* used to check overlapping faces */
varr[tot_edge] = BM_edge_other_vert(e, v);
tot_edge++;
}
if (((tot_edge == 4) && (tot_edge_boundary == 0) && (tot_edge_manifold == 4)) ||
((tot_edge == 3) && (tot_edge_boundary == 0) && (tot_edge_manifold == 3)) ||
((tot_edge == 3) && (tot_edge_boundary == 2) && (tot_edge_manifold == 1)))
{
if (!BM_face_exists(varr, tot_edge)) {
return true;
}
}
else if ((tot_edge == 2) && (tot_edge_wire == 2)) {
return true;
}
return false;
}
static bool bm_vert_dissolve_fan(BMesh *bm, BMVert *v)
{
/* Collapse under 2 conditions:
* - vert connects to 4 manifold edges (and 4 faces).
* - vert connects to 1 manifold edge, 2 boundary edges (and 2 faces).
*
* This covers boundary verts of a quad grid and center verts.
* note that surrounding faces don't have to be quads.
*/
BMIter iter;
BMEdge *e;
uint tot_loop = 0;
uint tot_edge = 0;
uint tot_edge_boundary = 0;
uint tot_edge_manifold = 0;
uint tot_edge_wire = 0;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
tot_edge_boundary++;
}
else if (BM_edge_is_manifold(e)) {
tot_edge_manifold++;
}
else if (BM_edge_is_wire(e)) {
tot_edge_wire++;
}
tot_edge++;
}
if (tot_edge == 2) {
/* check for 2 wire verts only */
if (tot_edge_wire == 2) {
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
}
else if (tot_edge == 4) {
/* check for 4 faces surrounding */
if (tot_edge_boundary == 0 && tot_edge_manifold == 4) {
/* good to go! */
tot_loop = 4;
}
}
else if (tot_edge == 3) {
/* check for 2 faces surrounding at a boundary */
if (tot_edge_boundary == 2 && tot_edge_manifold == 1) {
/* good to go! */
tot_loop = 2;
}
else if (tot_edge_boundary == 0 && tot_edge_manifold == 3) {
/* good to go! */
tot_loop = 3;
}
}
if (tot_loop) {
BMLoop *f_loop[4];
uint i;
/* ensure there are exactly tot_loop loops */
BLI_assert(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v, tot_loop) == nullptr);
BM_iter_as_array(bm, BM_LOOPS_OF_VERT, v, (void **)f_loop, tot_loop);
for (i = 0; i < tot_loop; i++) {
BMLoop *l = f_loop[i];
if (l->f->len > 3) {
BMLoop *l_new;
BLI_assert(l->prev->v != l->next->v);
BM_face_split(bm, l->f, l->prev, l->next, &l_new, nullptr, true);
BM_elem_flag_merge_into(l_new->e, l->e, l->prev->e);
}
}
return BM_vert_dissolve(bm, v);
}
return false;
}
enum {
VERT_INDEX_DO_COLLAPSE = -1,
VERT_INDEX_INIT = 0,
VERT_INDEX_IGNORE = 1,
};
// #define USE_WALKER /* gives uneven results, disable for now */
/* - BMVert.flag & BM_ELEM_TAG: shows we touched this vert
* - BMVert.index == -1: shows we will remove this vert
*/
void BM_mesh_decimate_unsubdivide_ex(BMesh *bm, const int iterations, const bool tag_only)
{
#ifdef USE_WALKER
# define ELE_VERT_TAG 1
#else
BMVert **vert_seek_a = static_cast<BMVert **>(
MEM_mallocN(sizeof(BMVert *) * bm->totvert, __func__));
BMVert **vert_seek_b = static_cast<BMVert **>(
MEM_mallocN(sizeof(BMVert *) * bm->totvert, __func__));
uint vert_seek_a_tot = 0;
uint vert_seek_b_tot = 0;
#endif
BMIter iter;
const uint offset = 0;
const uint nth = 2;
int iter_step;
/* if tag_only is set, we assume the caller knows what verts to tag
* needed for the operator */
if (tag_only == false) {
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
}
for (iter_step = 0; iter_step < iterations; iter_step++) {
BMVert *v, *v_next;
bool iter_done;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_TAG) && bm_vert_dissolve_fan_test(v)) {
#ifdef USE_WALKER
BMO_vert_flag_enable(bm, v, ELE_VERT_TAG);
#endif
BM_elem_index_set(v, VERT_INDEX_INIT); /* set_dirty! */
}
else {
BM_elem_index_set(v, VERT_INDEX_IGNORE); /* set_dirty! */
}
}
/* done with selecting tagged verts */
/* main loop, keep tagging until we can't tag any more islands */
while (true) {
#ifdef USE_WALKER
BMWalker walker;
#else
uint depth = 1;
uint i;
#endif
BMVert *v_first = nullptr;
/* we could avoid iterating from the start each time */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (v->e && (BM_elem_index_get(v) == VERT_INDEX_INIT)) {
#ifdef USE_WALKER
if (BMO_vert_flag_test(bm, v, ELE_VERT_TAG))
#endif
{
/* Check again in case the topology changed. */
if (bm_vert_dissolve_fan_test(v)) {
v_first = v;
}
break;
}
}
}
if (v_first == nullptr) {
break;
}
#ifdef USE_WALKER
/* Walk over selected elements starting at active */
BMW_init(
&walker,
bm,
BMW_CONNECTED_VERTEX,
ELE_VERT_TAG,
BMW_MASK_NOP,
BMW_MASK_NOP,
BMW_FLAG_NOP, /* don't use #BMW_FLAG_TEST_HIDDEN here since we want to deselect all. */
BMW_NIL_LAY);
BLI_assert(walker.order == BMW_BREADTH_FIRST);
for (v = BMW_begin(&walker, v_first); v != nullptr; v = BMW_step(&walker)) {
/* Deselect elements that aren't at "nth" depth from active */
if (BM_elem_index_get(v) == VERT_INDEX_INIT) {
if ((offset + BMW_current_depth(&walker)) % nth) {
/* tag for removal */
BM_elem_index_set(v, VERT_INDEX_DO_COLLAPSE); /* set_dirty! */
}
else {
/* works better to allow these verts to be checked again */
// BM_elem_index_set(v, VERT_INDEX_IGNORE); /* set_dirty! */
}
}
}
BMW_end(&walker);
#else
BM_elem_index_set(v_first,
((offset + depth) % nth) ? VERT_INDEX_IGNORE :
VERT_INDEX_DO_COLLAPSE); /* set_dirty! */
vert_seek_b_tot = 0;
vert_seek_b[vert_seek_b_tot++] = v_first;
while (true) {
BMEdge *e;
if ((offset + depth) % nth) {
vert_seek_a_tot = 0;
for (i = 0; i < vert_seek_b_tot; i++) {
v = vert_seek_b[i];
BLI_assert(BM_elem_index_get(v) == VERT_INDEX_IGNORE);
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
BMVert *v_other = BM_edge_other_vert(e, v);
if (BM_elem_index_get(v_other) == VERT_INDEX_INIT) {
BM_elem_index_set(v_other, VERT_INDEX_DO_COLLAPSE); /* set_dirty! */
vert_seek_a[vert_seek_a_tot++] = v_other;
}
}
}
if (vert_seek_a_tot == 0) {
break;
}
}
else {
vert_seek_b_tot = 0;
for (i = 0; i < vert_seek_a_tot; i++) {
v = vert_seek_a[i];
BLI_assert(BM_elem_index_get(v) == VERT_INDEX_DO_COLLAPSE);
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
BMVert *v_other = BM_edge_other_vert(e, v);
if (BM_elem_index_get(v_other) == VERT_INDEX_INIT) {
BM_elem_index_set(v_other, VERT_INDEX_IGNORE); /* set_dirty! */
vert_seek_b[vert_seek_b_tot++] = v_other;
}
}
}
if (vert_seek_b_tot == 0) {
break;
}
}
depth++;
}
#endif /* USE_WALKER */
}
/* now we tagged all verts -1 for removal, lets loop over and rebuild faces */
iter_done = false;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_index_get(v) == VERT_INDEX_DO_COLLAPSE) {
if (bm_vert_dissolve_fan(bm, v)) {
iter_done = true;
}
}
}
if (iter_done == false) {
break;
}
}
bm->elem_index_dirty |= BM_VERT;
#ifndef USE_WALKER
MEM_freeN(vert_seek_a);
MEM_freeN(vert_seek_b);
#endif
}
void BM_mesh_decimate_unsubdivide(BMesh *bm, const int iterations)
{
BM_mesh_decimate_unsubdivide_ex(bm, iterations, false);
}