Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,399 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Beautify the mesh by rotating edges between triangles
* to more attractive positions until no more rotations can be made.
*
* In principle this is very simple however there is the possibility of
* going into an eternal loop where edges keep rotating.
* To avoid this - each edge stores a set of it previous
* states so as not to rotate back.
*
* TODO
* - Take face normals into account.
*/
#include "BLI_heap.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_set.hh"
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "bmesh_beautify.hh" /* own include */
// #define DEBUG_TIME
#ifdef DEBUG_TIME
# include "BLI_time.h"
# include "BLI_time_utildefines.h"
#endif
namespace blender {
/* -------------------------------------------------------------------- */
/* Set for edge rotation */
struct EdRotState {
/**
* Edge vert indices (ordered small -> large).
*/
int2 v_pair;
/**
* Face vert indices (small -> large).
*
* Each face-vertex points to a connected triangles vertex
* that's isn't part of the edge defined by `v_pair`.
*/
int2 f_pair;
friend bool operator==(const EdRotState &a, const EdRotState &b) = default;
uint64_t hash() const
{
return get_default_hash(this->v_pair, this->f_pair);
}
};
#if 0
/* use BLI_ghashutil_inthash_v4 direct */
static uint erot_gsetutil_hash(const void *ptr)
{
const EdRotState *e_state = (const EdRotState *)ptr;
return BLI_ghashutil_inthash_v4(&e_state->v_pair[0]);
}
#endif
#if 0
static int erot_gsetutil_cmp(const void *a, const void *b)
{
const EdRotState *e_state_a = (const EdRotState *)a;
const EdRotState *e_state_b = (const EdRotState *)b;
if (e_state_a->v_pair[0] < e_state_b->v_pair[0]) {
return -1;
}
if (e_state_a->v_pair[0] > e_state_b->v_pair[0]) {
return 1;
}
if (e_state_a->v_pair[1] < e_state_b->v_pair[1]) {
return -1;
}
if (e_state_a->v_pair[1] > e_state_b->v_pair[1]) {
return 1;
}
if (e_state_a->f_pair[0] < e_state_b->f_pair[0]) {
return -1;
}
if (e_state_a->f_pair[0] > e_state_b->f_pair[0]) {
return 1;
}
if (e_state_a->f_pair[1] < e_state_b->f_pair[1]) {
return -1;
}
if (e_state_a->f_pair[1] > e_state_b->f_pair[1]) {
return 1;
}
return 0;
}
#endif
/* ensure v0 is smaller */
#define EDGE_ORD(v0, v1) \
if (v0 > v1) { \
std::swap(v0, v1); \
} \
(void)0
static void erot_state_ex(const BMEdge *e, int v_index[2], int f_index[2])
{
BLI_assert(BM_edge_is_manifold(e));
BLI_assert(BM_vert_in_edge(e, e->l->prev->v) == false);
BLI_assert(BM_vert_in_edge(e, e->l->radial_next->prev->v) == false);
/* verts of the edge */
v_index[0] = BM_elem_index_get(e->v1);
v_index[1] = BM_elem_index_get(e->v2);
EDGE_ORD(v_index[0], v_index[1]);
/* verts of each of the 2 faces attached to this edge
* (that are not a part of this edge) */
f_index[0] = BM_elem_index_get(e->l->prev->v);
f_index[1] = BM_elem_index_get(e->l->radial_next->prev->v);
EDGE_ORD(f_index[0], f_index[1]);
}
static void erot_state_current(const BMEdge *e, EdRotState *e_state)
{
erot_state_ex(e, e_state->v_pair, e_state->f_pair);
}
static void erot_state_alternate(const BMEdge *e, EdRotState *e_state)
{
erot_state_ex(e, e_state->f_pair, e_state->v_pair);
}
/* -------------------------------------------------------------------- */
/* Calculate the improvement of rotating the edge */
static float bm_edge_calc_rotate_beauty__angle(const float v1[3],
const float v2[3],
const float v3[3],
const float v4[3])
{
/* not a loop (only to be able to break out) */
do {
float no_a[3], no_b[3];
float angle_24, angle_13;
/* edge (2-4), current state */
normal_tri_v3(no_a, v2, v3, v4);
normal_tri_v3(no_b, v2, v4, v1);
angle_24 = angle_normalized_v3v3(no_a, no_b);
/* edge (1-3), new state */
/* only check new state for degenerate outcome */
if ((normal_tri_v3(no_a, v1, v2, v3) == 0.0f) || (normal_tri_v3(no_b, v1, v3, v4) == 0.0f)) {
break;
}
angle_13 = angle_normalized_v3v3(no_a, no_b);
return angle_13 - angle_24;
} while (false);
return FLT_MAX;
}
float BM_verts_calc_rotate_beauty(const BMVert *v1,
const BMVert *v2,
const BMVert *v3,
const BMVert *v4,
const short flag,
const short method)
{
/* not a loop (only to be able to break out) */
do {
if (flag & VERT_RESTRICT_TAG) {
const BMVert *v_a = v1, *v_b = v3;
if (BM_elem_flag_test(v_a, BM_ELEM_TAG) == BM_elem_flag_test(v_b, BM_ELEM_TAG)) {
break;
}
}
if (UNLIKELY(v1 == v3)) {
// printf("This should never happen, but does sometimes!\n");
break;
}
switch (method) {
case 0:
return BLI_polyfill_edge_calc_rotate_beauty__area(
v1->co, v2->co, v3->co, v4->co, flag & EDGE_RESTRICT_DEGENERATE);
default:
return bm_edge_calc_rotate_beauty__angle(v1->co, v2->co, v3->co, v4->co);
}
} while (false);
return FLT_MAX;
}
static float bm_edge_calc_rotate_beauty(const BMEdge *e, const short flag, const short method)
{
const BMVert *v1, *v2, *v3, *v4;
v1 = e->l->prev->v; /* First vert co */
v2 = e->l->v; /* `e->v1` or `e->v2`. */
v3 = e->l->radial_next->prev->v; /* Second vert co */
v4 = e->l->next->v; /* `e->v1` or `e->v2`. */
return BM_verts_calc_rotate_beauty(v1, v2, v3, v4, flag, method);
}
/* -------------------------------------------------------------------- */
/* Update the edge cost of rotation in the heap */
BLI_INLINE bool edge_in_array(const BMEdge *e, const BMEdge **edge_array, const int edge_array_len)
{
const int index = BM_elem_index_get(e);
return ((index >= 0) && (index < edge_array_len) && (e == edge_array[index]));
}
/* recalc an edge in the heap (surrounding geometry has changed) */
static void bm_edge_update_beauty_cost_single(BMEdge *e,
Heap *eheap,
HeapNode **eheap_table,
const Span<Set<EdRotState>> edge_state_arr,
/* only for testing the edge is in the array */
const BMEdge **edge_array,
const int edge_array_len,
const short flag,
const short method)
{
if (edge_in_array(e, edge_array, edge_array_len)) {
const int i = BM_elem_index_get(e);
const Set<EdRotState> &e_state_set = edge_state_arr[i];
if (eheap_table[i]) {
BLI_heap_remove(eheap, eheap_table[i]);
eheap_table[i] = nullptr;
}
/* check if we can add it back */
BLI_assert(BM_edge_is_manifold(e) == true);
/* check we're not moving back into a state we have been in before */
EdRotState e_state_alt;
erot_state_alternate(e, &e_state_alt);
if (e_state_set.contains(e_state_alt)) {
// printf(" skipping, we already have this state\n");
return;
}
{
/* recalculate edge */
const float cost = bm_edge_calc_rotate_beauty(e, flag, method);
if (cost < 0.0f) {
eheap_table[i] = BLI_heap_insert(eheap, cost, e);
}
else {
eheap_table[i] = nullptr;
}
}
}
}
/* we have rotated an edge, tag other edges and clear this one */
static void bm_edge_update_beauty_cost(BMEdge *e,
Heap *eheap,
HeapNode **eheap_table,
const Span<Set<EdRotState>> edge_state_arr,
const BMEdge **edge_array,
const int edge_array_len,
/* only for testing the edge is in the array */
const short flag,
const short method)
{
int i;
BMEdge *e_arr[4] = {
e->l->next->e,
e->l->prev->e,
e->l->radial_next->next->e,
e->l->radial_next->prev->e,
};
BLI_assert(e->l->f->len == 3 && e->l->radial_next->f->len == 3);
BLI_assert(BM_edge_face_count_is_equal(e, 2));
for (i = 0; i < 4; i++) {
bm_edge_update_beauty_cost_single(
e_arr[i], eheap, eheap_table, edge_state_arr, edge_array, edge_array_len, flag, method);
}
}
/* -------------------------------------------------------------------- */
/* Beautify Fill */
void BM_mesh_beautify_fill(BMesh *bm,
BMEdge **edge_array,
const int edge_array_len,
const short flag,
const short method,
const short oflag_edge,
const short oflag_face)
{
Heap *eheap; /* edge heap */
HeapNode **eheap_table; /* edge index aligned table pointing to the eheap */
Array<Set<EdRotState>> edge_state_arr(edge_array_len);
BLI_mempool *edge_state_pool = BLI_mempool_create(sizeof(EdRotState), 0, 512, BLI_MEMPOOL_NOP);
int i;
#ifdef DEBUG_TIME
TIMEIT_START(beautify_fill);
#endif
eheap = BLI_heap_new_ex(uint(edge_array_len));
eheap_table = MEM_new_array_uninitialized<HeapNode *>(size_t(edge_array_len), __func__);
/* build heap */
for (i = 0; i < edge_array_len; i++) {
BMEdge *e = edge_array[i];
const float cost = bm_edge_calc_rotate_beauty(e, flag, method);
if (cost < 0.0f) {
eheap_table[i] = BLI_heap_insert(eheap, cost, e);
}
else {
eheap_table[i] = nullptr;
}
BM_elem_index_set(e, i); /* set_dirty */
}
bm->elem_index_dirty |= BM_EDGE;
while (BLI_heap_is_empty(eheap) == false) {
BMEdge *e = static_cast<BMEdge *>(BLI_heap_pop_min(eheap));
i = BM_elem_index_get(e);
eheap_table[i] = nullptr;
BLI_assert(BM_edge_face_count_is_equal(e, 2));
e = BM_edge_rotate(bm, e, false, BM_EDGEROT_CHECK_EXISTS);
BLI_assert(e == nullptr || BM_edge_face_count_is_equal(e, 2));
if (LIKELY(e)) {
Set<EdRotState> &e_state_set = edge_state_arr[i];
/* add the new state into the set so we don't move into this state again
* NOTE: we could add the previous state too but this isn't essential
* for avoiding eternal loops */
EdRotState *e_state = static_cast<EdRotState *>(BLI_mempool_alloc(edge_state_pool));
erot_state_current(e, e_state);
BLI_assert(!e_state_set.contains(*e_state));
e_state_set.add(*e_state);
// printf(" %d -> %d, %d\n", i, BM_elem_index_get(e->v1), BM_elem_index_get(e->v2));
/* maintain the index array */
edge_array[i] = e;
BM_elem_index_set(e, i);
/* recalculate faces connected on the heap */
bm_edge_update_beauty_cost(e,
eheap,
eheap_table,
edge_state_arr,
(const BMEdge **)edge_array,
edge_array_len,
flag,
method);
/* update flags */
if (oflag_edge) {
BMO_edge_flag_enable(bm, e, oflag_edge);
}
if (oflag_face) {
BMO_face_flag_enable(bm, e->l->f, oflag_face);
BMO_face_flag_enable(bm, e->l->radial_next->f, oflag_face);
}
}
}
BLI_heap_free(eheap, nullptr);
MEM_delete(eheap_table);
BLI_mempool_destroy(edge_state_pool);
#ifdef DEBUG_TIME
TIMEIT_END(beautify_fill);
#endif
}
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
enum {
/** Vertices tags must match (special case). */
VERT_RESTRICT_TAG = (1 << 0),
/** Don't rotate out of degenerate state (needed for iterative rotation). */
EDGE_RESTRICT_DEGENERATE = (1 << 1),
};
/**
* \note This function sets the edge indices to invalid values.
*/
void BM_mesh_beautify_fill(BMesh *bm,
BMEdge **edge_array,
int edge_array_len,
short flag,
short method,
short oflag_edge,
short oflag_face);
/**
* Assuming we have 2 triangles sharing an edge (2 - 4),
* check if the edge running from (1 - 3) gives better results.
*
* \return (negative number means the edge can be rotated, lager == better).
*/
float BM_verts_calc_rotate_beauty(const BMVert *v1,
const BMVert *v2,
const BMVert *v3,
const BMVert *v4,
short flag,
short method);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
struct CurveProfile;
struct MDeformVert;
/**
* - Currently only bevels BM_ELEM_TAG'd verts and edges.
*
* - Newly created faces, edges, and verts are BM_ELEM_TAG'd too,
* the caller needs to ensure these are cleared before calling
* if its going to use this tag.
*
* - If limit_offset is set, adjusts offset down if necessary
* to avoid geometry collisions.
*
* \warning all tagged edges _must_ be manifold.
*/
void BM_mesh_bevel(BMesh *bm,
float offset,
int offset_type,
int profile_type,
int segments,
float profile,
bool affect_type,
bool use_weights,
bool limit_offset,
const MDeformVert *dvert,
int vertex_group,
int mat,
bool loop_slide,
bool mark_seam,
bool mark_sharp,
bool harden_normals,
int face_strength_mode,
int miter_outer,
int miter_inner,
float spread,
const CurveProfile *custom_profile,
int vmesh_method,
int bweight_offset_vert,
int bweight_offset_edge);
} // namespace blender

View File

@@ -0,0 +1,560 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Cut the geometry in half using a plane.
*
* \par Implementation
* This simply works by splitting tagged edges who's verts span either side of
* the plane, then splitting faces along their dividing verts.
* The only complex case is when a ngon spans the axis multiple times,
* in this case we need to do some extra checks to correctly bisect the ngon.
* see: #bm_face_bisect_verts
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_linklist_stack.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_set.hh"
#include "BLI_utildefines.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "bmesh_bisect_plane.hh" /* Own include. */
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Math Functions
* \{ */
static short plane_point_test_v3(const float plane[4],
const float co[3],
const float eps,
float *r_depth)
{
const float f = plane_point_side_v3(plane, co);
*r_depth = f;
if (f <= -eps) {
return -1;
}
if (f >= eps) {
return 1;
}
return 0;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMesh Element Accessors
*
* Wrappers to hide internal data-structure abuse,
* later we may want to move this into some hash lookup
* to a separate struct, but for now we can store in #BMesh data.
* \{ */
#define BM_VERT_DIR(v) ((short *)(&(v)->head.index))[0] /* Direction -1/0/1 */
#define BM_VERT_SKIP(v) ((short *)(&(v)->head.index))[1] /* Skip Vert 0/1 */
#define BM_VERT_DIST(v) ((v)->no[0]) /* Distance from the plane. */
#define BM_VERT_SORTVAL(v) ((v)->no[1]) /* Temp value for sorting. */
#define BM_VERT_LOOPINDEX(v) /* The verts index within a face (temp var) */ \
(*((uint *)(&(v)->no[2])))
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMesh Flag Accessors
*
* Hide flag access
* (for more readable code since same flag is used differently for vert/edge-face).
* \{ */
/** Enable when vertex is in the center and its faces have been added to the stack. */
BLI_INLINE void vert_is_center_enable(BMVert *v)
{
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
BLI_INLINE void vert_is_center_disable(BMVert *v)
{
BM_elem_flag_disable(v, BM_ELEM_TAG);
}
BLI_INLINE bool vert_is_center_test(BMVert *v)
{
return (BM_elem_flag_test(v, BM_ELEM_TAG) != 0);
}
BLI_INLINE bool vert_pair_adjacent_in_orig_face(BMVert *v_a, BMVert *v_b, const uint f_len_orig)
{
const uint delta = uint(abs(int(BM_VERT_LOOPINDEX(v_a)) - int(BM_VERT_LOOPINDEX(v_b))));
return ELEM(delta, 1, uint(f_len_orig - 1));
}
/** Enable when the edge can be cut. */
BLI_INLINE void edge_is_cut_enable(BMEdge *e)
{
BM_elem_flag_enable(e, BM_ELEM_TAG);
}
BLI_INLINE void edge_is_cut_disable(BMEdge *e)
{
BM_elem_flag_disable(e, BM_ELEM_TAG);
}
BLI_INLINE bool edge_is_cut_test(BMEdge *e)
{
return (BM_elem_flag_test(e, BM_ELEM_TAG) != 0);
}
/** Enable when the faces are added to the stack. */
BLI_INLINE void face_in_stack_enable(BMFace *f)
{
BM_elem_flag_disable(f, BM_ELEM_TAG);
}
BLI_INLINE void face_in_stack_disable(BMFace *f)
{
BM_elem_flag_enable(f, BM_ELEM_TAG);
}
BLI_INLINE bool face_in_stack_test(BMFace *f)
{
return (BM_elem_flag_test(f, BM_ELEM_TAG) == 0);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMesh Face Bisect
* \{ */
static void bm_face_bisect_verts(
BMesh *bm, BMFace *f, const float plane[4], const short oflag_center, const short oflag_new)
{
/* Unlikely more than 2 verts are needed. */
const uint f_len_orig = uint(f->len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vert_split_arr_buf(f_len_orig);
BMVert **vert_split_arr = vert_split_arr_buf.data();
STACK_DECLARE(vert_split_arr);
BMLoop *l_iter, *l_first;
bool use_dirs[3] = {false, false, false};
bool is_inside = false;
/* True when the face contains one or more edges with both it's vertices on the plane.
* When set, centered loops are walked over to check if they need to be skipped. */
bool face_has_center_edge = false;
STACK_INIT(vert_split_arr, f_len_orig);
l_first = BM_FACE_FIRST_LOOP(f);
/* Add plane-aligned verts to the stack and check we have verts from both sides in this face
* (that the face doesn't only have boundary verts on the plane for eg). */
l_iter = l_first;
do {
if (vert_is_center_test(l_iter->v)) {
BLI_assert(BM_VERT_DIR(l_iter->v) == 0);
/* If both are -1 or 1, or both are zero: don't flip 'inside' var while walking. */
BM_VERT_SKIP(l_iter->v) = ((BM_VERT_DIR(l_iter->prev->v) ^ BM_VERT_DIR(l_iter->next->v)) ==
0);
STACK_PUSH(vert_split_arr, l_iter->v);
if (face_has_center_edge == false) {
if (vert_is_center_test(l_iter->prev->v)) {
face_has_center_edge = true;
}
}
}
use_dirs[BM_VERT_DIR(l_iter->v) + 1] = true;
} while ((l_iter = l_iter->next) != l_first);
if ((STACK_SIZE(vert_split_arr) > 1) && (use_dirs[0] && use_dirs[2])) {
if (LIKELY(STACK_SIZE(vert_split_arr) == 2)) {
BMLoop *l_new;
BMLoop *l_a, *l_b;
l_a = BM_face_vert_share_loop(f, vert_split_arr[0]);
l_b = BM_face_vert_share_loop(f, vert_split_arr[1]);
/* Common case, just cut the face once. */
BM_face_split(bm, f, l_a, l_b, &l_new, nullptr, true);
if (l_new) {
if (oflag_center | oflag_new) {
BMO_edge_flag_enable(bm, l_new->e, oflag_center | oflag_new);
}
if (oflag_new) {
BMO_face_flag_enable(bm, l_new->f, oflag_new);
}
}
}
else {
/* Less common case, _complicated_ we need to calculate how to do multiple cuts. */
uint i = 0;
/* ---- */
/* Check contiguous spans of centered vertices (skipping when necessary). */
if (face_has_center_edge) {
/* Loop indices need to be set for adjacency checks. */
l_iter = l_first;
do {
BM_VERT_LOOPINDEX(l_iter->v) = i++;
} while ((l_iter = l_iter->next) != l_first);
/* Start out on a non-centered vertex so a span of centered vertices can be looped over
* without having to scan backwards as well as forwards. */
BMLoop *l_first_non_center = l_first;
while (vert_is_center_test(l_first_non_center->v)) {
l_first_non_center = l_first_non_center->next;
}
l_iter = l_first_non_center;
do {
if (BM_VERT_SKIP(l_iter->v)) {
continue;
}
/* No need to check the previous as the iteration starts on a non-centered vertex. */
if (!(vert_is_center_test(l_iter->v) && vert_is_center_test(l_iter->next->v))) {
continue;
}
/* Walk over the next loops as long as they are centered. */
BMLoop *l_prev = l_iter->prev;
BMLoop *l_next = l_iter->next->next;
/* No need to scan the previous vertices,
* these will have been dealt with in previous steps. */
BLI_assert(!vert_is_center_test(l_prev->v));
while (vert_is_center_test(l_next->v)) {
l_next = l_next->next;
}
/* Skip all vertices when the edges connected to the beginning/end
* of the range are on a different side of the bisecting plane. */
if (!(BM_VERT_DIR(l_prev->v) ^ BM_VERT_DIR(l_next->v))) {
BLI_assert(!vert_is_center_test(l_prev->v));
l_iter = l_prev->next;
while (l_iter != l_next) {
BLI_assert(vert_is_center_test(l_iter->v));
BM_VERT_SKIP(l_iter->v) = true;
l_iter = l_iter->next;
}
}
/* Step over the span already handled, even if skip wasn't set. */
l_iter = l_next->prev;
} while ((l_iter = l_iter->next) != l_first_non_center);
}
Array<BMFace *, BM_DEFAULT_NGON_STACK_SIZE> face_split_arr_buf(STACK_SIZE(vert_split_arr));
BMFace **face_split_arr = face_split_arr_buf.data();
STACK_DECLARE(face_split_arr);
float sort_dir[3];
/* ---- */
/* Calculate the direction to sort verts in the face intersecting the plane */
/* The exact direction isn't important, vertices just need to be sorted across the face.
* (`sort_dir` could be flipped either way). */
BLI_assert(BM_face_is_normal_valid(f));
cross_v3_v3v3(sort_dir, f->no, plane);
if (UNLIKELY(normalize_v3(sort_dir) == 0.0f)) {
/* find any 2 verts and get their direction */
for (i = 0; i < STACK_SIZE(vert_split_arr); i++) {
if (!equals_v3v3(vert_split_arr[0]->co, vert_split_arr[i]->co)) {
sub_v3_v3v3(sort_dir, vert_split_arr[0]->co, vert_split_arr[i]->co);
normalize_v3(sort_dir);
}
}
if (UNLIKELY(i == STACK_SIZE(vert_split_arr))) {
/* Ok, we can't do anything useful here,
* face has no area or so, bail out, this is highly unlikely but not impossible. */
goto finally;
}
}
/* ---- */
/* Sort the verts across the face from one side to another. */
for (i = 0; i < STACK_SIZE(vert_split_arr); i++) {
BMVert *v = vert_split_arr[i];
BM_VERT_SORTVAL(v) = dot_v3v3(sort_dir, v->co);
}
std::sort(
vert_split_arr,
vert_split_arr + STACK_SIZE(vert_split_arr),
[](BMVert *v_a, BMVert *v_b) { return BM_VERT_SORTVAL(v_a) < BM_VERT_SORTVAL(v_b); });
/* ---- */
/* Split the face across sorted splits. */
/* NOTE: we don't know which face gets which splits,
* so at the moment we have to search all faces for the vert pair,
* while not all that nice, typically there are < 5 resulting faces,
* so its not _that_ bad. */
STACK_INIT(face_split_arr, STACK_SIZE(vert_split_arr));
STACK_PUSH(face_split_arr, f);
for (i = 0; i < STACK_SIZE(vert_split_arr) - 1; i++) {
BMVert *v_a = vert_split_arr[i];
BMVert *v_b = vert_split_arr[i + 1];
if (face_has_center_edge) {
if (vert_pair_adjacent_in_orig_face(v_a, v_b, f_len_orig)) {
continue;
}
}
if (!BM_VERT_SKIP(v_a)) {
is_inside = !is_inside;
}
if (is_inside) {
BMLoop *l_a, *l_b;
bool found = false;
uint j;
for (j = 0; j < STACK_SIZE(face_split_arr); j++) {
/* It would be nice to avoid loop lookup here,
* but we need to know which face the verts are in. */
if ((l_a = BM_face_vert_share_loop(face_split_arr[j], v_a)) &&
(l_b = BM_face_vert_share_loop(face_split_arr[j], v_b)))
{
found = true;
break;
}
}
/* Ideally won't happen, but it can for self-intersecting faces. */
// BLI_assert(found == true);
/* In fact this simple test is good enough, test if the loops are adjacent. */
if (found && !BM_loop_is_adjacent(l_a, l_b)) {
BMLoop *l_new;
BMFace *f_tmp;
f_tmp = BM_face_split(bm, face_split_arr[j], l_a, l_b, &l_new, nullptr, true);
if (l_new) {
if (oflag_center | oflag_new) {
BMO_edge_flag_enable(bm, l_new->e, oflag_center | oflag_new);
}
if (oflag_new) {
BMO_face_flag_enable(bm, l_new->f, oflag_new);
}
}
if (f_tmp) {
if (f_tmp != face_split_arr[j]) {
STACK_PUSH(face_split_arr, f_tmp);
BLI_assert(STACK_SIZE(face_split_arr) <= STACK_SIZE(vert_split_arr));
}
}
}
}
else {
// printf("no intersect\n");
}
}
}
}
finally:
(void)vert_split_arr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public BMesh Bisect Function
* \{ */
void BM_mesh_bisect_plane(BMesh *bm,
const float plane[4],
const bool use_snap_center,
const bool use_tag,
const short oflag_center,
const short oflag_new,
const float eps)
{
uint einput_len;
uint i;
BMEdge **edges_arr = MEM_new_array_uninitialized<BMEdge *>(size_t(bm->totedge), __func__);
BLI_LINKSTACK_DECLARE(face_stack, BMFace *);
BMVert *v;
BMFace *f;
BMIter iter;
/* Needed when `use_snap_center` moves vertices. */
Set<BMFace *> faces_deferred_normal_update;
if (use_tag) {
/* Build tagged edge array. */
BMEdge *e;
einput_len = 0;
/* Flush edge tags to verts. */
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
/* Keep face tags as is. */
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
if (edge_is_cut_test(e)) {
edges_arr[einput_len++] = e;
/* Flush edge tags to verts. */
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
}
}
/* Face tags are set by caller. */
}
else {
BMEdge *e;
einput_len = uint(bm->totedge);
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
edge_is_cut_enable(e);
edges_arr[i] = e;
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
face_in_stack_disable(f);
}
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (use_tag && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
vert_is_center_disable(v);
/* These should never be accessed. */
BM_VERT_DIR(v) = 0;
BM_VERT_DIST(v) = 0.0f;
continue;
}
vert_is_center_disable(v);
BM_VERT_DIR(v) = plane_point_test_v3(plane, v->co, eps, &BM_VERT_DIST(v));
if (BM_VERT_DIR(v) == 0) {
if (oflag_center) {
BMO_vert_flag_enable(bm, v, oflag_center);
}
if (use_snap_center) {
float co_center[3];
closest_to_plane_v3(co_center, plane, v->co);
if (!equals_v3v3(co_center, v->co)) {
copy_v3_v3(v->co, co_center);
BMIter itersub;
BM_ITER_ELEM (f, &itersub, v, BM_FACES_OF_VERT) {
faces_deferred_normal_update.add(f);
}
}
}
}
}
for (BMFace *f_iter : faces_deferred_normal_update) {
BM_face_normal_update(f_iter);
}
/* Store a stack of faces to be evaluated for splitting. */
BLI_LINKSTACK_INIT(face_stack);
for (i = 0; i < einput_len; i++) {
/* We could check `edge_is_cut_test(e)` but there is no point. */
BMEdge *e = edges_arr[i];
const int side[2] = {BM_VERT_DIR(e->v1), BM_VERT_DIR(e->v2)};
const float dist[2] = {BM_VERT_DIST(e->v1), BM_VERT_DIST(e->v2)};
if (side[0] && side[1] && (side[0] != side[1])) {
const float e_fac = dist[0] / (dist[0] - dist[1]);
BMVert *v_new;
if (e->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
if (!face_in_stack_test(l_iter->f)) {
face_in_stack_enable(l_iter->f);
BLI_LINKSTACK_PUSH(face_stack, l_iter->f);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
{
BMEdge *e_new;
v_new = BM_edge_split(bm, e, e->v1, &e_new, e_fac);
if (oflag_new) {
BMO_edge_flag_enable(bm, e_new, oflag_new);
}
}
vert_is_center_enable(v_new);
if (oflag_new | oflag_center) {
BMO_vert_flag_enable(bm, v_new, oflag_new | oflag_center);
}
BM_VERT_DIR(v_new) = 0;
BM_VERT_DIST(v_new) = 0.0f;
}
else if (side[0] == 0 || side[1] == 0) {
/* Check if either edge verts are aligned,
* if so - tag and push all faces that use it into the stack. */
uint j;
BM_ITER_ELEM_INDEX (v, &iter, e, BM_VERTS_OF_EDGE, j) {
if (side[j] == 0) {
if (vert_is_center_test(v) == 0) {
BMIter itersub;
BMLoop *l_iter;
vert_is_center_enable(v);
BM_ITER_ELEM (l_iter, &itersub, v, BM_LOOPS_OF_VERT) {
if (!face_in_stack_test(l_iter->f)) {
face_in_stack_enable(l_iter->f);
BLI_LINKSTACK_PUSH(face_stack, l_iter->f);
}
}
}
}
}
/* If both verts are on the center - tag it. */
if (oflag_center) {
if (side[0] == 0 && side[1] == 0) {
BMO_edge_flag_enable(bm, e, oflag_center);
}
}
}
}
MEM_delete(edges_arr);
while ((f = BLI_LINKSTACK_POP(face_stack))) {
bm_face_bisect_verts(bm, f, plane, oflag_center, oflag_new);
}
/* Caused by access macros: #BM_VERT_DIR, #BM_VERT_SKIP. */
bm->elem_index_dirty |= BM_VERT;
/* Now we have all faces to split in the stack. */
BLI_LINKSTACK_FREE(face_stack);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* \param use_snap_center: Snap verts onto the plane.
* \param use_tag: Only bisect tagged edges and faces.
* \param oflag_center: Operator flag, enabled for geometry on the axis (existing and created)
*/
void BM_mesh_bisect_plane(BMesh *bm,
const float plane[4],
bool use_snap_center,
bool use_tag,
short oflag_center,
short oflag_new,
float eps);
} // namespace blender

View File

@@ -0,0 +1,498 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Main functions for boolean on a #BMesh (used by the tool and modifier)
*/
#include <functional>
#include "BLI_array.hh"
#include "BLI_math_base.h"
#include "BLI_math_mpq.hh"
#include "BLI_mesh_boolean.hh"
#include "BLI_mesh_intersect.hh"
#include "bmesh.hh"
#include "bmesh_boolean.hh"
#include "bmesh_edgesplit.hh"
namespace blender {
// #define PERF_DEBUG
namespace meshintersect {
#ifdef WITH_GMP
static float3 clean_float3(const float3 &co)
{
float3 cleaned = co;
if (!isfinite(co[0])) [[unlikely]] {
cleaned[0] = 0.0f;
}
if (!isfinite(co[1])) [[unlikely]] {
cleaned[1] = 0.0f;
}
if (!isfinite(co[2])) [[unlikely]] {
cleaned[2] = 0.0f;
}
return cleaned;
}
/**
* Make a #meshintersect::Mesh from #BMesh bm.
* We are given a triangulation of it from the caller via #looptris,
* which are looptris_tot triples of loops that together tessellate
* the faces of bm.
* Return a second #IMesh in *r_triangulated that has the triangulated
* mesh, with face "orig" fields that connect the triangles back to
* the faces in the returned (polygonal) mesh.
*/
static IMesh mesh_from_bm(BMesh *bm,
const Span<std::array<BMLoop *, 3>> looptris,
IMesh *r_triangulated,
IMeshArena *arena)
{
BLI_assert(r_triangulated != nullptr);
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE);
BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE);
/* Account for triangulation and intersects. */
const int estimate_num_outv = 3 * bm->totvert;
const int estimate_num_outf = 4 * bm->totface;
arena->reserve(estimate_num_outv, estimate_num_outf);
Array<const Vert *> vert(bm->totvert);
for (int v = 0; v < bm->totvert; ++v) {
const BMVert *bmv = BM_vert_at_index(bm, v);
const float3 co = clean_float3(bmv->co);
vert[v] = arena->add_or_find_vert(mpq3(co[0], co[1], co[2]), v);
}
Array<Face *> face(bm->totface);
constexpr int estimated_max_facelen = 100;
Vector<const Vert *, estimated_max_facelen> face_vert;
Vector<int, estimated_max_facelen> face_edge_orig;
for (int f = 0; f < bm->totface; ++f) {
BMFace *bmf = BM_face_at_index(bm, f);
int flen = bmf->len;
face_vert.clear();
face_edge_orig.clear();
BMLoop *l = bmf->l_first;
for (int i = 0; i < flen; ++i) {
const Vert *v = vert[BM_elem_index_get(l->v)];
face_vert.append(v);
int e_index = BM_elem_index_get(l->e);
face_edge_orig.append(e_index);
l = l->next;
}
face[f] = arena->add_face(face_vert, f, face_edge_orig);
}
/* Now do the triangulation mesh.
* The loop_tris have accurate v and f members for the triangles,
* but their next and e pointers are not correct for the loops
* that start added-diagonal edges. */
Array<Face *> tri_face(looptris.size());
face_vert.resize(3);
face_edge_orig.resize(3);
for (const int i : looptris.index_range()) {
BMFace *bmf = looptris[i][0]->f;
int f = BM_elem_index_get(bmf);
for (int j = 0; j < 3; ++j) {
BMLoop *l = looptris[i][j];
int v_index = BM_elem_index_get(l->v);
int e_index;
if (l->next->v == looptris[i][(j + 1) % 3]->v) {
e_index = BM_elem_index_get(l->e);
}
else {
e_index = NO_INDEX;
}
face_vert[j] = vert[v_index];
face_edge_orig[j] = e_index;
}
tri_face[i] = arena->add_face(face_vert, f, face_edge_orig);
}
r_triangulated->set_faces(tri_face);
return IMesh(face);
}
static bool bmvert_attached_to_wire(const BMVert *bmv)
{
/* This is not quite right. It returns true if the only edges
* Attached to \a bmv are wire edges. TODO: iterate through edges
* attached to \a bmv and check #BM_edge_is_wire. */
return BM_vert_is_wire(bmv);
}
static bool bmvert_attached_to_hidden_face(BMVert *bmv)
{
BMIter iter;
for (BMFace *bmf = static_cast<BMFace *>(BM_iter_new(&iter, nullptr, BM_FACES_OF_VERT, bmv));
bmf;
bmf = static_cast<BMFace *>(BM_iter_step(&iter)))
{
if (BM_elem_flag_test(bmf, BM_ELEM_HIDDEN)) {
return true;
}
}
return false;
}
/** Use the unused _BM_ELEM_TAG_ALT #BMElem.hflag to mark geometry we will keep. */
constexpr uint KEEP_FLAG = (1 << 6);
/**
* Change #BMesh bm to have the mesh match m_out. Return true if there were any changes at all.
* Vertices, faces, and edges in the current bm that are not used in the output are killed,
* except we don't kill wire edges and we don't kill hidden geometry.
* Also, the #BM_ELEM_TAG header flag is set for those #BMEdge's that come from intersections
* resulting from the intersection needed by the Boolean operation.
*/
static bool apply_mesh_output_to_bmesh(BMesh *bm, IMesh &m_out, bool keep_hidden)
{
bool any_change = false;
m_out.populate_vert();
/* Initially mark all existing verts as "don't keep", except hidden verts
* (if keep_hidden is true), and verts attached to wire edges. */
for (int v = 0; v < bm->totvert; ++v) {
BMVert *bmv = BM_vert_at_index(bm, v);
if ((keep_hidden &&
(BM_elem_flag_test(bmv, BM_ELEM_HIDDEN) || bmvert_attached_to_hidden_face(bmv))) ||
bmvert_attached_to_wire(bmv))
{
BM_elem_flag_enable(bmv, KEEP_FLAG);
}
else {
BM_elem_flag_disable(bmv, KEEP_FLAG);
}
}
/* Reuse old or make new #BMVert's, depending on if there's an orig or not.
* For those reused, mark them "keep".
* Store needed old #BMVert's in new_bmvs first, as the table may be unusable after
* creating a new #BMVert. */
Array<BMVert *> new_bmvs(m_out.vert_size());
for (int v : m_out.vert_index_range()) {
const Vert *vertp = m_out.vert(v);
int orig = vertp->orig;
if (orig != NO_INDEX) {
BLI_assert(orig >= 0 && orig < bm->totvert);
BMVert *bmv = BM_vert_at_index(bm, orig);
new_bmvs[v] = bmv;
BM_elem_flag_enable(bmv, KEEP_FLAG);
}
else {
new_bmvs[v] = nullptr;
}
}
for (int v : m_out.vert_index_range()) {
const Vert *vertp = m_out.vert(v);
if (new_bmvs[v] == nullptr) {
float co[3];
const double3 &d_co = vertp->co;
for (int i = 0; i < 3; ++i) {
co[i] = float(d_co[i]);
}
BMVert *bmv = BM_vert_create(bm, co, nullptr, BM_CREATE_NOP);
new_bmvs[v] = bmv;
BM_elem_flag_enable(bmv, KEEP_FLAG);
any_change = true;
}
}
/* Initially mark all existing faces as "don't keep", except hidden faces (if keep_hidden).
* Also, save current #BMFace pointers as creating faces will disturb the table. */
Array<BMFace *> old_bmfs(bm->totface);
BM_mesh_elem_index_ensure(bm, BM_FACE);
for (int f = 0; f < bm->totface; ++f) {
BMFace *bmf = BM_face_at_index(bm, f);
old_bmfs[f] = bmf;
if (keep_hidden && BM_elem_flag_test(bmf, BM_ELEM_HIDDEN)) {
BM_elem_flag_enable(bmf, KEEP_FLAG);
}
else {
BM_elem_flag_disable(bmf, KEEP_FLAG);
}
}
/* Save the original #BMEdge's so we can use them as examples. */
Array<BMEdge *> old_edges(bm->totedge);
std::copy_n(bm->etable, bm->totedge, old_edges.begin());
/* Reuse or make new #BMFace's, as the faces are identical to old ones or not.
* If reusing, mark them as "keep". First find the maximum face length
* so we can declare some arrays outside of the face-creating loop. */
int maxflen = 0;
for (const Face *f : m_out.faces()) {
maxflen = max_ii(maxflen, f->size());
}
Array<BMVert *> face_bmverts(maxflen);
Array<BMEdge *> face_bmedges(maxflen);
for (const Face *f : m_out.faces()) {
const Face &face = *f;
int flen = face.size();
for (int i = 0; i < flen; ++i) {
const Vert *v = face[i];
int v_index = m_out.lookup_vert(v);
BLI_assert(v_index < new_bmvs.size());
face_bmverts[i] = new_bmvs[v_index];
}
BMFace *bmf = BM_face_exists(face_bmverts.data(), flen);
/* Never allow any duplicates (either winding), as this isn't legal mesh data, see: 160437. */
if (bmf != nullptr) {
BM_elem_flag_enable(bmf, KEEP_FLAG);
}
else {
int orig = face.orig;
BMFace *orig_face;
/* There should always be an orig face, but just being extra careful here. */
if (orig != NO_INDEX) {
orig_face = old_bmfs[orig];
}
else {
orig_face = nullptr;
}
/* Make or find #BMEdge's. */
for (int i = 0; i < flen; ++i) {
BMVert *bmv1 = face_bmverts[i];
BMVert *bmv2 = face_bmverts[(i + 1) % flen];
BMEdge *bme = BM_edge_exists(bmv1, bmv2);
if (bme == nullptr) {
BMEdge *orig_edge = nullptr;
if (face.edge_orig[i] != NO_INDEX) {
orig_edge = old_edges[face.edge_orig[i]];
}
bme = BM_edge_create(bm, bmv1, bmv2, orig_edge, BM_CREATE_NOP);
if (orig_edge != nullptr) {
BM_elem_select_copy(bm, bme, orig_edge);
}
}
face_bmedges[i] = bme;
if (face.is_intersect[i]) {
BM_elem_flag_enable(bme, BM_ELEM_TAG);
}
else {
BM_elem_flag_disable(bme, BM_ELEM_TAG);
}
}
BMFace *bmf = BM_face_create(
bm, face_bmverts.data(), face_bmedges.data(), flen, orig_face, BM_CREATE_NOP);
if (orig_face != nullptr) {
BM_elem_select_copy(bm, bmf, orig_face);
}
BM_elem_flag_enable(bmf, KEEP_FLAG);
/* Now do interpolation of loop data (e.g., UVs) using the example face. */
if (orig_face != nullptr) {
BMIter liter;
BMLoop *l = static_cast<BMLoop *>(BM_iter_new(&liter, bm, BM_LOOPS_OF_FACE, bmf));
while (l != nullptr) {
BM_loop_interp_from_face(bm, l, orig_face, false, true);
l = static_cast<BMLoop *>(BM_iter_step(&liter));
}
}
any_change = true;
}
}
/* Now kill the unused faces and verts, and clear flags for kept ones. */
/* #BM_ITER_MESH_MUTABLE macro needs type casts for C++, so expand here.
* TODO(howard): make some nice C++ iterators for #BMesh. */
BMIter iter;
BMFace *bmf = static_cast<BMFace *>(BM_iter_new(&iter, bm, BM_FACES_OF_MESH, nullptr));
while (bmf != nullptr) {
# ifndef NDEBUG
iter.count = BM_iter_mesh_count(BM_FACES_OF_MESH, bm);
# endif
BMFace *bmf_next = static_cast<BMFace *>(BM_iter_step(&iter));
if (BM_elem_flag_test(bmf, KEEP_FLAG)) {
BM_elem_flag_disable(bmf, KEEP_FLAG);
}
else {
BM_face_kill_loose(bm, bmf);
# if 0
BM_face_kill(bm, bmf);
# endif
any_change = true;
}
bmf = bmf_next;
}
BMVert *bmv = static_cast<BMVert *>(BM_iter_new(&iter, bm, BM_VERTS_OF_MESH, nullptr));
while (bmv != nullptr) {
# ifndef NDEBUG
iter.count = BM_iter_mesh_count(BM_VERTS_OF_MESH, bm);
# endif
BMVert *bmv_next = static_cast<BMVert *>(BM_iter_step(&iter));
if (BM_elem_flag_test(bmv, KEEP_FLAG)) {
BM_elem_flag_disable(bmv, KEEP_FLAG);
}
else {
BM_vert_kill(bm, bmv);
any_change = true;
}
bmv = bmv_next;
}
return any_change;
}
static bool bmesh_boolean(BMesh *bm,
const Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
int nshapes,
const bool use_self,
const bool use_separate_all,
const bool keep_hidden,
const bool hole_tolerant,
const BoolOpType boolean_mode)
{
IMeshArena arena;
IMesh m_triangulated;
# ifdef PERF_DEBUG
double start_time = BLI_time_now_seconds();
# endif
IMesh m_in = mesh_from_bm(bm, looptris, &m_triangulated, &arena);
# ifdef PERF_DEBUG
double mesh_time = BLI_time_now_seconds();
std::cout << "bmesh_boolean, imesh_from_bm done, time = " << mesh_time - start_time << "\n";
# endif
std::function<int(int)> shape_fn;
if (use_self && boolean_mode == BoolOpType::None) {
/* Unary knife operation. Want every face where test_fn doesn't return -1. */
BLI_assert(nshapes == 1);
shape_fn = [bm, test_fn, user_data](int f) {
BMFace *bmf = BM_face_at_index(bm, f);
if (test_fn(bmf, user_data) != -1) {
return 0;
}
return -1;
};
}
else {
shape_fn = [bm, test_fn, user_data](int f) {
BMFace *bmf = BM_face_at_index(bm, f);
int test_val = test_fn(bmf, user_data);
if (test_val >= 0) {
return test_val;
}
return -1;
};
}
IMesh m_out = boolean_mesh(
m_in, boolean_mode, nshapes, shape_fn, use_self, hole_tolerant, &m_triangulated, &arena);
# ifdef PERF_DEBUG
double boolean_time = BLI_time_now_seconds();
std::cout << "boolean done, time = " << boolean_time - mesh_time << "\n";
# endif
bool any_change = apply_mesh_output_to_bmesh(bm, m_out, keep_hidden);
# ifdef PERF_DEBUG
double apply_mesh_time = BLI_time_now_seconds();
std::cout << "applied boolean output to bmesh, time = " << apply_mesh_time - boolean_time
<< "\n";
# endif
if (use_separate_all) {
/* We are supposed to separate all faces that are incident on intersection edges. */
BM_mesh_edgesplit(bm, false, true, false);
}
return any_change;
}
#endif // WITH_GMP
} // namespace meshintersect
/**
* Perform the boolean operation specified by boolean_mode on the mesh bm.
* The inputs to the boolean operation are either one sub-mesh (if use_self is true),
* or two sub-meshes. The sub-meshes are specified by providing a test_fn which takes
* a face and the supplied user_data and says with 'side' of the boolean operation
* that face is for: 0 for the first side (side A), 1 for the second side (side B),
* and -1 if the face is to be ignored completely in the boolean operation.
*
* If use_self is true, all operations do the same: the sub-mesh is self-intersected
* and all pieces inside that result are removed.
* Otherwise, the operations can be one of #BMESH_ISECT_BOOLEAN_ISECT, #BMESH_ISECT_BOOLEAN_UNION,
* or #BMESH_ISECT_BOOLEAN_DIFFERENCE.
*
* (The actual library function called to do the boolean is internally capable of handling
* n-ary operands, so maybe in the future we can expose that functionality to users.)
*/
#ifdef WITH_GMP
bool BM_mesh_boolean(BMesh *bm,
const Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const int nshapes,
const bool use_self,
const bool keep_hidden,
const bool hole_tolerant,
const int boolean_mode)
{
return meshintersect::bmesh_boolean(bm,
looptris,
test_fn,
user_data,
nshapes,
use_self,
false,
keep_hidden,
hole_tolerant,
static_cast<meshintersect::BoolOpType>(boolean_mode));
}
bool BM_mesh_boolean_knife(BMesh *bm,
const Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
const int nshapes,
const bool use_self,
const bool use_separate_all,
const bool hole_tolerant,
const bool keep_hidden)
{
return meshintersect::bmesh_boolean(bm,
looptris,
test_fn,
user_data,
nshapes,
use_self,
use_separate_all,
keep_hidden,
hole_tolerant,
meshintersect::BoolOpType::None);
}
#else
bool BM_mesh_boolean(BMesh * /*bm*/,
Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *, void *),
void * /*user_data*/,
const int /*nshapes*/,
const bool /*use_self*/,
const bool /*keep_hidden*/,
const bool /*hole_tolerant*/,
const int /*boolean_mode*/)
{
UNUSED_VARS(looptris, test_fn);
return false;
}
bool BM_mesh_boolean_knife(BMesh * /*bm*/,
Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *, void *),
void * /*user_data*/,
const int /*nshapes*/,
const bool /*use_self*/,
const bool /*use_separate_all*/,
const bool /*hole_tolerant*/,
const bool /*keep_hidden*/)
{
UNUSED_VARS(looptris, test_fn);
return false;
}
#endif
} // namespace blender

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include <array>
#include "BLI_span.hh"
#include "bmesh_class.hh"
namespace blender {
bool BM_mesh_boolean(BMesh *bm,
Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
int nshapes,
bool use_self,
bool keep_hidden,
bool hole_tolerant,
int boolean_mode);
/**
* Perform a Knife Intersection operation on the mesh `bm`.
* There are either one or two operands, the same as described above for #BM_mesh_boolean().
*
* \param use_separate_all: When true, each edge that is created from the intersection should
* be used to separate all its incident faces. TODO: implement that.
*
* TODO: need to ensure that "selected/non-selected" flag of original faces gets propagated
* to the intersection result faces.
*/
bool BM_mesh_boolean_knife(BMesh *bm,
Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
int nshapes,
bool use_self,
bool use_separate_all,
bool hole_tolerant,
bool keep_hidden);
} // namespace blender

View File

@@ -0,0 +1,59 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "intern/bmesh_operator_api.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* \brief BM_mesh_decimate
* \param bm: The mesh
* \param factor: face count multiplier [0 - 1]
* \param vweights: Optional array of vertex aligned weights [0 - 1],
* a vertex group is the usual source for this.
* \param symmetry_axis: Axis of symmetry, -1 to disable mirror decimate.
* \param symmetry_eps: Threshold when matching mirror verts.
*
* \note The caller is responsible for recalculating face and vertex normals.
* - Vertex normals are maintained while decimating,
* although they won't necessarily match the final recalculated normals.
* - Face normals are not maintained at all.
*/
void BM_mesh_decimate_collapse(BMesh *bm,
float factor,
float *vweights,
float vweight_factor,
bool do_triangulate,
int symmetry_axis,
float symmetry_eps);
/**
* \param tag_only: so we can call this from an operator.
*/
void BM_mesh_decimate_unsubdivide_ex(BMesh *bm, int iterations, bool tag_only);
void BM_mesh_decimate_unsubdivide(BMesh *bm, int iterations);
void BM_mesh_decimate_dissolve_ex(BMesh *bm,
float angle_limit,
bool do_dissolve_boundaries,
BMO_Delimit delimit,
BMVert **vinput_arr,
int vinput_len,
BMEdge **einput_arr,
int einput_len,
short oflag_out);
void BM_mesh_decimate_dissolve(BMesh *bm,
float angle_limit,
bool do_dissolve_boundaries,
const BMO_Delimit delimit);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,578 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh decimator that dissolves flat areas into polygons (ngons).
*/
#include "MEM_guardedalloc.h"
#include <algorithm>
#include "BLI_heap.h"
#include "BLI_math_geom.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "bmesh_decimate.hh" /* own include */
namespace blender {
/* check that collapsing a vertex between 2 edges doesn't cause a degenerate face. */
#define USE_DEGENERATE_CHECK
#define COST_INVALID FLT_MAX
namespace {
struct DelimitData {
int cd_loop_type;
int cd_loop_size;
int cd_loop_offset;
int cd_loop_offset_end;
};
} // namespace
static bool bm_edge_is_delimiter(const BMEdge *e,
const BMO_Delimit delimit,
const DelimitData *delimit_data);
static bool bm_vert_is_delimiter(const BMVert *v,
const BMO_Delimit delimit,
const DelimitData *delimit_data);
/* multiply vertex edge angle by face angle
* this means we are not left with sharp corners between _almost_ planer faces
* convert angles [0-PI/2] -> [0-1], multiply together, then convert back to radians. */
static float bm_vert_edge_face_angle(BMVert *v,
const BMO_Delimit delimit,
const DelimitData *delimit_data)
{
#define UNIT_TO_ANGLE DEG2RADF(90.0f)
#define ANGLE_TO_UNIT (1.0f / UNIT_TO_ANGLE)
const float angle = BM_vert_calc_edge_angle(v);
/* NOTE: could be either edge, it doesn't matter. */
if (v->e && BM_edge_is_manifold(v->e)) {
/* Checking delimited is important here,
* otherwise, for example, the boundary between two materials
* will collapse if the faces on either side of the edge have a small angle.
*
* This way, delimiting edges are treated like boundary edges,
* the detail between two delimiting regions won't over-collapse. */
if (!bm_vert_is_delimiter(v, delimit, delimit_data)) {
return ((angle * ANGLE_TO_UNIT) * (BM_edge_calc_face_angle(v->e) * ANGLE_TO_UNIT)) *
UNIT_TO_ANGLE;
}
}
return angle;
#undef UNIT_TO_ANGLE
#undef ANGLE_TO_UNIT
}
static bool bm_edge_is_contiguous_loop_cd_all(const BMEdge *e, const DelimitData *delimit_data)
{
int cd_loop_offset;
for (cd_loop_offset = delimit_data->cd_loop_offset;
cd_loop_offset < delimit_data->cd_loop_offset_end;
cd_loop_offset += delimit_data->cd_loop_size)
{
if (BM_edge_is_contiguous_loop_cd(e, delimit_data->cd_loop_type, cd_loop_offset) == false) {
return false;
}
}
return true;
}
static bool bm_edge_is_delimiter(const BMEdge *e,
const BMO_Delimit delimit,
const DelimitData *delimit_data)
{
/* Caller must ensure. */
BLI_assert(BM_edge_is_manifold(e));
if (delimit != 0) {
if (delimit & BMO_DELIM_SEAM) {
if (BM_elem_flag_test(e, BM_ELEM_SEAM)) {
return true;
}
}
if (delimit & BMO_DELIM_SHARP) {
if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) == 0) {
return true;
}
}
if (delimit & BMO_DELIM_MATERIAL) {
if (e->l->f->mat_nr != e->l->radial_next->f->mat_nr) {
return true;
}
}
if (delimit & BMO_DELIM_NORMAL) {
if (!BM_edge_is_contiguous(e)) {
return true;
}
}
if (delimit & BMO_DELIM_UV) {
if (bm_edge_is_contiguous_loop_cd_all(e, delimit_data) == 0) {
return true;
}
}
}
return false;
}
static bool bm_vert_is_delimiter(const BMVert *v,
const BMO_Delimit delimit,
const DelimitData *delimit_data)
{
BLI_assert(v->e != nullptr);
if (delimit != 0) {
const BMEdge *e, *e_first;
e = e_first = v->e;
do {
if (BM_edge_is_manifold(e)) {
if (bm_edge_is_delimiter(e, delimit, delimit_data)) {
return true;
}
}
} while ((e = BM_DISK_EDGE_NEXT(e, v)) != e_first);
}
return false;
}
static float bm_edge_calc_dissolve_error(const BMEdge *e,
const BMO_Delimit delimit,
const DelimitData *delimit_data)
{
if (BM_edge_is_manifold(e) && !bm_edge_is_delimiter(e, delimit, delimit_data)) {
float angle_cos_neg = dot_v3v3(e->l->f->no, e->l->radial_next->f->no);
if (BM_edge_is_contiguous(e)) {
angle_cos_neg *= -1;
}
return angle_cos_neg;
}
return COST_INVALID;
}
#ifdef USE_DEGENERATE_CHECK
static void mul_v2_m3v3_center(float r[2],
const float m[3][3],
const float a[3],
const float center[3])
{
BLI_assert(r != a);
BLI_assert(r != center);
float co[3];
sub_v3_v3v3(co, a, center);
r[0] = m[0][0] * co[0] + m[1][0] * co[1] + m[2][0] * co[2];
r[1] = m[0][1] * co[0] + m[1][1] * co[1] + m[2][1] * co[2];
}
static bool bm_loop_collapse_is_degenerate(BMLoop *l_ear)
{
/* Calculate relative to the central vertex for higher precision. */
const float *center = l_ear->v->co;
float tri_2d[3][2];
float axis_mat[3][3];
axis_dominant_v3_to_m3(axis_mat, l_ear->f->no);
{
mul_v2_m3v3_center(tri_2d[0], axis_mat, l_ear->prev->v->co, center);
# if 0
mul_v2_m3v3_center(tri_2d[1], axis_mat, l_ear->v->co, center);
# else
zero_v2(tri_2d[1]);
# endif
mul_v2_m3v3_center(tri_2d[2], axis_mat, l_ear->next->v->co, center);
}
/* check we're not flipping face corners before or after the ear */
{
float adjacent_2d[2];
if (!BM_vert_is_edge_pair(l_ear->prev->v)) {
mul_v2_m3v3_center(adjacent_2d, axis_mat, l_ear->prev->prev->v->co, center);
if (signum_i(cross_tri_v2(adjacent_2d, tri_2d[0], tri_2d[1])) !=
signum_i(cross_tri_v2(adjacent_2d, tri_2d[0], tri_2d[2])))
{
return true;
}
}
if (!BM_vert_is_edge_pair(l_ear->next->v)) {
mul_v2_m3v3_center(adjacent_2d, axis_mat, l_ear->next->next->v->co, center);
if (signum_i(cross_tri_v2(adjacent_2d, tri_2d[2], tri_2d[1])) !=
signum_i(cross_tri_v2(adjacent_2d, tri_2d[2], tri_2d[0])))
{
return true;
}
}
}
/* check no existing verts are inside the triangle */
{
/* triangle may be concave, if so - flip so we can use clockwise check */
if (cross_tri_v2(UNPACK3(tri_2d)) < 0.0f) {
swap_v2_v2(tri_2d[1], tri_2d[2]);
}
/* skip l_ear and adjacent verts */
BMLoop *l_iter, *l_first;
l_iter = l_ear->next->next;
l_first = l_ear->prev;
do {
float co_2d[2];
mul_v2_m3v3_center(co_2d, axis_mat, l_iter->v->co, center);
if (isect_point_tri_v2_cw(co_2d, tri_2d[0], tri_2d[1], tri_2d[2])) {
return true;
}
} while ((l_iter = l_iter->next) != l_first);
}
return false;
}
static bool bm_vert_collapse_is_degenerate(BMVert *v)
{
BMEdge *e_pair[2];
BMVert *v_pair[2];
if (BM_vert_edge_pair(v, &e_pair[0], &e_pair[1])) {
/* allow wire edges */
if (BM_edge_is_wire(e_pair[0]) || BM_edge_is_wire(e_pair[1])) {
return false;
}
v_pair[0] = BM_edge_other_vert(e_pair[0], v);
v_pair[1] = BM_edge_other_vert(e_pair[1], v);
if (fabsf(cos_v3v3v3(v_pair[0]->co, v->co, v_pair[1]->co)) < (1.0f - FLT_EPSILON)) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e_pair[1]->l;
do {
if (l_iter->f->len > 3) {
BMLoop *l_pivot = (l_iter->v == v ? l_iter : l_iter->next);
BLI_assert(v == l_pivot->v);
if (bm_loop_collapse_is_degenerate(l_pivot)) {
return true;
}
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
return false;
}
return true;
}
#endif /* USE_DEGENERATE_CHECK */
void BM_mesh_decimate_dissolve_ex(BMesh *bm,
const float angle_limit,
const bool do_dissolve_boundaries,
BMO_Delimit delimit,
BMVert **vinput_arr,
const int vinput_len,
BMEdge **einput_arr,
const int einput_len,
const short oflag_out)
{
const float angle_limit_cos_neg = -cosf(angle_limit);
DelimitData delimit_data = {0};
const int eheap_table_len = do_dissolve_boundaries ? einput_len : max_ii(einput_len, vinput_len);
void *_heap_table = MEM_new_array_uninitialized<HeapNode *>(eheap_table_len, __func__);
int i;
if (delimit & BMO_DELIM_UV) {
const int layer_len = CustomData_number_of_layers(&bm->ldata, CD_PROP_FLOAT2);
if (layer_len == 0) {
delimit &= ~BMO_DELIM_UV;
}
else {
delimit_data.cd_loop_type = CD_PROP_FLOAT2;
delimit_data.cd_loop_size = CustomData_sizeof(eCustomDataType(delimit_data.cd_loop_type));
delimit_data.cd_loop_offset = CustomData_get_n_offset(&bm->ldata, CD_PROP_FLOAT2, 0);
delimit_data.cd_loop_offset_end = delimit_data.cd_loop_offset +
delimit_data.cd_loop_size * layer_len;
}
}
/* --- first edges --- */
if (true) {
BMEdge **earray;
Heap *eheap;
HeapNode **eheap_table = static_cast<HeapNode **>(_heap_table);
HeapNode *enode_top;
int *vert_reverse_lookup;
BMIter iter;
BMEdge *e_iter;
/* --- setup heap --- */
eheap = BLI_heap_new_ex(einput_len);
/* wire -> tag */
BM_ITER_MESH (e_iter, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_set(e_iter, BM_ELEM_TAG, BM_edge_is_wire(e_iter));
BM_elem_index_set(e_iter, -1); /* set dirty */
}
bm->elem_index_dirty |= BM_EDGE;
/* build heap */
for (i = 0; i < einput_len; i++) {
BMEdge *e = einput_arr[i];
const float cost = bm_edge_calc_dissolve_error(e, delimit, &delimit_data);
eheap_table[i] = BLI_heap_insert(eheap, cost, e);
BM_elem_index_set(e, i); /* set dirty */
}
while ((BLI_heap_is_empty(eheap) == false) &&
(BLI_heap_node_value(enode_top = BLI_heap_top(eheap)) < angle_limit_cos_neg))
{
BMFace *f_new = nullptr;
BMEdge *e;
e = static_cast<BMEdge *>(BLI_heap_node_ptr(enode_top));
i = BM_elem_index_get(e);
if (BM_edge_is_manifold(e)) {
/* The `f_new` may be an existing face, see #144383.
* In this case it's still flagged as output so the selection
* isn't "lost" when dissolving, see: !144653. */
f_new = BM_faces_join_pair(bm, e->l, e->l->radial_next, false, nullptr);
if (f_new) {
BMLoop *l_first, *l_iter;
BLI_heap_remove(eheap, enode_top);
eheap_table[i] = nullptr;
/* update normal */
BM_face_normal_update(f_new);
if (oflag_out) {
BMO_face_flag_enable(bm, f_new, oflag_out);
}
/* re-calculate costs */
l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
do {
const int j = BM_elem_index_get(l_iter->e);
if (j != -1 && eheap_table[j]) {
const float cost = bm_edge_calc_dissolve_error(l_iter->e, delimit, &delimit_data);
BLI_heap_node_value_update(eheap, eheap_table[j], cost);
}
} while ((l_iter = l_iter->next) != l_first);
}
}
if (UNLIKELY(f_new == nullptr)) {
BLI_heap_node_value_update(eheap, enode_top, COST_INVALID);
}
}
/* prepare for cleanup */
BM_mesh_elem_index_ensure(bm, BM_VERT);
vert_reverse_lookup = MEM_new_array_uninitialized<int>(bm->totvert, __func__);
std::fill_n(vert_reverse_lookup, bm->totvert, -1);
for (i = 0; i < vinput_len; i++) {
BMVert *v = vinput_arr[i];
vert_reverse_lookup[BM_elem_index_get(v)] = i;
}
/* --- cleanup --- */
earray = MEM_new_array_uninitialized<BMEdge *>(bm->totedge, __func__);
BM_ITER_MESH_INDEX (e_iter, &iter, bm, BM_EDGES_OF_MESH, i) {
earray[i] = e_iter;
}
/* Remove all edges/verts left behind from dissolving,
* nulling the vertex array so we don't re-use. */
for (i = bm->totedge - 1; i != -1; i--) {
e_iter = earray[i];
if (BM_edge_is_wire(e_iter) && (BM_elem_flag_test(e_iter, BM_ELEM_TAG) == false)) {
/* edge has become wire */
int vidx_reverse;
BMVert *v1 = e_iter->v1;
BMVert *v2 = e_iter->v2;
BM_edge_kill(bm, e_iter);
if (v1->e == nullptr) {
vidx_reverse = vert_reverse_lookup[BM_elem_index_get(v1)];
if (vidx_reverse != -1) {
vinput_arr[vidx_reverse] = nullptr;
}
BM_vert_kill(bm, v1);
}
if (v2->e == nullptr) {
vidx_reverse = vert_reverse_lookup[BM_elem_index_get(v2)];
if (vidx_reverse != -1) {
vinput_arr[vidx_reverse] = nullptr;
}
BM_vert_kill(bm, v2);
}
}
}
MEM_delete(vert_reverse_lookup);
MEM_delete(earray);
BLI_heap_free(eheap, nullptr);
}
/* --- second verts --- */
if (do_dissolve_boundaries) {
/* simple version of the branch below, since we will dissolve _all_ verts that use 2 edges */
for (i = 0; i < vinput_len; i++) {
BMVert *v = vinput_arr[i];
if (LIKELY(v != nullptr) && BM_vert_is_edge_pair(v)) {
BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
}
}
}
else {
Heap *vheap;
HeapNode **vheap_table = static_cast<HeapNode **>(_heap_table);
HeapNode *vnode_top;
BMVert *v_iter;
BMIter iter;
BM_ITER_MESH (v_iter, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_index_set(v_iter, -1); /* set dirty */
}
bm->elem_index_dirty |= BM_VERT;
vheap = BLI_heap_new_ex(vinput_len);
for (i = 0; i < vinput_len; i++) {
BMVert *v = vinput_arr[i];
if (LIKELY(v != nullptr)) {
const float cost = bm_vert_edge_face_angle(v, delimit, &delimit_data);
vheap_table[i] = BLI_heap_insert(vheap, cost, v);
BM_elem_index_set(v, i); /* set dirty */
}
}
while ((BLI_heap_is_empty(vheap) == false) &&
(BLI_heap_node_value(vnode_top = BLI_heap_top(vheap)) < angle_limit))
{
BMEdge *e_new = nullptr;
BMVert *v;
v = static_cast<BMVert *>(BLI_heap_node_ptr(vnode_top));
i = BM_elem_index_get(v);
if (
#ifdef USE_DEGENERATE_CHECK
!bm_vert_collapse_is_degenerate(v)
#else
BM_vert_is_edge_pair(v)
#endif
)
{
e_new = BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
if (e_new) {
BLI_heap_remove(vheap, vnode_top);
vheap_table[i] = nullptr;
/* update normal */
if (e_new->l) {
BMLoop *l_first, *l_iter;
l_iter = l_first = e_new->l;
do {
BM_face_normal_update(l_iter->f);
} while ((l_iter = l_iter->radial_next) != l_first);
}
/* re-calculate costs */
BM_ITER_ELEM (v_iter, &iter, e_new, BM_VERTS_OF_EDGE) {
const int j = BM_elem_index_get(v_iter);
if (j != -1 && vheap_table[j]) {
const float cost = bm_vert_edge_face_angle(v_iter, delimit, &delimit_data);
BLI_heap_node_value_update(vheap, vheap_table[j], cost);
}
}
#ifdef USE_DEGENERATE_CHECK
/* dissolving a vertex may mean vertices we previously weren't able to dissolve
* can now be re-evaluated. */
if (e_new->l) {
BMLoop *l_first, *l_iter;
l_iter = l_first = e_new->l;
do {
/* skip vertices part of this edge, evaluated above */
BMLoop *l_cycle_first, *l_cycle_iter;
l_cycle_iter = l_iter->next->next;
l_cycle_first = l_iter->prev;
do {
const int j = BM_elem_index_get(l_cycle_iter->v);
if (j != -1 && vheap_table[j] &&
(BLI_heap_node_value(vheap_table[j]) == COST_INVALID))
{
const float cost = bm_vert_edge_face_angle(
l_cycle_iter->v, delimit, &delimit_data);
BLI_heap_node_value_update(vheap, vheap_table[j], cost);
}
} while ((l_cycle_iter = l_cycle_iter->next) != l_cycle_first);
} while ((l_iter = l_iter->radial_next) != l_first);
}
#endif /* USE_DEGENERATE_CHECK */
}
}
if (UNLIKELY(e_new == nullptr)) {
BLI_heap_node_value_update(vheap, vnode_top, COST_INVALID);
}
}
BLI_heap_free(vheap, nullptr);
}
MEM_delete_void(_heap_table);
}
void BM_mesh_decimate_dissolve(BMesh *bm,
const float angle_limit,
const bool do_dissolve_boundaries,
const BMO_Delimit delimit)
{
int vinput_len;
int einput_len;
BMVert **vinput_arr = static_cast<BMVert **>(
BM_iter_as_arrayN(bm, BM_VERTS_OF_MESH, nullptr, &vinput_len, nullptr, 0));
BMEdge **einput_arr = static_cast<BMEdge **>(
BM_iter_as_arrayN(bm, BM_EDGES_OF_MESH, nullptr, &einput_len, nullptr, 0));
BM_mesh_decimate_dissolve_ex(bm,
angle_limit,
do_dissolve_boundaries,
delimit,
vinput_arr,
vinput_len,
einput_arr,
einput_len,
0);
MEM_delete(vinput_arr);
MEM_delete(einput_arr);
}
} // namespace blender

View File

@@ -0,0 +1,573 @@
/* 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 <algorithm>
#include <optional>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "bmesh.hh"
#include "bmesh_decimate.hh" /* own include */
namespace blender {
/**
* Vertices with more than this number of connected edges/faces are ignored,
* they don't need to be classified/dissolved.
*/
constexpr int VERT_DISSOLVE_MAX = 4;
/** Classify the kind of dissolve the vertex may use. */
enum class VertDissolveMethod {
/**
* Surrounded by 3/4 manifold edges (each with 2 connected faces).
*
* Dissolves by clipping ears of connected faces into triangles, then joining them.
*/
InteriorFan,
/**
* Surrounded by 2 edges, each connected to the same 2 faces.
*
* Dissolves by removing the vertex and connecting the two adjacent edges into a single edge.
*/
InteriorChain,
/**
* Surrounded by 1 manifold edge (2 connected faces), and 2 boundary edges.
*
* Dissolves by clipping ears of connected faces into triangles, then joining them.
*/
BoundaryFan,
/**
* Surrounded by 2 edges, not connected to any faces.
*
* Dissolves by removing the vertex and connecting the two adjacent edges into a single edge.
*/
WireChain,
};
static int bm_vert_loops_from_vert_edges(const BMVert *v,
BMEdge **v_edges,
const int v_edges_num,
BMLoop *v_loops[VERT_DISSOLVE_MAX])
{
int v_loops_num = 0;
for (int i = 0; i < v_edges_num; i++) {
const BMEdge *e = v_edges[i];
if (e->l != nullptr) {
BMLoop *l_radial_iter = e->l;
do {
if (l_radial_iter->v == v) {
/* Shouldn't be exceeded, more of a guard against corrupt topology. */
if (v_loops_num == VERT_DISSOLVE_MAX) [[unlikely]] {
BLI_assert_unreachable();
return v_loops_num;
}
v_loops[v_loops_num++] = l_radial_iter;
}
} while ((l_radial_iter = l_radial_iter->radial_next) != e->l);
}
}
return v_loops_num;
}
static bool bm_vert_dissolve_fan_makes_double_whole_face(BMLoop **v_loops_all,
const int v_loops_all_num)
{
BMEdge *edges_from_loop_ear[VERT_DISSOLVE_MAX];
uint edges_from_loop_ear_num = 0;
for (int i = 0; i < v_loops_all_num; i++) {
BMLoop *l = v_loops_all[i];
BMEdge *e_opposite;
if (l->f->len > 3) {
e_opposite = BM_edge_exists(l->prev->v, l->next->v);
if (e_opposite == nullptr) {
return false;
}
if (edges_from_loop_ear_num == VERT_DISSOLVE_MAX) {
return false;
}
}
else {
e_opposite = l->next->e;
}
edges_from_loop_ear[edges_from_loop_ear_num++] = e_opposite;
}
if (edges_from_loop_ear_num >= 3) {
if (BM_face_exists_multi_edge(edges_from_loop_ear, edges_from_loop_ear_num)) {
return true;
}
}
return false;
}
/**
* Check if splitting all triangle "ears", then dissolving the vertex would create duplicate faces.
* Only possible duplicates with *existing* faces are accounted for.
* See #bm_vert_dissolve_fan_makes_double_with_self which accounts for the creation of duplicates.
*
* \param v_loops_all: One loop per face around the vertex (each `l->v` is that vertex),
* as gathered for triangulation.
* \return true if the dissolve would create a duplicate face.
*/
static bool bm_vert_dissolve_fan_makes_double_with_existing(BMLoop **v_loops_all,
const int v_loops_all_num)
{
for (int i = 0; i < v_loops_all_num; i++) {
BMLoop *l = v_loops_all[i];
if (l->f->len > 3) {
if (BM_face_split_check_double_face(l->prev, l->next, 3)) {
return true;
}
}
}
return false;
}
/**
* Check if splitting all triangle "ears", then dissolving the vertex would create duplicate faces.
*
* Note that this is a little more specialized than it may seem because
* we already check if splitting a face would create a duplicate face: *this* checks
* the faces connected to the vertex don't create a duplicate against *each other*.
*
* The topology that does this ends up being fairly messy in practice - but legal.
* The simplest case is 4 vertices storing 3 quads (one typical quad, and 2x bow-tie quads),
* in this case collapsing one vertex would create 3x overlapping triangles.
*
* \param v_loops_all: One loop per face around the vertex (each `l->v` is that vertex),
* as gathered for triangulation.
* \return true if the dissolve would create a duplicate face.
*/
static bool bm_vert_dissolve_fan_makes_double_with_self(BMLoop **v_loops_all,
const int v_loops_all_num)
{
BLI_assert(v_loops_all_num <= VERT_DISSOLVE_MAX);
if (v_loops_all_num < 2) {
return false;
}
BMLoop *v_loops[VERT_DISSOLVE_MAX];
int v_loops_num = 0;
for (int i = 0; i < v_loops_all_num; i++) {
BLI_assert(v_loops_all[i]->v == v_loops_all[0]->v);
BMLoop *l = v_loops_all[i];
if (l->f->len > 3) {
v_loops[v_loops_num++] = l;
}
}
if (v_loops_num < 2) {
return false;
}
std::ranges::sort(v_loops, v_loops + v_loops_num, {}, [](const BMLoop *l) { return l->f->len; });
using VertArray = Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE>;
/* Return the vertices left forming a face when the central vertex is collapsed out,
* canonicalized so two faces that collapse to the same loop compare equal. */
auto verts_canonical_collapsed_fn = [](const BMLoop *l_vert) -> VertArray {
const int verts_num = l_vert->f->len - 1;
VertArray verts(verts_num, NoInitialization());
int verts_index = 0;
for (const BMLoop *l = l_vert->next; l != l_vert; l = l->next) {
verts[verts_index++] = l->v;
}
BLI_assert(verts_index == verts_num);
/* Canonicalize using the minimum pointer, with the order
* defined by the next adjacent minimum. */
int i_min = 0;
for (int i = 1; i < verts_num; i++) {
if (verts[i] < verts[i_min]) {
i_min = i;
}
}
const int i_next = (i_min + 1 == verts_num) ? 0 : i_min + 1;
const int i_prev = (i_min == 0) ? verts_num - 1 : i_min - 1;
/* A step of `verts_num - 1` moves one place backward & wraps. */
const int step = (verts[i_next] < verts[i_prev]) ? 1 : (verts_num - 1);
VertArray verts_ordered(verts_num, NoInitialization());
for (int i = 0, j = i_min; i < verts_num; i++) {
verts_ordered[i] = verts[j];
j += step;
if (j >= verts_num) {
j -= verts_num;
}
}
return verts_ordered;
};
/* Only perform the relatively expensive unique face checks for
* groups of faces with matching numbers of sides. */
for (int group_beg = 0; group_beg < v_loops_num;) {
const int group_f_len = v_loops[group_beg]->f->len;
int group_end = group_beg + 1;
while (group_end < v_loops_num && v_loops[group_end]->f->len == group_f_len) {
group_end++;
}
const int group_len = group_end - group_beg;
if (group_len >= 2) {
VertArray collapsed[VERT_DISSOLVE_MAX];
for (int i = 0; i < group_len; i++) {
collapsed[i] = verts_canonical_collapsed_fn(v_loops[group_beg + i]);
}
for (int i = 0; i + 1 < group_len; i++) {
for (int j = i + 1; j < group_len; j++) {
if (std::ranges::equal(collapsed[i], collapsed[j])) {
return true;
}
}
}
}
group_beg = group_end;
}
return false;
}
/**
* Detect if `v` can be dissolved as a fan or a chain based on its topology.
*
* \param check_for_duplicates: When true, extensive tests are performed that ensure
* dissolving won't create duplicate faces.
* Otherwise perform a simple topology check.
* Avoid the expensive check on the initial pass to mark vertices as candidates to dissolve
* because it's slow and the exact topology will change as vertices begin to dissolve.
* \param v_loops, v_loops_num_p: Surrounding loops, set when `check_for_duplicates`
* is true and the return value isn't `nullopt`.
*
* \note It's important this *only* dissolves `v`, otherwise
* the main dissolve loop may iterate over freed vertices.
* Enabling `check_for_duplicates` ensures this.
*/
static std::optional<VertDissolveMethod> bm_vert_dissolve_fan_or_chain_test(
BMVert *v,
const bool check_for_duplicates,
BMLoop *v_loops[VERT_DISSOLVE_MAX],
int *v_loops_num_p)
{
BMIter iter;
BMEdge *e;
/* All edges connected to `v`. */
BMEdge *v_edges[VERT_DISSOLVE_MAX];
/* Boundary edges, only check for 2 because cases with more than 2 are ignored. */
BMEdge *e_boundary[2];
uint e_boundary_num = 0;
uint tot_edge = 0;
uint tot_edge_boundary = 0;
uint tot_edge_manifold = 0;
uint tot_edge_wire = 0;
/* In this loop we try to bail out early and gather data to be used later.
* Avoid expensive queries up front. */
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
if (e_boundary_num < ARRAY_SIZE(e_boundary)) {
e_boundary[e_boundary_num] = e;
}
e_boundary_num++;
tot_edge_boundary++;
}
else if (BM_edge_is_manifold(e)) {
tot_edge_manifold++;
}
else if (BM_edge_is_wire(e)) {
tot_edge_wire++;
}
else {
return std::nullopt;
}
if (tot_edge == VERT_DISSOLVE_MAX) {
return std::nullopt;
}
v_edges[tot_edge] = e;
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)))
{
if (check_for_duplicates) {
const int v_loops_num = bm_vert_loops_from_vert_edges(v, v_edges, tot_edge, v_loops);
/* Check the corners around `v` don't already form a face. */
if (bm_vert_dissolve_fan_makes_double_whole_face(v_loops, v_loops_num)) {
return std::nullopt;
}
/* Check if splits would create a duplicate (with existing or between each other). */
if (bm_vert_dissolve_fan_makes_double_with_existing(v_loops, v_loops_num) ||
bm_vert_dissolve_fan_makes_double_with_self(v_loops, v_loops_num))
{
return std::nullopt;
}
*v_loops_num_p = v_loops_num;
}
return VertDissolveMethod::InteriorFan;
}
if ((tot_edge == 3) && (tot_edge_boundary == 2) && (tot_edge_manifold == 1)) {
if (check_for_duplicates) {
BLI_assert(e_boundary_num == 2);
if (const BMEdge *e_span = BM_edge_exists(BM_edge_other_vert(e_boundary[0], v),
BM_edge_other_vert(e_boundary[1], v)))
{
if (e_span->l) {
/* Boundary dissolving connects the two boundary edges into `e_span`.
* When `e_span` exists with a face, the dissolve may join across it.
* In isolation that is "ok" - however it means the dissolve may remove vertices
* other than `v`, causing the main un-subdivide loop to operate on freed geometry.
*
* NOTE(@ideasman42): If this was an important use case it's possible to inspect the
* topology and only reject dissolving when it would cause problems.
* However the situations where this occurs are not so important to support
* as they tend to be caused by overlapping/bow-tie faces.
* So it's simpler to reject them. */
return std::nullopt;
}
}
const int v_loops_num = bm_vert_loops_from_vert_edges(v, v_edges, tot_edge, v_loops);
/* Check if splits would create a duplicate (with existing or between each other). */
if (bm_vert_dissolve_fan_makes_double_with_existing(v_loops, v_loops_num) ||
bm_vert_dissolve_fan_makes_double_with_self(v_loops, v_loops_num))
{
return std::nullopt;
}
*v_loops_num_p = v_loops_num;
}
return VertDissolveMethod::BoundaryFan;
}
if ((tot_edge == 2) && (tot_edge_wire == 2)) {
return VertDissolveMethod::WireChain;
}
if ((tot_edge == 2) && (tot_edge_manifold == 2)) {
return VertDissolveMethod::InteriorChain;
}
return std::nullopt;
}
/**
* Dissolve `v` (and only `v`).
*/
static bool bm_vert_dissolve_fan_or_chain(BMesh *bm, BMVert *v)
{
/* Re-test if dissolve is possible.
* The surrounding geometry may have changed since the first check,
* the first call also skips expensive degeneracy checks for this very reason. */
BMLoop *v_loops[VERT_DISSOLVE_MAX];
int v_loops_num = 0;
const std::optional<VertDissolveMethod> dissolve = bm_vert_dissolve_fan_or_chain_test(
v, true, v_loops, &v_loops_num);
if (!dissolve) {
return false;
}
switch (*dissolve) {
case VertDissolveMethod::WireChain:
case VertDissolveMethod::InteriorChain: {
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
case VertDissolveMethod::BoundaryFan:
case VertDissolveMethod::InteriorFan: {
for (int i = 0; i < v_loops_num; i++) {
BMLoop *l = v_loops[i];
if (l->f->len > 3) {
BMLoop *l_new;
BLI_assert(l->prev->v != l->next->v);
/* Must have been rejected by #bm_vert_dissolve_fan_or_chain_test.
* NOTE(@ideasman42): Assert because it's theoretically possible an edit
* in this loop creates a duplicate (although incredibly unlikely). */
BLI_assert(!BM_face_split_check_double_face(l->prev, l->next, 3));
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;
}
/**
* Note that #bm_tag_untagged_neighbors requires #VERT_INDEX_DO_COLLAPSE & #VERT_INDEX_IGNORE
* are equal magnitude, opposite sign.
*/
enum {
VERT_INDEX_DO_COLLAPSE = -1,
VERT_INDEX_INIT = 0,
VERT_INDEX_IGNORE = 1,
};
/**
* Given a set of starting verts, find all the currently-untagged neighbors of those verts, tag
* them with the specified value, and return an array specifying all the newly-tagged verts.
*
* By using two arrays and two tag values, repeated alternating calls will expand the selection in
* an alternating tagging pattern. Dissolving one of the two tags will then reduce the density of
* the mesh, by half, in a regular diamond pattern.
*
* \param verts_start: The array of starting verts whose neighbors should be tagged.
* \param verts_start_num: The number of verts in the verts_start array.
* \param desired_tag: The value to set as a tag, on any currently-untagged neighbors.
* \param r_verts_tagged: Returned array of all the verts which were tagged in this call.
* \param r_verts_tagged_num: Returned number of verts in the r_verts_tagged array.
*/
static void bm_tag_untagged_neighbors(BMVert *verts_start[],
const uint verts_start_num,
const int desired_tag,
BMVert *r_verts_tagged[],
uint &r_verts_tagged_num)
{
BMEdge *e;
BMIter iter;
r_verts_tagged_num = 0;
for (int i = 0; i < verts_start_num; i++) {
BMVert *v = verts_start[i];
/* Since DO_COLLAPSE and IGNORE are -1 and +1, inverting the sign finds the other. */
BLI_assert(BM_elem_index_get(v) == -desired_tag);
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, desired_tag); /* set_dirty! */
r_verts_tagged[r_verts_tagged_num++] = v_other;
}
}
}
}
/* - 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)
{
/* NOTE: while #BMWalker seems like a logical choice, it results in uneven geometry. */
BMVert **verts_collapse = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
BMVert **verts_ignore = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
uint verts_collapse_num = 0;
uint verts_ignore_num = 0;
BMIter iter;
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);
}
}
/* Perform the number of iteration steps which the user requested. */
for (iter_step = 0; iter_step < iterations; iter_step++) {
BMVert *v, *v_next;
bool verts_were_marked_for_dissolve = false;
/* Tag all verts which are eligible to be dissolved on this iteration. */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, BM_ELEM_TAG) &&
bm_vert_dissolve_fan_or_chain_test(v, false, nullptr, nullptr))
{
BM_elem_index_set(v, VERT_INDEX_INIT); /* set_dirty! */
}
else {
BM_elem_index_set(v, VERT_INDEX_IGNORE); /* set_dirty! */
}
}
/* main loop, keep tagging until we can't tag any more islands */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
/* Only process verts which are eligible for dissolve and which have not yet been tagged. */
if (!(BM_elem_index_get(v) == VERT_INDEX_INIT)) {
continue;
}
/* Set the first #VERT_INDEX_INIT vert as the first starting vert. */
BM_elem_index_set(v, VERT_INDEX_IGNORE); /* set_dirty! */
verts_ignore[0] = v;
verts_ignore_num = 1;
/* Starting at v, expand outwards, tagging any currently untagged neighbors.
* verts will be alternately tagged for collapse or ignore.
* Stop when there are no neighbors left to expand to. */
while (true) {
bm_tag_untagged_neighbors(verts_ignore,
verts_ignore_num,
VERT_INDEX_DO_COLLAPSE, /* set_dirty! */
verts_collapse,
verts_collapse_num);
if (verts_collapse_num == 0) {
break;
}
verts_were_marked_for_dissolve = true;
bm_tag_untagged_neighbors(verts_collapse,
verts_collapse_num,
VERT_INDEX_IGNORE, /* set_dirty! */
verts_ignore,
verts_ignore_num);
if (verts_ignore_num == 0) {
break;
}
}
}
/* At high iteration levels, later steps can run out of verts that are eligible for dissolve.
* If this occurs, stop. Future iterations won't find any verts that this iteration didn't. */
if (!verts_were_marked_for_dissolve) {
break;
}
/* Remove all verts tagged for removal. */
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_index_get(v) == VERT_INDEX_DO_COLLAPSE) {
bm_vert_dissolve_fan_or_chain(bm, v);
}
}
}
/* Ensure the vert index values will be recomputed. */
bm->elem_index_dirty |= BM_VERT;
MEM_delete(verts_collapse);
MEM_delete(verts_ignore);
}
void BM_mesh_decimate_unsubdivide(BMesh *bm, const int iterations)
{
BM_mesh_decimate_unsubdivide_ex(bm, iterations, false);
}
} // namespace blender

View File

@@ -0,0 +1,482 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Edge-net Fill.
*/
#include <climits>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_linklist.h"
#include "BLI_mempool.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "bmesh_edgenet.hh" /* own include */
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
/* Struct for storing a path of verts walked over */
struct VertNetInfo {
BMVert *prev; /* previous vertex */
int pass; /* path scanning pass value, for internal calculation */
int face; /* face index connected to the edge between this and the previous vert */
int flag; /* flag */
};
enum {
VNINFO_FLAG_IS_MIXFACE = (1 << 0),
};
/**
* Check if this edge can be used in a path.
*/
static bool bm_edge_step_ok(BMEdge *e)
{
return BM_elem_flag_test(e, BM_ELEM_TAG) && ELEM(e->l, nullptr, e->l->radial_next);
}
static int bm_edge_face(BMEdge *e)
{
return e->l ? BM_elem_index_get(e->l->f) : -1;
}
/**
* Get the next available edge we can use to attempt to calculate a path from.
*/
static BMEdge *bm_edgenet_edge_get_next(BMesh *bm,
LinkNode **edge_queue,
BLI_mempool *edge_queue_pool)
{
BMEdge *e;
BMIter iter;
while (*edge_queue) {
e = static_cast<BMEdge *>(BLI_linklist_pop_pool(edge_queue, edge_queue_pool));
if (bm_edge_step_ok(e)) {
return e;
}
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (bm_edge_step_ok(e)) {
return e;
}
}
return nullptr;
}
/**
* Edge loops are built up using links to the 'prev' member.
* with each side of the loop having its own pass (negated from the other).
*
* This function returns half a loop, the caller needs to run twice to get both sides.
*/
static uint bm_edgenet_path_from_pass(BMVert *v,
LinkNode **v_ls,
VertNetInfo *vnet_info,
BLI_mempool *path_pool)
{
VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
const int pass = vn->pass;
uint v_ls_tot = 0;
do {
BLI_linklist_prepend_pool(v_ls, v, path_pool);
v_ls_tot += 1;
v = vn->prev;
vn = &vnet_info[BM_elem_index_get(v)];
} while (vn->pass == pass);
return v_ls_tot;
}
/**
* Specialized wrapper for #BM_face_exists_overlap_subset
* that gets the verts from a path before we allocate it in the correct order.
*/
static bool bm_edgenet_path_check_overlap(BMVert *v1, BMVert *v2, VertNetInfo *vnet_info)
{
/* vert order doesn't matter */
uint v_ls_tot = 0;
LinkNode *v_ls = nullptr;
BMVert *v_pair[2] = {v1, v2};
uint i;
for (i = 0; i < 2; i++) {
BMVert *v = v_pair[i];
VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
const int pass = vn->pass;
do {
BLI_linklist_prepend_alloca(&v_ls, v);
v_ls_tot += 1;
v = vn->prev;
vn = &vnet_info[BM_elem_index_get(v)];
} while (vn->pass == pass);
}
if (v_ls_tot) {
Array<BMVert *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> vert_arr(v_ls_tot);
LinkNode *v_lnk;
for (i = 0, v_lnk = v_ls; i < v_ls_tot; v_lnk = v_lnk->next, i++) {
vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
}
return BM_face_exists_overlap_subset(vert_arr.data(), int(v_ls_tot));
}
return false;
}
/**
* Create a face from the path.
*/
static BMFace *bm_edgenet_face_from_path(BMesh *bm, LinkNode *path, const uint path_len)
{
BMFace *f;
LinkNode *v_lnk;
int i;
bool ok;
Array<BMVert *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> vert_arr(path_len);
Array<BMEdge *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> edge_arr(path_len);
for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
}
ok = BM_edges_from_verts(edge_arr.data(), vert_arr.data(), i);
BLI_assert(ok);
UNUSED_VARS_NDEBUG(ok);
/* no need for this, we do overlap checks before allowing the path to be used */
#if 0
if (BM_face_exists_multi(vert_arr.data(), edge_arr.data(), path_len)) {
return nullptr;
}
#endif
f = BM_face_create(bm, vert_arr.data(), edge_arr.data(), int(path_len), nullptr, BM_CREATE_NOP);
return f;
}
/**
* Step along the path from \a v_curr to any vert not already in the path.
*
* \return The connecting edge if the path is found, otherwise nullptr.
*/
static BMEdge *bm_edgenet_path_step(BMVert *v_curr,
LinkNode **v_ls,
VertNetInfo *vnet_info,
BLI_mempool *path_pool)
{
const VertNetInfo *vn_curr;
BMEdge *e;
BMIter iter;
uint tot;
uint v_ls_tot;
begin:
tot = 0;
v_ls_tot = 0;
vn_curr = &vnet_info[BM_elem_index_get(v_curr)];
BM_ITER_ELEM (e, &iter, v_curr, BM_EDGES_OF_VERT) {
BMVert *v_next = BM_edge_other_vert(e, v_curr);
if (v_next != vn_curr->prev) {
if (bm_edge_step_ok(e)) {
VertNetInfo *vn_next = &vnet_info[BM_elem_index_get(v_next)];
/* check we're not looping back on ourselves */
if (vn_curr->pass != vn_next->pass) {
if (vn_curr->pass == -vn_next->pass) {
if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) ||
(vn_next->flag & VNINFO_FLAG_IS_MIXFACE))
{
/* found connecting edge */
if (bm_edgenet_path_check_overlap(v_curr, v_next, vnet_info) == false) {
return e;
}
}
}
else {
vn_next->face = bm_edge_face(e);
vn_next->pass = vn_curr->pass;
vn_next->prev = v_curr;
/* flush flag down the path */
vn_next->flag &= ~VNINFO_FLAG_IS_MIXFACE;
if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) || (vn_next->face == -1) ||
(vn_next->face != vn_curr->face))
{
vn_next->flag |= VNINFO_FLAG_IS_MIXFACE;
}
/* add to the list! */
BLI_linklist_prepend_pool(v_ls, v_next, path_pool);
v_ls_tot += 1;
}
}
}
tot += 1;
}
}
/* trick to walk along wire-edge paths */
if (v_ls_tot == 1 && tot == 1) {
v_curr = static_cast<BMVert *>(BLI_linklist_pop_pool(v_ls, path_pool));
/* avoid recursion, can crash on very large nets */
#if 0
bm_edgenet_path_step(v_curr, v_ls, vnet_info, path_pool);
#else
goto begin;
#endif
}
return nullptr;
}
/**
* Given an edge, find the first path that can form a face.
*
* \return A linked list of verts.
*/
static LinkNode *bm_edgenet_path_calc(BMEdge *e,
const int pass_nr,
const uint path_cost_max,
uint *r_path_len,
uint *r_path_cost,
VertNetInfo *vnet_info,
BLI_mempool *path_pool)
{
VertNetInfo *vn_1, *vn_2;
const int f_index = bm_edge_face(e);
bool found;
LinkNode *v_ls_prev = nullptr;
LinkNode *v_ls_next = nullptr;
uint path_cost_accum = 0;
BLI_assert(bm_edge_step_ok(e));
*r_path_len = 0;
*r_path_cost = 0;
vn_1 = &vnet_info[BM_elem_index_get(e->v1)];
vn_2 = &vnet_info[BM_elem_index_get(e->v2)];
vn_1->pass = pass_nr;
vn_2->pass = -pass_nr;
vn_1->prev = e->v2;
vn_2->prev = e->v1;
vn_1->face = vn_2->face = f_index;
vn_1->flag = vn_2->flag = (f_index == -1) ? VNINFO_FLAG_IS_MIXFACE : 0;
/* Prime the search-list. */
BLI_linklist_prepend_pool(&v_ls_prev, e->v1, path_pool);
BLI_linklist_prepend_pool(&v_ls_prev, e->v2, path_pool);
do {
found = false;
/* no point to continue, we're over budget */
if (path_cost_accum >= path_cost_max) {
BLI_linklist_free_pool(v_ls_next, nullptr, path_pool);
BLI_linklist_free_pool(v_ls_prev, nullptr, path_pool);
return nullptr;
}
while (v_ls_prev) {
const LinkNode *v_ls_next_old = v_ls_next;
BMVert *v = static_cast<BMVert *>(BLI_linklist_pop_pool(&v_ls_prev, path_pool));
BMEdge *e_found = bm_edgenet_path_step(v, &v_ls_next, vnet_info, path_pool);
if (e_found) {
LinkNode *path = nullptr;
uint path_len;
BLI_linklist_free_pool(v_ls_next, nullptr, path_pool);
BLI_linklist_free_pool(v_ls_prev, nullptr, path_pool);
// BLI_assert(BLI_mempool_len(path_pool) == 0);
path_len = bm_edgenet_path_from_pass(e_found->v1, &path, vnet_info, path_pool);
BLI_linklist_reverse(&path);
path_len += bm_edgenet_path_from_pass(e_found->v2, &path, vnet_info, path_pool);
*r_path_len = path_len;
*r_path_cost = path_cost_accum;
return path;
}
/* check if a change was made */
if (v_ls_next_old != v_ls_next) {
found = true;
}
}
BLI_assert(v_ls_prev == nullptr);
path_cost_accum++;
/* swap */
v_ls_prev = v_ls_next;
v_ls_next = nullptr;
} while (found);
BLI_assert(v_ls_prev == nullptr);
BLI_assert(v_ls_next == nullptr);
/* tag not to search again */
BM_elem_flag_disable(e, BM_ELEM_TAG);
return nullptr;
}
/**
* Wrapper for #bm_edgenet_path_calc which ensures all included edges
* _don't_ have a better option.
*/
static LinkNode *bm_edgenet_path_calc_best(BMEdge *e,
int *pass_nr,
uint path_cost_max,
uint *r_path_len,
uint *r_path_cost,
VertNetInfo *vnet_info,
BLI_mempool *path_pool)
{
LinkNode *path;
uint path_cost;
path = bm_edgenet_path_calc(
e, *pass_nr, path_cost_max, r_path_len, &path_cost, vnet_info, path_pool);
(*pass_nr)++;
if (path == nullptr) {
return nullptr;
}
if (path_cost < 1) {
/* any face that takes 1 iteration to find we consider valid */
return path;
}
/* Check every edge to see if any can give a better path.
* This avoids very strange/long paths from being created. */
const uint path_len = *r_path_len;
uint i, i_prev;
Array<BMVert *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> vert_arr(path_len);
LinkNode *v_lnk;
for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
vert_arr[i] = static_cast<BMVert *>(v_lnk->link);
}
i_prev = path_len - 1;
for (i = 0; i < path_len; i++) {
BMEdge *e_other = BM_edge_exists(vert_arr[i], vert_arr[i_prev]);
if (e_other != e) {
LinkNode *path_test;
uint path_len_test;
uint path_cost_test;
path_test = bm_edgenet_path_calc(
e_other, *pass_nr, path_cost, &path_len_test, &path_cost_test, vnet_info, path_pool);
(*pass_nr)++;
if (path_test) {
BLI_assert(path_cost_test < path_cost);
BLI_linklist_free_pool(path, nullptr, path_pool);
path = path_test;
*r_path_len = path_len_test;
*r_path_cost = path_cost_test;
path_cost = path_cost_test;
}
}
i_prev = i;
}
return path;
}
void BM_mesh_edgenet(BMesh *bm, const bool use_edge_tag, const bool use_new_face_tag)
{
VertNetInfo *vnet_info = MEM_new_array_zeroed<VertNetInfo>(size_t(bm->totvert), __func__);
BLI_mempool *edge_queue_pool = BLI_mempool_create(sizeof(LinkNode), 0, 512, BLI_MEMPOOL_NOP);
BLI_mempool *path_pool = BLI_mempool_create(sizeof(LinkNode), 0, 512, BLI_MEMPOOL_NOP);
LinkNode *edge_queue = nullptr;
BMEdge *e;
BMIter iter;
int pass_nr = 1;
if (use_edge_tag == false) {
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_set(e, BM_ELEM_TAG, bm_edge_step_ok(e));
}
}
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
while (true) {
LinkNode *path = nullptr;
uint path_len;
uint path_cost;
e = bm_edgenet_edge_get_next(bm, &edge_queue, edge_queue_pool);
if (e == nullptr) {
break;
}
BLI_assert(bm_edge_step_ok(e) == true);
path = bm_edgenet_path_calc_best(
e, &pass_nr, UINT_MAX, &path_len, &path_cost, vnet_info, path_pool);
if (path) {
BMFace *f = bm_edgenet_face_from_path(bm, path, path_len);
/* queue edges to operate on */
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (bm_edge_step_ok(l_iter->e)) {
BLI_linklist_prepend_pool(&edge_queue, l_iter->e, edge_queue_pool);
}
} while ((l_iter = l_iter->next) != l_first);
if (use_new_face_tag) {
BM_elem_flag_enable(f, BM_ELEM_TAG);
}
/* the face index only needs to be unique, not kept valid */
BM_elem_index_set(f, bm->totface - 1); /* set_dirty */
}
BLI_linklist_free_pool(path, nullptr, path_pool);
BLI_assert(BLI_mempool_len(path_pool) == 0);
}
bm->elem_index_dirty |= BM_FACE | BM_LOOP;
BLI_mempool_destroy(edge_queue_pool);
BLI_mempool_destroy(path_pool);
MEM_delete(vnet_info);
}
} // namespace blender

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Fill in faces from an edgenet made up of boundary and wire edges.
*
* \note New faces currently don't have their normals calculated and are flipped randomly.
* The caller needs to flip faces correctly.
*
* \param bm: The mesh to operate on.
* \param use_edge_tag: Only fill tagged edges.
*/
void BM_mesh_edgenet(BMesh *bm, bool use_edge_tag, bool use_new_face_tag);
} // namespace blender

View File

@@ -0,0 +1,120 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Edge-Split.
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "bmesh_edgesplit.hh" /* own include */
namespace blender {
void BM_mesh_edgesplit(BMesh *bm,
const bool use_verts,
const bool tag_only,
const bool copy_select)
{
BMIter iter;
BMEdge *e;
bool use_ese = false;
Map<BMElem *, BMEditSelection *> ese_gh;
if (copy_select && bm->selected.first) {
for (BMEditSelection &ese : bm->selected) {
if (ese.htype != BM_FACE) {
ese_gh.add(ese.ele, &ese);
}
}
use_ese = true;
}
if (tag_only == false) {
BM_mesh_elem_hflag_enable_all(bm, BM_EDGE | (use_verts ? BM_VERT : 0), BM_ELEM_TAG, false);
}
if (use_verts) {
/* prevent one edge having both verts unflagged
* we could alternately disable these edges, either way its a corner case.
*
* This is needed so we don't split off the edge but then none of its verts which
* would leave a duplicate edge.
*/
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
if (UNLIKELY((BM_elem_flag_test(e->v1, BM_ELEM_TAG) == false) &&
(BM_elem_flag_test(e->v2, BM_ELEM_TAG) == false)))
{
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
}
}
}
}
else {
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
}
}
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
uint i;
for (i = 0; i < 2; i++) {
BMVert *v = ((&e->v1)[i]);
if (BM_elem_flag_test(v, BM_ELEM_TAG)) {
BM_elem_flag_disable(v, BM_ELEM_TAG);
if (use_ese) {
BMVert **vtar;
int vtar_len;
BM_vert_separate_hflag(bm, v, BM_ELEM_TAG, copy_select, &vtar, &vtar_len);
/* first value is always in 'v' */
if (vtar_len > 1) {
BMEditSelection *ese = ese_gh.lookup_default(reinterpret_cast<BMElem *>(v), nullptr);
BLI_assert(v == vtar[0]);
if (UNLIKELY(ese)) {
int j;
for (j = 1; j < vtar_len; j++) {
BLI_assert(v != vtar[j]);
BM_select_history_store_after_notest(bm, ese, vtar[j]);
}
}
}
MEM_delete(vtar);
}
else {
BM_vert_separate_hflag(bm, v, BM_ELEM_TAG, copy_select, nullptr, nullptr);
}
}
}
}
}
#ifndef NDEBUG
/* ensure we don't have any double edges! */
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
BLI_assert(BM_edge_find_double(e) == nullptr);
}
}
#endif
}
} // namespace blender

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* \param use_verts: Use flagged verts instead of edges.
* \param tag_only: Only split tagged edges.
* \param copy_select: Copy selection history.
*/
void BM_mesh_edgesplit(BMesh *bm, bool use_verts, bool tag_only, bool copy_select);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_span.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* Intersect tessellated faces
* leaving the resulting edges tagged.
*
* \param test_fn: Return value: -1: skip, 0: tree_a, 1: tree_b (use_self == false)
* \param boolean_mode: -1: no-boolean, 0: intersection... see #BMESH_ISECT_BOOLEAN_ISECT.
* \return true if the mesh is changed (intersections cut or faces removed from boolean).
*/
bool BM_mesh_intersect(BMesh *bm,
Span<std::array<BMLoop *, 3>> looptris,
int (*test_fn)(BMFace *f, void *user_data),
void *user_data,
bool use_self,
bool use_separate,
bool use_dissolve,
bool use_island_connect,
bool use_partial_connect,
bool use_edge_tag,
int boolean_mode,
float eps);
enum {
BMESH_ISECT_BOOLEAN_NONE = -1,
/* aligned with BooleanModifierOp */
BMESH_ISECT_BOOLEAN_ISECT = 0,
BMESH_ISECT_BOOLEAN_UNION = 1,
BMESH_ISECT_BOOLEAN_DIFFERENCE = 2,
};
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#pragma once
#include "BLI_ghash.h"
#include "bmesh_class.hh"
namespace blender {
bool BM_mesh_intersect_edges(
BMesh *bm, char hflag, float dist, bool split_faces, GHash *r_targetmap);
} // namespace blender

View File

@@ -0,0 +1,596 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Find a path between 2 elements.
*
* \note All 3 functions are similar, changes to one most likely apply to another.
*/
#include "MEM_guardedalloc.h"
#include <algorithm>
#include "BLI_heap_simple.h"
#include "BLI_linklist.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "bmesh_path.hh" /* own include */
namespace blender {
#define COST_INIT_MAX FLT_MAX
/* -------------------------------------------------------------------- */
/** \name Generic Helpers
* \{ */
/**
* Use skip options when we want to start measuring from a boundary.
*/
static float step_cost_3_v3_ex(
const float v1[3], const float v2[3], const float v3[3], bool skip_12, bool skip_23)
{
float d1[3], d2[3];
/* The cost is based on the simple sum of the length of the two edges. */
sub_v3_v3v3(d1, v2, v1);
sub_v3_v3v3(d2, v3, v2);
const float cost_12 = normalize_v3(d1);
const float cost_23 = normalize_v3(d2);
const float cost = ((skip_12 ? 0.0f : cost_12) + (skip_23 ? 0.0f : cost_23));
/* But is biased to give higher values to sharp turns, so that it will take paths with
* fewer "turns" when selecting between equal-weighted paths between the two edges. */
return cost * (1.0f + 0.5f * (2.0f - sqrtf(fabsf(dot_v3v3(d1, d2)))));
}
static float step_cost_3_v3(const float v1[3], const float v2[3], const float v3[3])
{
return step_cost_3_v3_ex(v1, v2, v3, false, false);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_vert
* \{ */
static void verttag_add_adjacent(HeapSimple *heap,
BMVert *v_a,
BMVert **verts_prev,
float *cost,
const BMCalcPathParams *params)
{
const int v_a_index = BM_elem_index_get(v_a);
{
BMIter eiter;
BMEdge *e;
/* Loop over faces of face, but do so by first looping over loops. */
BM_ITER_ELEM (e, &eiter, v_a, BM_EDGES_OF_VERT) {
BMVert *v_b = BM_edge_other_vert(e, v_a);
if (!BM_elem_flag_test(v_b, BM_ELEM_TAG)) {
/* We know 'v_b' is not visited, check it out! */
const int v_b_index = BM_elem_index_get(v_b);
const float cost_cut = params->use_topology_distance ? 1.0f : len_v3v3(v_a->co, v_b->co);
const float cost_new = cost[v_a_index] + cost_cut;
if (cost[v_b_index] > cost_new) {
cost[v_b_index] = cost_new;
verts_prev[v_b_index] = v_a;
BLI_heapsimple_insert(heap, cost_new, v_b);
}
}
}
}
if (params->use_step_face) {
BMIter liter;
BMLoop *l;
/* Loop over faces of face, but do so by first looping over loops. */
BM_ITER_ELEM (l, &liter, v_a, BM_LOOPS_OF_VERT) {
if (l->f->len > 3) {
/* Skip loops on adjacent edges. */
BMLoop *l_iter = l->next->next;
do {
BMVert *v_b = l_iter->v;
if (!BM_elem_flag_test(v_b, BM_ELEM_TAG)) {
/* We know 'v_b' is not visited, check it out! */
const int v_b_index = BM_elem_index_get(v_b);
const float cost_cut = params->use_topology_distance ? 1.0f :
len_v3v3(v_a->co, v_b->co);
const float cost_new = cost[v_a_index] + cost_cut;
if (cost[v_b_index] > cost_new) {
cost[v_b_index] = cost_new;
verts_prev[v_b_index] = v_a;
BLI_heapsimple_insert(heap, cost_new, v_b);
}
}
} while ((l_iter = l_iter->next) != l->prev);
}
}
}
}
LinkNode *BM_mesh_calc_path_vert(BMesh *bm,
BMVert *v_src,
BMVert *v_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMVert *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* #BM_ELEM_TAG flag is used to store visited edges. */
BMVert *v;
BMIter viter;
HeapSimple *heap;
float *cost;
BMVert **verts_prev;
int i, totvert;
/* NOTE: would pass #BM_EDGE except we are looping over all faces anyway. */
// BM_mesh_elem_index_ensure(bm, BM_VERT /* | BM_EDGE */); // NOT NEEDED FOR FACETAG
BM_ITER_MESH_INDEX (v, &viter, bm, BM_VERTS_OF_MESH, i) {
BM_elem_flag_set(v, BM_ELEM_TAG, !filter_fn(v, user_data));
BM_elem_index_set(v, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_VERT;
/* Allocate. */
totvert = bm->totvert;
verts_prev = MEM_new_array_zeroed<BMVert *>(totvert, __func__);
cost = MEM_new_array_uninitialized<float>(totvert, __func__);
std::fill_n(cost, totvert, COST_INIT_MAX);
/*
* Arrays are now filled as follows:
*
* As the search continues, `verts_prev[n]` will be the previous verts on the shortest
* path found so far to face `n`. #BM_ELEM_TAG is used to tag elements we have visited,
* `cost[n]` will contain the length of the shortest
* path to face n found so far, Finally, heap is a priority heap which is built on the
* the same data as the cost array, but inverted: it is a work-list of faces prioritized
* by the shortest path found so far to the face.
*/
/* Regular dijkstra shortest path, but over faces instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, v_src);
cost[BM_elem_index_get(v_src)] = 0.0f;
while (!BLI_heapsimple_is_empty(heap)) {
v = static_cast<BMVert *>(BLI_heapsimple_pop_min(heap));
if (v == v_dst) {
break;
}
if (!BM_elem_flag_test(v, BM_ELEM_TAG)) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
verttag_add_adjacent(heap, v, verts_prev, cost, params);
}
}
if (v == v_dst) {
do {
BLI_linklist_prepend(&path, v);
} while ((v = verts_prev[BM_elem_index_get(v)]));
}
MEM_delete(verts_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_edge
* \{ */
static float edgetag_cut_cost_vert(BMEdge *e_a, BMEdge *e_b, BMVert *v)
{
BMVert *v1 = BM_edge_other_vert(e_a, v);
BMVert *v2 = BM_edge_other_vert(e_b, v);
return step_cost_3_v3(v1->co, v->co, v2->co);
}
static float edgetag_cut_cost_face(BMEdge *e_a, BMEdge *e_b, BMFace *f)
{
float e_a_cent[3], e_b_cent[3], f_cent[3];
mid_v3_v3v3(e_a_cent, e_a->v1->co, e_a->v1->co);
mid_v3_v3v3(e_b_cent, e_b->v1->co, e_b->v1->co);
BM_face_calc_center_median_weighted(f, f_cent);
return step_cost_3_v3(e_a_cent, e_b_cent, f_cent);
}
static void edgetag_add_adjacent(HeapSimple *heap,
BMEdge *e_a,
BMEdge **edges_prev,
float *cost,
const BMCalcPathParams *params)
{
const int e_a_index = BM_elem_index_get(e_a);
/* Unlike vert/face, stepping faces disables scanning connected edges
* and only steps over faces (selecting a ring of edges instead of a loop). */
if (params->use_step_face == false || e_a->l == nullptr) {
BMIter viter;
BMVert *v;
BMIter eiter;
BMEdge *e_b;
BM_ITER_ELEM (v, &viter, e_a, BM_VERTS_OF_EDGE) {
/* Don't walk over previous vertex. */
if ((edges_prev[e_a_index]) && BM_vert_in_edge(edges_prev[e_a_index], v)) {
continue;
}
BM_ITER_ELEM (e_b, &eiter, v, BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e_b, BM_ELEM_TAG) &&
/* Prevent the path overlapping itself in rare cases, see: #137456. */
!BM_elem_flag_test(BM_edge_other_vert(e_b, v), BM_ELEM_TAG))
{
/* We know 'e_b' is not visited, check it out! */
const int e_b_index = BM_elem_index_get(e_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
edgetag_cut_cost_vert(e_a, e_b, v);
const float cost_new = cost[e_a_index] + cost_cut;
if (cost[e_b_index] > cost_new) {
cost[e_b_index] = cost_new;
edges_prev[e_b_index] = e_a;
BLI_heapsimple_insert(heap, cost_new, e_b);
}
}
}
}
}
else {
BMLoop *l_first, *l_iter;
l_iter = l_first = e_a->l;
do {
BMLoop *l_cycle_iter, *l_cycle_end;
l_cycle_iter = l_iter->next;
l_cycle_end = l_iter;
/* Good, but we need to allow this otherwise paths may fail to connect at all. */
#if 0
if (l_iter->f->len > 3) {
l_cycle_iter = l_cycle_iter->next;
l_cycle_end = l_cycle_end->prev;
}
#endif
do {
BMEdge *e_b = l_cycle_iter->e;
if (!BM_elem_flag_test(e_b, BM_ELEM_TAG)) {
/* We know 'e_b' is not visited, check it out! */
const int e_b_index = BM_elem_index_get(e_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
edgetag_cut_cost_face(e_a, e_b, l_iter->f);
const float cost_new = cost[e_a_index] + cost_cut;
if (cost[e_b_index] > cost_new) {
cost[e_b_index] = cost_new;
edges_prev[e_b_index] = e_a;
BLI_heapsimple_insert(heap, cost_new, e_b);
}
}
} while ((l_cycle_iter = l_cycle_iter->next) != l_cycle_end);
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
LinkNode *BM_mesh_calc_path_edge(BMesh *bm,
BMEdge *e_src,
BMEdge *e_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMEdge *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* #BM_ELEM_TAG flag is used to store visited edges. */
BMIter iter;
HeapSimple *heap;
float *cost;
BMEdge **edges_prev;
int i, totedge;
{
BMVert *v;
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
BM_elem_flag_disable(v, BM_ELEM_TAG);
BM_elem_index_set(v, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_VERT;
}
{
BMEdge *e;
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
BM_elem_flag_set(e, BM_ELEM_TAG, !filter_fn(e, user_data));
BM_elem_index_set(e, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_EDGE;
}
/* Allocate. */
totedge = bm->totedge;
edges_prev = MEM_new_array_zeroed<BMEdge *>(totedge, __func__);
cost = MEM_new_array_uninitialized<float>(totedge, __func__);
std::fill_n(cost, totedge, COST_INIT_MAX);
/*
* Arrays are now filled as follows:
*
* As the search continues, `edges_prev[n]` will be the previous edge on the shortest
* path found so far to edge `n`. #BM_ELEM_TAG is used to tag elements we have visited,
* `cost[n]` will contain the length of the shortest
* path to edge n found so far, Finally, heap is a priority heap which is built on the
* the same data as the cost array, but inverted: it is a work-list of edges prioritized
* by the shortest path found so far to the edge.
*/
/* Regular dijkstra shortest path, but over edges instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, e_src);
cost[BM_elem_index_get(e_src)] = 0.0f;
BMEdge *e = nullptr;
while (!BLI_heapsimple_is_empty(heap)) {
e = static_cast<BMEdge *>(BLI_heapsimple_pop_min(heap));
if (e == e_dst) {
break;
}
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
BM_elem_flag_enable(e, BM_ELEM_TAG);
/* Prevent the path overlapping itself in rare cases, see: #137456. */
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
edgetag_add_adjacent(heap, e, edges_prev, cost, params);
}
}
if (e == e_dst) {
do {
BLI_linklist_prepend(&path, e);
} while ((e = edges_prev[BM_elem_index_get(e)]));
}
MEM_delete(edges_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_face
* \{ */
static float facetag_cut_cost_edge(BMFace *f_a,
BMFace *f_b,
BMEdge *e,
const void *const f_endpoints[2])
{
float f_a_cent[3];
float f_b_cent[3];
float e_cent[3];
BM_face_calc_center_median_weighted(f_a, f_a_cent);
BM_face_calc_center_median_weighted(f_b, f_b_cent);
#if 0
mid_v3_v3v3(e_cent, e->v1->co, e->v2->co);
#else
/* For triangle fans it gives better results to pick a point on the edge. */
{
float ix_e[3], ix_f[3];
isect_line_line_v3(e->v1->co, e->v2->co, f_a_cent, f_b_cent, ix_e, ix_f);
const float factor = line_point_factor_v3(ix_e, e->v1->co, e->v2->co);
if (factor < 0.0f) {
copy_v3_v3(e_cent, e->v1->co);
}
else if (factor > 1.0f) {
copy_v3_v3(e_cent, e->v2->co);
}
else {
copy_v3_v3(e_cent, ix_e);
}
}
#endif
return step_cost_3_v3_ex(
f_a_cent, e_cent, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1]));
}
static float facetag_cut_cost_vert(BMFace *f_a,
BMFace *f_b,
BMVert *v,
const void *const f_endpoints[2])
{
float f_a_cent[3];
float f_b_cent[3];
BM_face_calc_center_median_weighted(f_a, f_a_cent);
BM_face_calc_center_median_weighted(f_b, f_b_cent);
return step_cost_3_v3_ex(
f_a_cent, v->co, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1]));
}
static void facetag_add_adjacent(HeapSimple *heap,
BMFace *f_a,
BMFace **faces_prev,
float *cost,
const void *const f_endpoints[2],
const BMCalcPathParams *params)
{
const int f_a_index = BM_elem_index_get(f_a);
/* Loop over faces of face, but do so by first looping over loops. */
{
BMIter liter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) {
BMLoop *l_first, *l_iter;
l_iter = l_first = l_a;
do {
BMFace *f_b = l_iter->f;
if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) {
/* We know 'f_b' is not visited, check it out! */
const int f_b_index = BM_elem_index_get(f_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
facetag_cut_cost_edge(f_a, f_b, l_iter->e, f_endpoints);
const float cost_new = cost[f_a_index] + cost_cut;
if (cost[f_b_index] > cost_new) {
cost[f_b_index] = cost_new;
faces_prev[f_b_index] = f_a;
BLI_heapsimple_insert(heap, cost_new, f_b);
}
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
if (params->use_step_face) {
BMIter liter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) {
BMIter litersub;
BMLoop *l_b;
BM_ITER_ELEM (l_b, &litersub, l_a->v, BM_LOOPS_OF_VERT) {
if ((l_a != l_b) && !BM_loop_share_edge_check(l_a, l_b)) {
BMFace *f_b = l_b->f;
if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) {
/* We know 'f_b' is not visited, check it out! */
const int f_b_index = BM_elem_index_get(f_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
facetag_cut_cost_vert(f_a, f_b, l_a->v, f_endpoints);
const float cost_new = cost[f_a_index] + cost_cut;
if (cost[f_b_index] > cost_new) {
cost[f_b_index] = cost_new;
faces_prev[f_b_index] = f_a;
BLI_heapsimple_insert(heap, cost_new, f_b);
}
}
}
}
}
}
}
LinkNode *BM_mesh_calc_path_face(BMesh *bm,
BMFace *f_src,
BMFace *f_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMFace *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* #BM_ELEM_TAG flag is used to store visited edges. */
BMFace *f;
BMIter fiter;
HeapSimple *heap;
float *cost;
BMFace **faces_prev;
int i, totface;
/* Start measuring face path at the face edges, ignoring their centers. */
const void *const f_endpoints[2] = {f_src, f_dst};
/* NOTE: would pass #BM_EDGE except we are looping over all faces anyway. */
// BM_mesh_elem_index_ensure(bm, BM_VERT /* | BM_EDGE */); // NOT NEEDED FOR FACETAG
BM_ITER_MESH_INDEX (f, &fiter, bm, BM_FACES_OF_MESH, i) {
BM_elem_flag_set(f, BM_ELEM_TAG, !filter_fn(f, user_data));
BM_elem_index_set(f, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_FACE;
/* Allocate. */
totface = bm->totface;
faces_prev = MEM_new_array_zeroed<BMFace *>(totface, __func__);
cost = MEM_new_array_uninitialized<float>(totface, __func__);
std::fill_n(cost, totface, COST_INIT_MAX);
/*
* Arrays are now filled as follows:
*
* As the search continues, `faces_prev[n]` will be the previous face on the shortest
* path found so far to face `n`. #BM_ELEM_TAG is used to tag elements we have visited,
* `cost[n]` will contain the length of the shortest
* path to face n found so far, Finally, heap is a priority heap which is built on the
* the same data as the cost array, but inverted: it is a work-list of faces prioritized
* by the shortest path found so far to the face.
*/
/* Regular dijkstra shortest path, but over faces instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, f_src);
cost[BM_elem_index_get(f_src)] = 0.0f;
while (!BLI_heapsimple_is_empty(heap)) {
f = static_cast<BMFace *>(BLI_heapsimple_pop_min(heap));
if (f == f_dst) {
break;
}
if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
BM_elem_flag_enable(f, BM_ELEM_TAG);
facetag_add_adjacent(heap, f, faces_prev, cost, f_endpoints, params);
}
}
if (f == f_dst) {
do {
BLI_linklist_prepend(&path, f);
} while ((f = faces_prev[BM_elem_index_get(f)]));
}
MEM_delete(faces_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_compiler_attrs.h"
#include "BLI_sys_types.h"
#include "bmesh_class.hh"
namespace blender {
struct LinkNode;
struct BMCalcPathParams {
uint use_topology_distance : 1;
uint use_step_face : 1;
};
LinkNode *BM_mesh_calc_path_vert(BMesh *bm,
BMVert *v_src,
BMVert *v_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMVert *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 5);
LinkNode *BM_mesh_calc_path_edge(BMesh *bm,
BMEdge *e_src,
BMEdge *e_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMEdge *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 5);
LinkNode *BM_mesh_calc_path_face(BMesh *bm,
BMFace *f_src,
BMFace *f_dst,
const BMCalcPathParams *params,
bool (*filter_fn)(BMFace *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3, 5);
} // namespace blender

View File

@@ -0,0 +1,482 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Find the region defined by the path(s) between 2 elements.
* (path isn't ordered).
*/
#include "MEM_guardedalloc.h"
#include <algorithm>
#include "BLI_array.hh"
#include "BLI_linklist.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "bmesh_path_region.hh" /* own include */
namespace blender {
/**
* Special handling of vertices with 2 edges
* (act as if the edge-chain is a single edge).
*
* \note Regarding manifold edge stepping: #BM_vert_is_edge_pair_manifold usage.
* Logic to skip a chain of vertices is not applied at boundaries because it gives
* strange behavior from a user perspective especially with boundary quads, see: #52701
*
* Restrict walking over a vertex chain to cases where the edges share the same faces.
* This is more typical of what a user would consider a vertex chain.
*/
#define USE_EDGE_CHAIN
#ifdef USE_EDGE_CHAIN
/**
* Takes a vertex with 2 edge users and assigns the vertices at each end-point,
*
* \return Success when \a v_end_pair values are set or false if the edges loop back on themselves.
*/
static bool bm_vert_pair_ends(BMVert *v_pivot, BMVert *v_end_pair[2])
{
BMEdge *e = v_pivot->e;
int j = 0;
do {
BMEdge *e_chain = e;
BMVert *v_other = BM_edge_other_vert(e_chain, v_pivot);
while (BM_vert_is_edge_pair_manifold(v_other)) {
BMEdge *e_chain_next = BM_DISK_EDGE_NEXT(e_chain, v_other);
BLI_assert(BM_DISK_EDGE_NEXT(e_chain_next, v_other) == e_chain);
v_other = BM_edge_other_vert(e_chain_next, v_other);
if (v_other == v_pivot) {
return false;
}
e_chain = e_chain_next;
}
v_end_pair[j++] = v_other;
} while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != v_pivot->e);
BLI_assert(j == 2);
return true;
}
#endif /* USE_EDGE_CHAIN */
/* -------------------------------------------------------------------- */
/** \name Vertex in Region Checks
* \{ */
static bool bm_vert_region_test(BMVert *v, int *const depths[2], const int pass)
{
const int index = BM_elem_index_get(v);
return (((depths[0][index] != -1) && (depths[1][index] != -1)) &&
((depths[0][index] + depths[1][index]) < pass));
}
#ifdef USE_EDGE_CHAIN
static bool bm_vert_region_test_chain(BMVert *v, int *const depths[2], const int pass)
{
BMVert *v_end_pair[2];
if (bm_vert_region_test(v, depths, pass)) {
return true;
}
if (BM_vert_is_edge_pair_manifold(v) && bm_vert_pair_ends(v, v_end_pair) &&
bm_vert_region_test(v_end_pair[0], depths, pass) &&
bm_vert_region_test(v_end_pair[1], depths, pass))
{
return true;
}
return false;
}
#else
static bool bm_vert_region_test_chain(BMVert *v, int *const depths[2], const int pass)
{
return bm_vert_region_test(v, depths, pass);
}
#endif
/** \} */
/**
* Main logic for calculating region between 2 elements.
*
* This method works walking (breadth first) over all vertices,
* keeping track of topological distance from the source.
*
* This is done in both directions, after that each vertices 'depth' is added to check
* if its less than the number of passes needed to complete the search.
* When it is, we know the path is one of possible paths
* that have the minimum topological distance.
*
* \note Only verts without BM_ELEM_TAG will be walked over.
*/
static LinkNode *mesh_calc_path_region_elem(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
const char path_htype)
{
int ele_verts_len[2];
Vector<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> ele_verts_buf[2];
BMVert **ele_verts[2];
/* Get vertices from any `ele_src/ele_dst` elements. */
for (int side = 0; side < 2; side++) {
BMElem *ele = side ? ele_dst : ele_src;
int j = 0;
if (ele->head.htype == BM_FACE) {
BMFace *f = reinterpret_cast<BMFace *>(ele);
ele_verts_buf[side].resize(f->len);
ele_verts[side] = ele_verts_buf[side].data();
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
ele_verts[side][j++] = l_iter->v;
} while ((l_iter = l_iter->next) != l_first);
}
else if (ele->head.htype == BM_EDGE) {
BMEdge *e = reinterpret_cast<BMEdge *>(ele);
ele_verts_buf[side].resize(2);
ele_verts[side] = ele_verts_buf[side].data();
ele_verts[side][j++] = e->v1;
ele_verts[side][j++] = e->v2;
}
else if (ele->head.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(ele);
ele_verts_buf[side].resize(1);
ele_verts[side] = ele_verts_buf[side].data();
ele_verts[side][j++] = v;
}
else {
BLI_assert(0);
}
ele_verts_len[side] = j;
}
int *depths[2] = {nullptr};
int pass = 0;
BMVert **stack = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
BMVert **stack_other = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
STACK_DECLARE(stack);
STACK_INIT(stack, bm->totvert);
STACK_DECLARE(stack_other);
STACK_INIT(stack_other, bm->totvert);
BM_mesh_elem_index_ensure(bm, BM_VERT);
/* After exhausting all possible elements, we should have found all elements on the 'side_other'.
* otherwise, exit early. */
bool found_all = false;
for (int side = 0; side < 2; side++) {
const int side_other = !side;
/* initialize depths to -1 (un-touched), fill in with the depth as we walk over the edges. */
depths[side] = MEM_new_array_uninitialized<int>(bm->totvert, __func__);
std::fill_n(depths[side], bm->totvert, -1);
/* needed for second side */
STACK_CLEAR(stack);
STACK_CLEAR(stack_other);
for (int i = 0; i < ele_verts_len[side]; i++) {
BMVert *v = ele_verts[side][i];
depths[side][BM_elem_index_get(v)] = 0;
if (v->e && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
STACK_PUSH(stack, v);
}
}
#ifdef USE_EDGE_CHAIN
/* Expand initial state to end-point vertices when they only have 2x edges,
* this prevents odd behavior when source or destination are in the middle
* of a long chain of edges. */
if (ELEM(path_htype, BM_VERT, BM_EDGE)) {
for (int i = 0; i < ele_verts_len[side]; i++) {
BMVert *v = ele_verts[side][i];
BMVert *v_end_pair[2];
if (BM_vert_is_edge_pair_manifold(v) && bm_vert_pair_ends(v, v_end_pair)) {
for (int j = 0; j < 2; j++) {
const int v_end_index = BM_elem_index_get(v_end_pair[j]);
if (depths[side][v_end_index] == -1) {
depths[side][v_end_index] = 0;
if (!BM_elem_flag_test(v_end_pair[j], BM_ELEM_TAG)) {
STACK_PUSH(stack, v_end_pair[j]);
}
}
}
}
}
}
#endif /* USE_EDGE_CHAIN */
/* Keep walking over connected geometry until we find all the vertices in
* `ele_verts[side_other]`, or exit the loop when there's no connection. */
found_all = false;
for (pass = 1; (STACK_SIZE(stack) != 0); pass++) {
while (STACK_SIZE(stack) != 0) {
BMVert *v_a = STACK_POP(stack);
// const int v_a_index = BM_elem_index_get(v_a); /* only for assert */
BMEdge *e = v_a->e;
do {
BMVert *v_b = BM_edge_other_vert(e, v_a);
int v_b_index = BM_elem_index_get(v_b);
if (depths[side][v_b_index] == -1) {
#ifdef USE_EDGE_CHAIN
/* Walk along the chain, fill in values until we reach a vertex with 3+ edges. */
{
BMEdge *e_chain = e;
while (BM_vert_is_edge_pair_manifold(v_b) && (depths[side][v_b_index] == -1)) {
depths[side][v_b_index] = pass;
BMEdge *e_chain_next = BM_DISK_EDGE_NEXT(e_chain, v_b);
BLI_assert(BM_DISK_EDGE_NEXT(e_chain_next, v_b) == e_chain);
v_b = BM_edge_other_vert(e_chain_next, v_b);
v_b_index = BM_elem_index_get(v_b);
e_chain = e_chain_next;
}
}
#endif /* USE_EDGE_CHAIN */
/* Add the other vertex to the stack, to be traversed in the next pass. */
if (depths[side][v_b_index] == -1) {
#ifdef USE_EDGE_CHAIN
BLI_assert(!BM_vert_is_edge_pair_manifold(v_b));
#endif
BLI_assert(pass == depths[side][BM_elem_index_get(v_a)] + 1);
depths[side][v_b_index] = pass;
if (!BM_elem_flag_test(v_b, BM_ELEM_TAG)) {
STACK_PUSH(stack_other, v_b);
}
}
}
} while ((e = BM_DISK_EDGE_NEXT(e, v_a)) != v_a->e);
}
/* Stop searching once there's none left.
* Note that this looks in-efficient, however until the target elements reached,
* it will exit immediately.
* After that, it takes as many passes as the element has edges to finish off. */
found_all = true;
for (int i = 0; i < ele_verts_len[side_other]; i++) {
if (depths[side][BM_elem_index_get(ele_verts[side_other][i])] == -1) {
found_all = false;
break;
}
}
if (found_all == true) {
pass++;
break;
}
STACK_SWAP(stack, stack_other);
}
/* if we have nothing left, and didn't find all elements on the other side,
* exit early and don't continue */
if (found_all == false) {
break;
}
}
MEM_delete(stack);
MEM_delete(stack_other);
/* Now we have depths recorded from both sides,
* select elements that use tagged verts. */
LinkNode *path = nullptr;
if (found_all == false) {
/* fail! (do nothing) */
}
else if (path_htype == BM_FACE) {
BMIter fiter;
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
/* check all verts in face are tagged */
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
bool ok = true;
#if 0
do {
if (!bm_vert_region_test_chain(l_iter->v, depths, pass)) {
ok = false;
break;
}
} while ((l_iter = l_iter->next) != l_first);
#else
/* Allowing a single failure on a face gives fewer 'gaps'.
* While correct, in practice they're often part of what
* a user would consider the 'region'. */
int ok_tests = f->len > 3 ? 1 : 0; /* how many times we may fail */
do {
if (!bm_vert_region_test_chain(l_iter->v, depths, pass)) {
if (ok_tests == 0) {
ok = false;
break;
}
ok_tests--;
}
} while ((l_iter = l_iter->next) != l_first);
#endif
if (ok) {
BLI_linklist_prepend(&path, f);
}
}
}
}
else if (path_htype == BM_EDGE) {
BMIter eiter;
BMEdge *e;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
/* check all verts in edge are tagged */
bool ok = true;
for (int j = 0; j < 2; j++) {
if (!bm_vert_region_test_chain(*((&e->v1) + j), depths, pass)) {
ok = false;
break;
}
}
if (ok) {
BLI_linklist_prepend(&path, e);
}
}
}
}
else if (path_htype == BM_VERT) {
BMIter viter;
BMVert *v;
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (bm_vert_region_test_chain(v, depths, pass)) {
BLI_linklist_prepend(&path, v);
}
}
}
for (int side = 0; side < 2; side++) {
if (depths[side]) {
MEM_delete(depths[side]);
}
}
return path;
}
#undef USE_EDGE_CHAIN
/* -------------------------------------------------------------------- */
/** \name Main Functions (exposed externally).
* \{ */
LinkNode *BM_mesh_calc_path_region_vert(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMVert *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMVert *v;
BMIter viter;
int i;
BM_ITER_MESH_INDEX (v, &viter, bm, BM_VERTS_OF_MESH, i) {
BM_elem_flag_set(v, BM_ELEM_TAG, !filter_fn(v, user_data));
BM_elem_index_set(v, i); /* set_inline */
}
bm->elem_index_dirty &= ~BM_VERT;
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_VERT);
return path;
}
LinkNode *BM_mesh_calc_path_region_edge(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMEdge *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMEdge *e;
BMIter eiter;
int i;
/* flush flag to verts */
BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH_INDEX (e, &eiter, bm, BM_EDGES_OF_MESH, i) {
bool test;
BM_elem_flag_set(e, BM_ELEM_TAG, test = !filter_fn(e, user_data));
/* flush tag to verts */
if (test == false) {
for (int j = 0; j < 2; j++) {
BM_elem_flag_disable(*((&e->v1) + j), BM_ELEM_TAG);
}
}
}
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_EDGE);
return path;
}
LinkNode *BM_mesh_calc_path_region_face(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMFace *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMFace *f;
BMIter fiter;
int i;
/* flush flag to verts */
BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH_INDEX (f, &fiter, bm, BM_FACES_OF_MESH, i) {
bool test;
BM_elem_flag_set(f, BM_ELEM_TAG, test = !filter_fn(f, user_data));
/* flush tag to verts */
if (test == false) {
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
} while ((l_iter = l_iter->next) != l_first);
}
}
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_FACE);
return path;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,38 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_compiler_attrs.h"
#include "bmesh_class.hh"
namespace blender {
struct LinkNode *BM_mesh_calc_path_region_vert(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMVert *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
struct LinkNode *BM_mesh_calc_path_region_edge(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMEdge *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
struct LinkNode *BM_mesh_calc_path_region_face(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
bool (*filter_fn)(BMFace *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
} // namespace blender

View File

@@ -0,0 +1,505 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Find the region defined by the path(s) between 2 UV elements.
* (path isn't ordered).
*
* \note This uses the same behavior as `bmesh_path_region.cc`
* however walking UVs causes enough differences that it's
* impractical to share the code.
*/
#include "MEM_guardedalloc.h"
#include <algorithm>
#include "BLI_array.hh"
#include "BLI_linklist.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "bmesh_path_region_uv.hh" /* own include */
namespace blender {
/**
* Special handling of vertices with 2 edges
* (act as if the edge-chain is a single edge).
*
* \note Regarding manifold edge stepping: #BM_vert_is_edge_pair_manifold usage.
* Logic to skip a chain of vertices is not applied at boundaries because it gives
* strange behavior from a user perspective especially with boundary quads, see: #52701
*
* Restrict walking over a vertex chain to cases where the edges share the same faces.
* This is more typical of what a user would consider a vertex chain.
*/
#define USE_EDGE_CHAIN
#ifdef USE_EDGE_CHAIN
/**
* Takes a vertex with 2 edge users and assigns the vertices at each end-point,
*
* \return Success when \a l_end_pair values are set or false if the edges loop back on themselves.
*/
static bool bm_loop_pair_ends(BMLoop *l_pivot, BMLoop *l_end_pair[2])
{
int j;
for (j = 0; j < 2; j++) {
BMLoop *l_other = j ? l_pivot->next : l_pivot->prev;
while (BM_vert_is_edge_pair_manifold(l_other->v)) {
l_other = j ? l_other->next : l_other->prev;
if (l_other == l_pivot) {
return false;
}
}
l_end_pair[j] = l_other;
}
BLI_assert(j == 2);
return true;
}
#endif /* USE_EDGE_CHAIN */
/* -------------------------------------------------------------------- */
/** \name Loop Vertex in Region Checks
* \{ */
static bool bm_loop_region_test(BMLoop *l, int *const depths[2], const int pass)
{
const int index = BM_elem_index_get(l);
return (((depths[0][index] != -1) && (depths[1][index] != -1)) &&
((depths[0][index] + depths[1][index]) < pass));
}
#ifdef USE_EDGE_CHAIN
static bool bm_loop_region_test_chain(BMLoop *l, int *const depths[2], const int pass)
{
BMLoop *l_end_pair[2];
if (bm_loop_region_test(l, depths, pass)) {
return true;
}
if (BM_vert_is_edge_pair_manifold(l->v) && bm_loop_pair_ends(l, l_end_pair) &&
bm_loop_region_test(l_end_pair[0], depths, pass) &&
bm_loop_region_test(l_end_pair[1], depths, pass))
{
return true;
}
return false;
}
#else
static bool bm_loop_region_test_chain(BMLoop *l, int *const depths[2], const int pass)
{
return bm_loop_region_test(l, depths, pass);
}
#endif
/** \} */
/**
* Main logic for calculating region between 2 elements.
*
* This method works walking (breadth first) over all vertices,
* keeping track of topological distance from the source.
*
* This is done in both directions, after that each vertices 'depth' is added to check
* if its less than the number of passes needed to complete the search.
* When it is, we know the path is one of possible paths
* that have the minimum topological distance.
*
* \note Only verts without BM_ELEM_TAG will be walked over.
*/
static LinkNode *mesh_calc_path_region_elem(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
const int cd_loop_uv_offset,
const char path_htype)
{
BLI_assert(cd_loop_uv_offset >= 0);
int ele_loops_len[2];
Vector<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> ele_loops_buf[2];
BMLoop **ele_loops[2];
/* Get vertices from any `ele_src/ele_dst` elements. */
for (int side = 0; side < 2; side++) {
BMElem *ele = side ? ele_dst : ele_src;
int j = 0;
if (ele->head.htype == BM_FACE) {
BMFace *f = reinterpret_cast<BMFace *>(ele);
ele_loops_buf[side].resize(f->len);
ele_loops[side] = ele_loops_buf[side].data();
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
ele_loops[side][j++] = l_iter;
} while ((l_iter = l_iter->next) != l_first);
}
else if (ele->head.htype == BM_LOOP) {
if (path_htype == BM_EDGE) {
BMLoop *l = reinterpret_cast<BMLoop *>(ele);
ele_loops_buf[side].resize(2);
ele_loops[side] = ele_loops_buf[side].data();
ele_loops[side][j++] = l;
ele_loops[side][j++] = l->next;
}
else if (path_htype == BM_VERT) {
BMLoop *l = reinterpret_cast<BMLoop *>(ele);
ele_loops_buf[side].resize(1);
ele_loops[side] = ele_loops_buf[side].data();
ele_loops[side][j++] = l;
}
else {
BLI_assert(0);
}
}
else {
BLI_assert(0);
}
ele_loops_len[side] = j;
}
int *depths[2] = {nullptr};
int pass = 0;
BMLoop **stack = MEM_new_array_uninitialized<BMLoop *>(bm->totloop, __func__);
BMLoop **stack_other = MEM_new_array_uninitialized<BMLoop *>(bm->totloop, __func__);
STACK_DECLARE(stack);
STACK_INIT(stack, bm->totloop);
STACK_DECLARE(stack_other);
STACK_INIT(stack_other, bm->totloop);
BM_mesh_elem_index_ensure(bm, BM_LOOP);
/* After exhausting all possible elements, we should have found all elements on the 'side_other'.
* otherwise, exit early. */
bool found_all = false;
for (int side = 0; side < 2; side++) {
const int side_other = !side;
/* initialize depths to -1 (un-touched), fill in with the depth as we walk over the edges. */
depths[side] = MEM_new_array_uninitialized<int>(bm->totloop, __func__);
std::fill_n(depths[side], bm->totloop, -1);
/* needed for second side */
STACK_CLEAR(stack);
STACK_CLEAR(stack_other);
for (int i = 0; i < ele_loops_len[side]; i++) {
BMLoop *l = ele_loops[side][i];
depths[side][BM_elem_index_get(l)] = 0;
if (!BM_elem_flag_test(l, BM_ELEM_TAG)) {
STACK_PUSH(stack, l);
}
}
#ifdef USE_EDGE_CHAIN
/* Expand initial state to end-point vertices when they only have 2x edges,
* this prevents odd behavior when source or destination are in the middle
* of a long chain of edges. */
if (ELEM(path_htype, BM_VERT, BM_EDGE)) {
for (int i = 0; i < ele_loops_len[side]; i++) {
BMLoop *l = ele_loops[side][i];
BMLoop *l_end_pair[2];
if (BM_vert_is_edge_pair_manifold(l->v) && bm_loop_pair_ends(l, l_end_pair)) {
for (int j = 0; j < 2; j++) {
const int l_end_index = BM_elem_index_get(l_end_pair[j]);
if (depths[side][l_end_index] == -1) {
depths[side][l_end_index] = 0;
if (!BM_elem_flag_test(l_end_pair[j], BM_ELEM_TAG)) {
STACK_PUSH(stack, l_end_pair[j]);
}
}
}
}
}
}
#endif /* USE_EDGE_CHAIN */
/* Keep walking over connected geometry until we find all the vertices in
* `ele_loops[side_other]`, or exit the loop when there's no connection. */
found_all = false;
for (pass = 1; (STACK_SIZE(stack) != 0); pass++) {
while (STACK_SIZE(stack) != 0) {
BMLoop *l_a = STACK_POP(stack);
const int l_a_index = BM_elem_index_get(l_a);
BMIter iter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &iter, l_a->v, BM_LOOPS_OF_VERT) {
if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
continue;
}
if (!BM_loop_uv_share_vert_check(l_a, l_iter, cd_loop_uv_offset)) {
continue;
}
/* Flush the depth to connected loops (only needed for UVs). */
if (depths[side][BM_elem_index_get(l_iter)] == -1) {
depths[side][BM_elem_index_get(l_iter)] = depths[side][l_a_index];
}
for (int j = 0; j < 2; j++) {
BMLoop *l_b = j ? l_iter->next : l_iter->prev;
int l_b_index = BM_elem_index_get(l_b);
if (depths[side][l_b_index] == -1) {
#ifdef USE_EDGE_CHAIN
/* Walk along the chain, fill in values until we reach a vertex with 3+ edges. */
{
while (BM_vert_is_edge_pair_manifold(l_b->v) &&
((depths[side][l_b_index] == -1) &&
/* Don't walk back to the beginning */
(l_b != (j ? l_iter->prev : l_iter->next))))
{
depths[side][l_b_index] = pass;
l_b = j ? l_b->next : l_b->prev;
l_b_index = BM_elem_index_get(l_b);
}
}
#endif /* USE_EDGE_CHAIN */
/* Add the other vertex to the stack, to be traversed in the next pass. */
if (depths[side][l_b_index] == -1) {
#ifdef USE_EDGE_CHAIN
BLI_assert(!BM_vert_is_edge_pair_manifold(l_b->v));
#endif
BLI_assert(pass == depths[side][BM_elem_index_get(l_a)] + 1);
depths[side][l_b_index] = pass;
if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) {
STACK_PUSH(stack_other, l_b);
}
}
}
}
}
}
/* Stop searching once there's none left.
* Note that this looks in-efficient, however until the target elements reached,
* it will exit immediately.
* After that, it takes as many passes as the element has edges to finish off. */
found_all = true;
for (int i = 0; i < ele_loops_len[side_other]; i++) {
if (depths[side][BM_elem_index_get(ele_loops[side_other][i])] == -1) {
found_all = false;
break;
}
}
if (found_all == true) {
pass++;
break;
}
STACK_SWAP(stack, stack_other);
}
/* if we have nothing left, and didn't find all elements on the other side,
* exit early and don't continue */
if (found_all == false) {
break;
}
}
MEM_delete(stack);
MEM_delete(stack_other);
/* Now we have depths recorded from both sides,
* select elements that use tagged verts. */
LinkNode *path = nullptr;
if (found_all == false) {
/* fail! (do nothing) */
}
else if (path_htype == BM_FACE) {
BMIter fiter;
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
/* check all verts in face are tagged */
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
bool ok = true;
#if 0
do {
if (!bm_loop_region_test_chain(l_iter->v, depths, pass)) {
ok = false;
break;
}
} while ((l_iter = l_iter->next) != l_first);
#else
/* Allowing a single failure on a face gives fewer 'gaps'.
* While correct, in practice they're often part of what
* a user would consider the 'region'. */
int ok_tests = f->len > 3 ? 1 : 0; /* how many times we may fail */
do {
if (!bm_loop_region_test_chain(l_iter, depths, pass)) {
if (ok_tests == 0) {
ok = false;
break;
}
ok_tests--;
}
} while ((l_iter = l_iter->next) != l_first);
#endif
if (ok) {
BLI_linklist_prepend(&path, f);
}
}
}
}
else if (path_htype == BM_EDGE) {
BMIter fiter;
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l;
/* Check the current and next loop vertices are in the region. */
bool l_in_chain_next = bm_loop_region_test_chain(BM_FACE_FIRST_LOOP(f), depths, pass);
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
const bool l_in_chain = l_in_chain_next;
l_in_chain_next = bm_loop_region_test_chain(l->next, depths, pass);
if (l_in_chain && l_in_chain_next) {
BLI_linklist_prepend(&path, l);
}
}
}
}
else if (path_htype == BM_VERT) {
BMIter fiter;
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (bm_loop_region_test_chain(l, depths, pass)) {
BLI_linklist_prepend(&path, l);
}
}
}
}
for (int side = 0; side < 2; side++) {
if (depths[side]) {
MEM_delete(depths[side]);
}
}
return path;
}
#undef USE_EDGE_CHAIN
/* -------------------------------------------------------------------- */
/** \name Main Functions (exposed externally).
* \{ */
LinkNode *BM_mesh_calc_path_uv_region_vert(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
const int cd_loop_uv_offset,
bool (*filter_fn)(BMLoop *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMFace *f;
BMIter fiter;
int i = 0;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_set(l, BM_ELEM_TAG, !filter_fn(l, user_data));
BM_elem_index_set(l, i); /* set_inline */
i += 1;
}
}
bm->elem_index_dirty &= ~BM_LOOP;
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, cd_loop_uv_offset, BM_VERT);
return path;
}
LinkNode *BM_mesh_calc_path_uv_region_edge(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
const int cd_loop_uv_offset,
bool (*filter_fn)(BMLoop *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMFace *f;
BMIter fiter;
int i = 0;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_set(l, BM_ELEM_TAG, !filter_fn(l, user_data));
BM_elem_index_set(l, i); /* set_inline */
i += 1;
}
}
bm->elem_index_dirty &= ~BM_LOOP;
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, cd_loop_uv_offset, BM_EDGE);
return path;
}
LinkNode *BM_mesh_calc_path_uv_region_face(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
const int cd_loop_uv_offset,
bool (*filter_fn)(BMFace *, void *user_data),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited verts */
BMFace *f;
BMIter fiter;
int i;
/* flush flag to verts */
BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH_INDEX (f, &fiter, bm, BM_FACES_OF_MESH, i) {
bool test;
BM_elem_flag_set(f, BM_ELEM_TAG, test = !filter_fn(f, user_data));
/* flush tag to verts */
if (test == false) {
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
} while ((l_iter = l_iter->next) != l_first);
}
}
path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, cd_loop_uv_offset, BM_FACE);
return path;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_linklist.h"
#include "bmesh_class.hh"
namespace blender {
LinkNode *BM_mesh_calc_path_uv_region_vert(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
int cd_loop_uv_offset,
bool (*filter_fn)(BMLoop *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
LinkNode *BM_mesh_calc_path_uv_region_edge(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
int cd_loop_uv_offset,
bool (*filter_fn)(BMLoop *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
LinkNode *BM_mesh_calc_path_uv_region_face(BMesh *bm,
BMElem *ele_src,
BMElem *ele_dst,
int cd_loop_uv_offset,
bool (*filter_fn)(BMFace *, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
} // namespace blender

View File

@@ -0,0 +1,619 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Find a path between 2 elements in UV space.
*/
#include "MEM_guardedalloc.h"
#include <algorithm>
#include "BLI_heap_simple.h"
#include "BLI_linklist.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "bmesh_path_uv.hh" /* own include */
#include "intern/bmesh_query.hh"
#include "intern/bmesh_query_uv.hh"
namespace blender {
#define COST_INIT_MAX FLT_MAX
/* -------------------------------------------------------------------- */
/** \name Generic Helpers
* \{ */
/**
* Use skip options when we want to start measuring from a boundary.
*
* See #step_cost_3_v3_ex in `bmesh_path.cc` which follows the same logic.
*/
static float step_cost_3_v2_ex(
const float v1[2], const float v2[2], const float v3[2], bool skip_12, bool skip_23)
{
float d1[2], d2[2];
/* The cost is based on the simple sum of the length of the two edges. */
sub_v2_v2v2(d1, v2, v1);
sub_v2_v2v2(d2, v3, v2);
const float cost_12 = normalize_v2(d1);
const float cost_23 = normalize_v2(d2);
const float cost = ((skip_12 ? 0.0f : cost_12) + (skip_23 ? 0.0f : cost_23));
/* But is biased to give higher values to sharp turns, so that it will take paths with
* fewer "turns" when selecting between equal-weighted paths between the two edges. */
return cost * (1.0f + 0.5f * (2.0f - sqrtf(fabsf(dot_v2v2(d1, d2)))));
}
static float step_cost_3_v2(const float v1[2], const float v2[2], const float v3[2])
{
return step_cost_3_v2_ex(v1, v2, v3, false, false);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_uv_vert
* \{ */
static void verttag_add_adjacent_uv(HeapSimple *heap,
BMLoop *l_a,
BMLoop **loops_prev,
float *cost,
const BMCalcPathUVParams *params)
{
BLI_assert(params->aspect_y != 0.0f);
const int cd_loop_uv_offset = params->cd_loop_uv_offset;
const int l_a_index = BM_elem_index_get(l_a);
const float *luv_a = BM_ELEM_CD_GET_FLOAT_P(l_a, cd_loop_uv_offset);
const float uv_a[2] = {luv_a[0], luv_a[1] / params->aspect_y};
{
BMIter liter;
BMLoop *l;
/* Loop over faces of face, but do so by first looping over loops. */
BM_ITER_ELEM (l, &liter, l_a->v, BM_LOOPS_OF_VERT) {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
if (equals_v2v2(luv_a, luv)) {
/* 'l_a' is already tagged, tag all adjacent. */
BM_elem_flag_enable(l, BM_ELEM_TAG);
BMLoop *l_b = l->next;
do {
if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) {
const float *luv_b = BM_ELEM_CD_GET_FLOAT_P(l_b, cd_loop_uv_offset);
const float uv_b[2] = {luv_b[0], luv_b[1] / params->aspect_y};
/* We know 'l_b' is not visited, check it out! */
const int l_b_index = BM_elem_index_get(l_b);
const float cost_cut = params->use_topology_distance ? 1.0f : len_v2v2(uv_a, uv_b);
const float cost_new = cost[l_a_index] + cost_cut;
if (cost[l_b_index] > cost_new) {
cost[l_b_index] = cost_new;
loops_prev[l_b_index] = l_a;
BLI_heapsimple_insert(heap, cost_new, l_b);
}
}
/* This means we only step onto `l->prev` & `l->next`. */
if (params->use_step_face == false) {
if (l_b == l->next) {
l_b = l->prev->prev;
}
}
} while ((l_b = l_b->next) != l);
}
}
}
}
LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm,
BMLoop *l_src,
BMLoop *l_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMLoop *, void *),
void *user_data)
{
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited edges */
BMIter viter;
HeapSimple *heap;
float *cost;
BMLoop **loops_prev;
int i = 0, totloop;
BMFace *f;
/* NOTE: would pass BM_EDGE except we are looping over all faces anyway. */
// BM_mesh_elem_index_ensure(bm, BM_LOOP); /* NOTE: not needed for facetag. */
BM_ITER_MESH (f, &viter, bm, BM_FACES_OF_MESH) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BM_elem_flag_set(l_iter, BM_ELEM_TAG, !filter_fn(l_iter, user_data));
BM_elem_index_set(l_iter, i); /* set_inline */
i += 1;
} while ((l_iter = l_iter->next) != l_first);
}
bm->elem_index_dirty &= ~BM_LOOP;
/* Allocate. */
totloop = bm->totloop;
loops_prev = MEM_new_array_zeroed<BMLoop *>(totloop, __func__);
cost = MEM_new_array_uninitialized<float>(totloop, __func__);
std::fill_n(cost, totloop, COST_INIT_MAX);
/* Regular dijkstra shortest path, but over UV loops instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, l_src);
cost[BM_elem_index_get(l_src)] = 0.0f;
BMLoop *l = nullptr;
while (!BLI_heapsimple_is_empty(heap)) {
l = static_cast<BMLoop *>(BLI_heapsimple_pop_min(heap));
if ((l->v == l_dst->v) && BM_loop_uv_share_vert_check(l, l_dst, params->cd_loop_uv_offset)) {
break;
}
if (!BM_elem_flag_test(l, BM_ELEM_TAG)) {
/* Adjacent loops are tagged while stepping to avoid 2x loops. */
BM_elem_flag_enable(l, BM_ELEM_TAG);
verttag_add_adjacent_uv(heap, l, loops_prev, cost, params);
}
}
if ((l->v == l_dst->v) && BM_loop_uv_share_vert_check(l, l_dst, params->cd_loop_uv_offset)) {
do {
BLI_linklist_prepend(&path, l);
} while ((l = loops_prev[BM_elem_index_get(l)]));
}
MEM_delete(loops_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_uv_edge
* \{ */
static float edgetag_cut_cost_vert_uv(
BMLoop *l_e_a, BMLoop *l_e_b, BMLoop *l_v, const float aspect_y, const int cd_loop_uv_offset)
{
BMLoop *l_v1 = (l_v->v == l_e_a->v) ? l_e_a->next : l_e_a;
BMLoop *l_v2 = (l_v->v == l_e_b->v) ? l_e_b->next : l_e_b;
float *luv_v1 = BM_ELEM_CD_GET_FLOAT_P(l_v1, cd_loop_uv_offset);
float *luv_v2 = BM_ELEM_CD_GET_FLOAT_P(l_v2, cd_loop_uv_offset);
float *luv_v = BM_ELEM_CD_GET_FLOAT_P(l_v, cd_loop_uv_offset);
float uv_v1[2] = {luv_v1[0], luv_v1[1] / aspect_y};
float uv_v2[2] = {luv_v2[0], luv_v2[1] / aspect_y};
float uv_v[2] = {luv_v[0], luv_v[1] / aspect_y};
return step_cost_3_v2(uv_v1, uv_v, uv_v2);
}
static float edgetag_cut_cost_face_uv(
BMLoop *l_e_a, BMLoop *l_e_b, BMFace *f, const float aspect_v2[2], const int cd_loop_uv_offset)
{
float l_e_a_cent[2], l_e_b_cent[2], f_cent[2];
float *luv_e_a = BM_ELEM_CD_GET_FLOAT_P(l_e_a, cd_loop_uv_offset);
float *luv_e_b = BM_ELEM_CD_GET_FLOAT_P(l_e_b, cd_loop_uv_offset);
mid_v2_v2v2(l_e_a_cent, luv_e_a, luv_e_a);
mid_v2_v2v2(l_e_b_cent, luv_e_b, luv_e_b);
mul_v2_v2(l_e_a_cent, aspect_v2);
mul_v2_v2(l_e_b_cent, aspect_v2);
BM_face_uv_calc_center_median_weighted(f, aspect_v2, cd_loop_uv_offset, f_cent);
return step_cost_3_v2(l_e_a_cent, l_e_b_cent, f_cent);
}
static void edgetag_add_adjacent_uv(HeapSimple *heap,
BMLoop *l_a,
BMLoop **loops_prev,
float *cost,
const BMCalcPathUVParams *params)
{
BLI_assert(params->aspect_y != 0.0f);
const int cd_loop_uv_offset = params->cd_loop_uv_offset;
BMLoop *l_a_verts[2] = {l_a, l_a->next};
const int l_a_index = BM_elem_index_get(l_a);
if (params->use_step_face == false) {
for (int i = 0; i < ARRAY_SIZE(l_a_verts); i++) {
/* Skip current UV vert if it is part of the previous UV edge in the path. */
if (loops_prev[l_a_index]) {
BMLoop *l_prev = loops_prev[l_a_index];
if (l_a_verts[i]->v != l_prev->v) {
l_prev = (l_a_verts[i]->v == l_prev->next->v) ? l_prev->next : nullptr;
}
if (l_prev && BM_loop_uv_share_vert_check(l_a_verts[i], l_prev, cd_loop_uv_offset)) {
continue;
}
}
BMEdge *e_b;
BMIter eiter;
BM_ITER_ELEM (e_b, &eiter, l_a_verts[i]->v, BM_EDGES_OF_VERT) {
if (e_b->l == nullptr) {
continue;
}
BMLoop *l_first, *l_b;
l_first = l_b = e_b->l;
do {
if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) {
BMLoop *l_b_vert = (l_a_verts[i]->v == l_b->v) ? l_b : l_b->next;
if (BM_loop_uv_share_vert_check(l_a_verts[i], l_b_vert, cd_loop_uv_offset)) {
/* We know 'l_b' is not visited, check it out! */
const int l_b_index = BM_elem_index_get(l_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
edgetag_cut_cost_vert_uv(l_a,
l_b,
l_a_verts[i],
params->aspect_y,
cd_loop_uv_offset);
const float cost_new = cost[l_a_index] + cost_cut;
if (cost[l_b_index] > cost_new) {
cost[l_b_index] = cost_new;
loops_prev[l_b_index] = l_a;
BLI_heapsimple_insert(heap, cost_new, l_b);
}
}
}
} while ((l_b = l_b->radial_next) != l_first);
}
}
}
else {
const float aspect_v2[2] = {1.0f, 1.0f / params->aspect_y};
BMLoop *l_first, *l_iter;
l_iter = l_first = l_a;
do {
/* Ensures connected UVs and that they lie on the same island. */
if (!BM_loop_uv_share_edge_check(l_a, l_iter, cd_loop_uv_offset)) {
continue;
}
BMLoop *l_cycle_iter, *l_cycle_end;
l_cycle_iter = l_iter->next;
l_cycle_end = l_iter;
do {
BMLoop *l_b = l_cycle_iter;
if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) {
/* We know 'l_b' is not visited, check it out! */
const int l_b_index = BM_elem_index_get(l_b);
const float cost_cut = params->use_topology_distance ?
1.0f :
edgetag_cut_cost_face_uv(l_a,
l_b,
l_iter->f,
aspect_v2,
params->cd_loop_uv_offset);
const float cost_new = cost[l_a_index] + cost_cut;
if (cost[l_b_index] > cost_new) {
cost[l_b_index] = cost_new;
loops_prev[l_b_index] = l_a;
BLI_heapsimple_insert(heap, cost_new, l_b);
}
}
} while ((l_cycle_iter = l_cycle_iter->next) != l_cycle_end);
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
LinkNode *BM_mesh_calc_path_uv_edge(BMesh *bm,
BMLoop *l_src,
BMLoop *l_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMLoop *, void *),
void *user_data)
{
LinkNode *path = nullptr;
BMFace *f;
BMIter iter;
HeapSimple *heap;
float *cost;
BMLoop **loops_prev;
int i = 0, totloop;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BM_elem_flag_set(l_iter, BM_ELEM_TAG, !filter_fn(l_iter, user_data));
BM_elem_index_set(l_iter, i);
i += 1;
} while ((l_iter = l_iter->next) != l_first);
}
bm->elem_index_dirty &= ~BM_LOOP;
totloop = bm->totloop;
loops_prev = MEM_new_array_zeroed<BMLoop *>(totloop, __func__);
cost = MEM_new_array_uninitialized<float>(totloop, __func__);
std::fill_n(cost, totloop, COST_INIT_MAX);
/* Regular dijkstra shortest path, but over UV loops/edges instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, l_src);
cost[BM_elem_index_get(l_src)] = 0.0f;
BMLoop *l = nullptr;
while (!BLI_heapsimple_is_empty(heap)) {
l = static_cast<BMLoop *>(BLI_heapsimple_pop_min(heap));
if ((l->e == l_dst->e) && BM_loop_uv_share_edge_check(l, l_dst, params->cd_loop_uv_offset)) {
break;
}
if (!BM_elem_flag_test(l, BM_ELEM_TAG)) {
BM_elem_flag_enable(l, BM_ELEM_TAG);
edgetag_add_adjacent_uv(heap, l, loops_prev, cost, params);
}
}
if ((l->e == l_dst->e) && BM_loop_uv_share_edge_check(l, l_dst, params->cd_loop_uv_offset)) {
do {
BLI_linklist_prepend(&path, l);
} while ((l = loops_prev[BM_elem_index_get(l)]));
}
MEM_delete(loops_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BM_mesh_calc_path_uv_face
* \{ */
static float facetag_cut_cost_edge_uv(BMFace *f_a,
BMFace *f_b,
BMLoop *l_edge,
const void *const f_endpoints[2],
const float aspect_v2[2],
const int cd_loop_uv_offset)
{
float f_a_cent[2];
float f_b_cent[2];
float e_cent[2];
BM_face_uv_calc_center_median_weighted(f_a, aspect_v2, cd_loop_uv_offset, f_a_cent);
BM_face_uv_calc_center_median_weighted(f_b, aspect_v2, cd_loop_uv_offset, f_b_cent);
const float *co_v1 = BM_ELEM_CD_GET_FLOAT_P(l_edge, cd_loop_uv_offset);
const float *co_v2 = BM_ELEM_CD_GET_FLOAT_P(l_edge->next, cd_loop_uv_offset);
#if 0
mid_v2_v2v2(e_cent, co_v1, co_v2);
#else
/* For triangle fans it gives better results to pick a point on the edge. */
{
float ix_e[2];
isect_line_line_v2_point(co_v1, co_v2, f_a_cent, f_b_cent, ix_e);
const float factor = line_point_factor_v2(ix_e, co_v1, co_v2);
if (factor < 0.0f) {
copy_v2_v2(e_cent, co_v1);
}
else if (factor > 1.0f) {
copy_v2_v2(e_cent, co_v2);
}
else {
copy_v2_v2(e_cent, ix_e);
}
}
#endif
/* Apply aspect before calculating cost. */
mul_v2_v2(f_a_cent, aspect_v2);
mul_v2_v2(f_b_cent, aspect_v2);
mul_v2_v2(e_cent, aspect_v2);
return step_cost_3_v2_ex(
f_a_cent, e_cent, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1]));
}
static float facetag_cut_cost_vert_uv(BMFace *f_a,
BMFace *f_b,
BMLoop *l_vert,
const void *const f_endpoints[2],
const float aspect_v2[2],
const int cd_loop_uv_offset)
{
float f_a_cent[2];
float f_b_cent[2];
float v_cent[2];
BM_face_uv_calc_center_median_weighted(f_a, aspect_v2, cd_loop_uv_offset, f_a_cent);
BM_face_uv_calc_center_median_weighted(f_b, aspect_v2, cd_loop_uv_offset, f_b_cent);
copy_v2_v2(v_cent, BM_ELEM_CD_GET_FLOAT_P(l_vert, cd_loop_uv_offset));
mul_v2_v2(f_a_cent, aspect_v2);
mul_v2_v2(f_b_cent, aspect_v2);
mul_v2_v2(v_cent, aspect_v2);
return step_cost_3_v2_ex(
f_a_cent, v_cent, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1]));
}
static void facetag_add_adjacent_uv(HeapSimple *heap,
BMFace *f_a,
BMFace **faces_prev,
float *cost,
const void *const f_endpoints[2],
const float aspect_v2[2],
const BMCalcPathUVParams *params)
{
const int cd_loop_uv_offset = params->cd_loop_uv_offset;
const int f_a_index = BM_elem_index_get(f_a);
/* Loop over faces of face, but do so by first looping over loops. */
{
BMIter liter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) {
BMLoop *l_first, *l_iter;
/* Check there is an adjacent face to loop over. */
if (l_a != l_a->radial_next) {
l_iter = l_first = l_a->radial_next;
do {
BMFace *f_b = l_iter->f;
if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) {
if (BM_loop_uv_share_edge_check(l_a, l_iter, cd_loop_uv_offset)) {
/* We know 'f_b' is not visited, check it out! */
const int f_b_index = BM_elem_index_get(f_b);
const float cost_cut =
params->use_topology_distance ?
1.0f :
facetag_cut_cost_edge_uv(
f_a, f_b, l_iter, f_endpoints, aspect_v2, cd_loop_uv_offset);
const float cost_new = cost[f_a_index] + cost_cut;
if (cost[f_b_index] > cost_new) {
cost[f_b_index] = cost_new;
faces_prev[f_b_index] = f_a;
BLI_heapsimple_insert(heap, cost_new, f_b);
}
}
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
}
if (params->use_step_face) {
BMIter liter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) {
BMIter litersub;
BMLoop *l_b;
BM_ITER_ELEM (l_b, &litersub, l_a->v, BM_LOOPS_OF_VERT) {
if ((l_a != l_b) && !BM_loop_share_edge_check(l_a, l_b)) {
BMFace *f_b = l_b->f;
if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) {
if (BM_loop_uv_share_vert_check(l_a, l_b, cd_loop_uv_offset)) {
/* We know 'f_b' is not visited, check it out! */
const int f_b_index = BM_elem_index_get(f_b);
const float cost_cut =
params->use_topology_distance ?
1.0f :
facetag_cut_cost_vert_uv(
f_a, f_b, l_a, f_endpoints, aspect_v2, cd_loop_uv_offset);
const float cost_new = cost[f_a_index] + cost_cut;
if (cost[f_b_index] > cost_new) {
cost[f_b_index] = cost_new;
faces_prev[f_b_index] = f_a;
BLI_heapsimple_insert(heap, cost_new, f_b);
}
}
}
}
}
}
}
}
LinkNode *BM_mesh_calc_path_uv_face(BMesh *bm,
BMFace *f_src,
BMFace *f_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMFace *, void *),
void *user_data)
{
const float aspect_v2[2] = {1.0f, 1.0f / params->aspect_y};
LinkNode *path = nullptr;
/* BM_ELEM_TAG flag is used to store visited edges */
BMIter fiter;
HeapSimple *heap;
float *cost;
BMFace **faces_prev;
int i = 0, totface;
/* Start measuring face path at the face edges, ignoring their centers. */
const void *const f_endpoints[2] = {f_src, f_dst};
/* NOTE: would pass BM_EDGE except we are looping over all faces anyway. */
// BM_mesh_elem_index_ensure(bm, BM_LOOP); /* NOTE: not needed for facetag. */
{
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
BM_elem_flag_set(f, BM_ELEM_TAG, !filter_fn(f, user_data));
BM_elem_index_set(f, i); /* set_inline */
i += 1;
}
bm->elem_index_dirty &= ~BM_FACE;
}
/* Allocate. */
totface = bm->totface;
faces_prev = MEM_new_array_zeroed<BMFace *>(totface, __func__);
cost = MEM_new_array_uninitialized<float>(totface, __func__);
std::fill_n(cost, totface, COST_INIT_MAX);
/* Regular dijkstra shortest path, but over UV faces instead of vertices. */
heap = BLI_heapsimple_new();
BLI_heapsimple_insert(heap, 0.0f, f_src);
cost[BM_elem_index_get(f_src)] = 0.0f;
BMFace *f = nullptr;
while (!BLI_heapsimple_is_empty(heap)) {
f = static_cast<BMFace *>(BLI_heapsimple_pop_min(heap));
if (f == f_dst) {
break;
}
if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
/* Adjacent loops are tagged while stepping to avoid 2x loops. */
BM_elem_flag_enable(f, BM_ELEM_TAG);
facetag_add_adjacent_uv(heap, f, faces_prev, cost, f_endpoints, aspect_v2, params);
}
}
if (f == f_dst) {
do {
BLI_linklist_prepend(&path, f);
} while ((f = faces_prev[BM_elem_index_get(f)]));
}
MEM_delete(faces_prev);
MEM_delete(cost);
BLI_heapsimple_free(heap, nullptr);
return path;
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,49 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_linklist.h"
#include "BLI_sys_types.h"
#include "bmesh_class.hh"
namespace blender {
struct BMCalcPathUVParams {
uint use_topology_distance : 1;
uint use_step_face : 1;
int cd_loop_uv_offset;
float aspect_y;
};
LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm,
BMLoop *l_src,
BMLoop *l_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMLoop *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 5);
LinkNode *BM_mesh_calc_path_uv_edge(BMesh *bm,
BMLoop *l_src,
BMLoop *l_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMLoop *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 5);
LinkNode *BM_mesh_calc_path_uv_face(BMesh *bm,
BMFace *f_src,
BMFace *f_dst,
const BMCalcPathUVParams *params,
bool (*filter_fn)(BMFace *, void *),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 5);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_sys_types.h"
#include "bmesh_class.hh"
namespace blender {
/**
* Take a face-region and return a list of matching face-regions.
*
* \param faces_region: A single, contiguous face-region.
* \return A list of matching null-terminated face-region arrays.
*/
int BM_mesh_region_match(BMesh *bm,
BMFace **faces_region,
uint faces_region_len,
ListBaseT<LinkData> *r_face_regions);
} // namespace blender

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh separate, disconnects a set of faces from all others,
* so they don't share any vertices/edges with other faces.
*/
#include "bmesh_separate.hh" /* own include */
#include "MEM_guardedalloc.h"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "intern/bmesh_structure.hh"
namespace blender {
void BM_mesh_separate_faces(BMesh *bm, BMFaceFilterFunc filter_fn, void *user_data)
{
BMFace **faces_array_all = MEM_new_array_uninitialized<BMFace *>(bm->totface, __func__);
/*
* - Create an array of faces based on 'filter_fn'.
* First part of array for match, for non-match.
*
* - Enable all vertex tags, then clear all tagged vertices from 'faces_b'.
*
* - Loop over 'faces_a', checking each vertex,
* splitting out any which aren't tagged (and therefor shared), disabling tags as we go.
*/
BMFace *f;
BMIter iter;
uint faces_a_len = 0;
uint faces_b_len = 0;
{
int i_a = 0;
int i_b = bm->totface;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
faces_array_all[filter_fn(f, user_data) ? i_a++ : --i_b] = f;
}
faces_a_len = i_a;
faces_b_len = bm->totface - i_a;
}
BMFace **faces_a = faces_array_all;
BMFace **faces_b = faces_array_all + faces_a_len;
/* Enable for all. */
BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
/* Disable vert tag on faces_b */
for (uint i = 0; i < faces_b_len; i++) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(faces_b[i]);
do {
BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
} while ((l_iter = l_iter->next) != l_first);
}
Vector<BMLoop *, 128> loop_split;
/* Check shared verts ('faces_a' tag and disable) */
for (uint i = 0; i < faces_a_len; i++) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(faces_a[i]);
do {
if (!BM_elem_flag_test(l_iter->v, BM_ELEM_TAG)) {
BMVert *v = l_iter->v;
/* Enable, since we may visit this vertex again on other faces */
BM_elem_flag_enable(v, BM_ELEM_TAG);
/* We know the vertex is shared, collect all vertices and split them off. */
/* Fill 'loop_split' */
{
BMEdge *e_first, *e_iter;
e_iter = e_first = l_iter->e;
do {
if (e_iter->l != nullptr) {
BMLoop *l_radial_first, *l_radial_iter;
l_radial_first = l_radial_iter = e_iter->l;
do {
if (l_radial_iter->v == v) {
if (filter_fn(l_radial_iter->f, user_data)) {
loop_split.append(l_radial_iter);
}
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first);
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
/* Perform the split */
BM_face_loop_separate_multi(bm, loop_split.data(), loop_split.size());
loop_split.clear();
}
} while ((l_iter = l_iter->next) != l_first);
}
MEM_delete(faces_array_all);
}
} // namespace blender

View File

@@ -0,0 +1,21 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Split all faces that match `filter_fn`.
* \note
*/
void BM_mesh_separate_faces(BMesh *bm, BMFaceFilterFunc filter_fn, void *user_data);
} // namespace blender

View File

@@ -0,0 +1,159 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Triangulate.
*/
#include "DNA_modifier_types.h" /* for MOD_TRIANGULATE_NGON_BEAUTY only */
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_heap.h"
#include "BLI_linklist.h"
#include "BLI_memarena.h"
/* only for defines */
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "bmesh.hh"
#include "bmesh_triangulate.hh" /* own include */
namespace blender {
/**
* a version of #BM_face_triangulate that maps to #BMOpSlot
*/
static void bm_face_triangulate_mapping(BMesh *bm,
BMFace *face,
const int quad_method,
const int ngon_method,
const bool use_tag,
BMOperator *op,
BMOpSlot *slot_facemap_out,
BMOpSlot *slot_facemap_double_out,
MemArena *pf_arena,
/* use for MOD_TRIANGULATE_NGON_BEAUTY only! */
Heap *pf_heap)
{
int faces_array_tot = face->len - 3;
Array<BMFace *, BM_DEFAULT_NGON_STACK_SIZE> faces_array(faces_array_tot);
LinkNode *faces_double = nullptr;
BLI_assert(face->len > 3);
BM_face_triangulate(bm,
face,
faces_array.data(),
&faces_array_tot,
nullptr,
nullptr,
&faces_double,
quad_method,
ngon_method,
use_tag,
pf_arena,
pf_heap);
if (faces_array_tot) {
int i;
BMO_slot_map_elem_insert(op, slot_facemap_out, face, face);
for (i = 0; i < faces_array_tot; i++) {
BMO_slot_map_elem_insert(op, slot_facemap_out, faces_array[i], face);
}
while (faces_double) {
LinkNode *next = faces_double->next;
BMO_slot_map_elem_insert(op, slot_facemap_double_out, faces_double->link, face);
MEM_delete(faces_double);
faces_double = next;
}
}
}
void BM_mesh_triangulate(BMesh *bm,
const int quad_method,
const int ngon_method,
const int min_vertices,
const bool tag_only,
BMOperator *op,
BMOpSlot *slot_facemap_out,
BMOpSlot *slot_facemap_double_out)
{
BMIter iter;
BMFace *face;
MemArena *pf_arena;
Heap *pf_heap;
pf_arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
if (ngon_method == MOD_TRIANGULATE_NGON_BEAUTY) {
pf_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
}
else {
pf_heap = nullptr;
}
if (slot_facemap_out) {
/* same as below but call: bm_face_triangulate_mapping() */
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (face->len >= min_vertices) {
if (tag_only == false || BM_elem_flag_test(face, BM_ELEM_TAG)) {
bm_face_triangulate_mapping(bm,
face,
quad_method,
ngon_method,
tag_only,
op,
slot_facemap_out,
slot_facemap_double_out,
pf_arena,
pf_heap);
}
}
}
}
else {
LinkNode *faces_double = nullptr;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (face->len >= min_vertices) {
if (tag_only == false || BM_elem_flag_test(face, BM_ELEM_TAG)) {
BM_face_triangulate(bm,
face,
nullptr,
nullptr,
nullptr,
nullptr,
&faces_double,
quad_method,
ngon_method,
tag_only,
pf_arena,
pf_heap);
}
}
}
while (faces_double) {
LinkNode *next = faces_double->next;
BM_face_kill(bm, static_cast<BMFace *>(faces_double->link));
MEM_delete(faces_double);
faces_double = next;
}
}
BLI_memarena_free(pf_arena);
if (ngon_method == MOD_TRIANGULATE_NGON_BEAUTY) {
BLI_heap_free(pf_heap, nullptr);
}
}
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Triangulate.
*/
#pragma once
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
namespace blender {
void BM_mesh_triangulate(BMesh *bm,
int quad_method,
int ngon_method,
int min_vertices,
bool tag_only,
BMOperator *op,
BMOpSlot *slot_facemap_out,
BMOpSlot *slot_facemap_double_out);
} // namespace blender

View File

@@ -0,0 +1,590 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Creates a solid wireframe from connected faces.
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "bmesh_wireframe.hh"
namespace blender {
static BMLoop *bm_edge_tag_faceloop(BMEdge *e)
{
BMLoop *l, *l_first;
l = l_first = e->l;
do {
if (BM_elem_flag_test(l->f, BM_ELEM_TAG)) {
return l;
}
} while ((l = l->radial_next) != l_first);
/* in the case this is used, we know this will never happen */
return nullptr;
}
static void bm_vert_boundary_tangent(
BMVert *v, float r_no[3], float r_no_face[3], BMVert **r_va_other, BMVert **r_vb_other)
{
BMIter iter;
BMEdge *e_iter;
BMEdge *e_a = nullptr, *e_b = nullptr;
BMVert *v_a, *v_b;
BMLoop *l_a, *l_b;
float no_face[3], no_edge[3];
float tvec_a[3], tvec_b[3];
/* get 2 boundary edges, there should only _be_ 2,
* in case there are more - results won't be valid of course */
BM_ITER_ELEM (e_iter, &iter, v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e_iter, BM_ELEM_TAG)) {
if (e_a == nullptr) {
e_a = e_iter;
}
else {
e_b = e_iter;
break;
}
}
}
if (e_a && e_b) {
/* NOTE: with an incorrectly flushed selection this can crash. */
l_a = bm_edge_tag_faceloop(e_a);
l_b = bm_edge_tag_faceloop(e_b);
/* average edge face normal */
add_v3_v3v3(no_face, l_a->f->no, l_b->f->no);
normalize_v3(no_face);
/* average edge direction */
v_a = BM_edge_other_vert(e_a, v);
v_b = BM_edge_other_vert(e_b, v);
sub_v3_v3v3(tvec_a, v->co, v_a->co);
sub_v3_v3v3(tvec_b, v_b->co, v->co);
normalize_v3(tvec_a);
normalize_v3(tvec_b);
add_v3_v3v3(no_edge, tvec_a, tvec_b); /* not unit length but this is ok */
/* check are we flipped the right way */
BM_edge_calc_face_tangent(e_a, l_a, tvec_a);
BM_edge_calc_face_tangent(e_b, l_b, tvec_b);
add_v3_v3(tvec_a, tvec_b);
*r_va_other = v_a;
*r_vb_other = v_b;
}
else {
/* degenerate case - vertex connects a boundary edged face to other faces,
* so we have only one boundary face - only use it for calculations */
l_a = bm_edge_tag_faceloop(e_a);
copy_v3_v3(no_face, l_a->f->no);
/* edge direction */
v_a = BM_edge_other_vert(e_a, v);
v_b = nullptr;
sub_v3_v3v3(no_edge, v->co, v_a->co);
/* check are we flipped the right way */
BM_edge_calc_face_tangent(e_a, l_a, tvec_a);
*r_va_other = nullptr;
*r_vb_other = nullptr;
}
/* find the normal */
cross_v3_v3v3(r_no, no_edge, no_face);
normalize_v3(r_no);
if (dot_v3v3(r_no, tvec_a) > 0.0f) {
negate_v3(r_no);
}
copy_v3_v3(r_no_face, no_face);
}
/* check if we are the only tagged loop-face around this edge */
static bool bm_loop_is_radial_boundary(BMLoop *l_first)
{
BMLoop *l = l_first->radial_next;
if (l == l_first) {
return true; /* a real boundary */
}
do {
if (BM_elem_flag_test(l->f, BM_ELEM_TAG)) {
return false;
}
} while ((l = l->radial_next) != l_first);
return true;
}
void BM_mesh_wireframe(BMesh *bm,
const float offset,
const float offset_fac,
const float offset_fac_vg,
const bool use_replace,
const bool use_boundary,
const bool use_even_offset,
const bool use_relative_offset,
const bool use_crease,
const float crease_weight,
const int defgrp_index,
const bool defgrp_invert,
const short mat_offset,
const int mat_max,
/* for operators */
const bool use_tag)
{
const float ofs_orig = -(((-offset_fac + 1.0f) * 0.5f) * offset);
const float ofs_new = offset + ofs_orig;
const float ofs_mid = (ofs_orig + ofs_new) / 2.0f;
const float inset = offset / 2.0f;
int cd_edge_crease_offset = use_crease ? CustomData_get_offset_named(
&bm->edata, CD_PROP_FLOAT, "crease_edge") :
-1;
const int cd_dvert_offset = (defgrp_index != -1) ?
CustomData_get_offset(&bm->vdata, CD_MDEFORMVERT) :
-1;
const float offset_fac_vg_inv = 1.0f - offset_fac_vg;
const int totvert_orig = bm->totvert;
BMIter iter;
BMIter itersub;
/* filled only with boundary verts */
BMVert **verts_src = MEM_new_array_uninitialized<BMVert *>(totvert_orig, __func__);
BMVert **verts_neg = MEM_new_array_uninitialized<BMVert *>(totvert_orig, __func__);
BMVert **verts_pos = MEM_new_array_uninitialized<BMVert *>(totvert_orig, __func__);
/* Will over-allocate, but makes for easy lookups by index to keep aligned. */
BMVert **verts_boundary = use_boundary ?
MEM_new_array_uninitialized<BMVert *>(totvert_orig, __func__) :
nullptr;
float *verts_relfac = (use_relative_offset || (cd_dvert_offset != -1)) ?
MEM_new_array_uninitialized<float>(totvert_orig, __func__) :
nullptr;
/* May over-allocate if not all faces have wire. */
BMVert **verts_loop;
int verts_loop_tot = 0;
BMVert *v_src;
BMFace *f_src;
BMLoop *l;
float tvec[3];
float fac, fac_shell;
int i;
if (use_crease && cd_edge_crease_offset == -1) {
BM_data_layer_add_named(bm, &bm->edata, CD_PROP_FLOAT, "crease_edge");
cd_edge_crease_offset = CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "crease_edge");
}
BM_ITER_MESH_INDEX (v_src, &iter, bm, BM_VERTS_OF_MESH, i) {
BM_elem_index_set(v_src, i); /* set_inline */
verts_src[i] = v_src;
BM_elem_flag_disable(v_src, BM_ELEM_TAG);
}
bm->elem_index_dirty &= ~BM_VERT;
/* setup tags, all faces and verts will be tagged which will be duplicated */
BM_ITER_MESH_INDEX (f_src, &iter, bm, BM_FACES_OF_MESH, i) {
BM_elem_index_set(f_src, i); /* set_inline */
if (use_tag) {
if (!BM_elem_flag_test(f_src, BM_ELEM_TAG)) {
continue;
}
}
else {
BM_elem_flag_enable(f_src, BM_ELEM_TAG);
}
verts_loop_tot += f_src->len;
BM_ITER_ELEM (l, &itersub, f_src, BM_LOOPS_OF_FACE) {
BM_elem_flag_enable(l->v, BM_ELEM_TAG);
/* also tag boundary edges */
BM_elem_flag_set(l->e, BM_ELEM_TAG, bm_loop_is_radial_boundary(l));
}
}
bm->elem_index_dirty &= ~BM_FACE;
/* duplicate tagged verts */
for (i = 0; i < totvert_orig; i++) {
v_src = verts_src[i];
if (BM_elem_flag_test(v_src, BM_ELEM_TAG)) {
fac = 1.0f;
if (verts_relfac) {
if (use_relative_offset) {
verts_relfac[i] = BM_vert_calc_median_tagged_edge_length(v_src);
}
else {
verts_relfac[i] = 1.0f;
}
if (cd_dvert_offset != -1) {
MDeformVert *dvert = static_cast<MDeformVert *>(
BM_ELEM_CD_GET_VOID_P(v_src, cd_dvert_offset));
float defgrp_fac = BKE_defvert_find_weight(dvert, defgrp_index);
if (defgrp_invert) {
defgrp_fac = 1.0f - defgrp_fac;
}
if (offset_fac_vg > 0.0f) {
defgrp_fac = (offset_fac_vg + (defgrp_fac * offset_fac_vg_inv));
}
verts_relfac[i] *= defgrp_fac;
}
fac *= verts_relfac[i];
}
verts_neg[i] = BM_vert_create(bm, nullptr, v_src, BM_CREATE_NOP);
verts_pos[i] = BM_vert_create(bm, nullptr, v_src, BM_CREATE_NOP);
if (offset == 0.0f) {
madd_v3_v3v3fl(verts_neg[i]->co, v_src->co, v_src->no, ofs_orig * fac);
madd_v3_v3v3fl(verts_pos[i]->co, v_src->co, v_src->no, ofs_new * fac);
}
else {
madd_v3_v3v3fl(tvec, v_src->co, v_src->no, ofs_mid * fac);
madd_v3_v3v3fl(verts_neg[i]->co, tvec, v_src->no, (ofs_orig - ofs_mid) * fac);
madd_v3_v3v3fl(verts_pos[i]->co, tvec, v_src->no, (ofs_new - ofs_mid) * fac);
}
}
else {
/* could skip this */
verts_neg[i] = nullptr;
verts_pos[i] = nullptr;
}
/* conflicts with BM_vert_calc_median_tagged_edge_length */
if (use_relative_offset == false) {
BM_elem_flag_disable(v_src, BM_ELEM_TAG);
}
}
if (use_relative_offset) {
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
}
verts_loop = MEM_new_array_uninitialized<BMVert *>(verts_loop_tot, __func__);
verts_loop_tot = 0; /* count up again */
BM_ITER_MESH (f_src, &iter, bm, BM_FACES_OF_MESH) {
if (use_tag && !BM_elem_flag_test(f_src, BM_ELEM_TAG)) {
continue;
}
BM_ITER_ELEM (l, &itersub, f_src, BM_LOOPS_OF_FACE) {
/* Because some faces might be skipped! */
BM_elem_index_set(l, verts_loop_tot); /* set_dirty */
BM_loop_calc_face_tangent(l, tvec);
/* create offset vert */
fac = 1.0f;
if (verts_relfac) {
fac *= verts_relfac[BM_elem_index_get(l->v)];
}
fac_shell = fac;
if (use_even_offset) {
fac_shell *= shell_angle_to_dist((float(M_PI) - BM_loop_calc_face_angle(l)) * 0.5f);
}
madd_v3_v3v3fl(tvec, l->v->co, tvec, inset * fac_shell);
if (offset != 0.0f) {
madd_v3_v3fl(tvec, l->v->no, ofs_mid * fac);
}
verts_loop[verts_loop_tot] = BM_vert_create(bm, tvec, l->v, BM_CREATE_NOP);
if (use_boundary) {
if (BM_elem_flag_test(l->e, BM_ELEM_TAG)) { /* is this a boundary? */
BMVert *v_pair[2] = {l->v, l->next->v};
for (i = 0; i < 2; i++) {
BMVert *v_boundary = v_pair[i];
if (!BM_elem_flag_test(v_boundary, BM_ELEM_TAG)) {
const int v_boundary_index = BM_elem_index_get(v_boundary);
float no_face[3];
BMVert *va_other;
BMVert *vb_other;
BM_elem_flag_enable(v_boundary, BM_ELEM_TAG);
bm_vert_boundary_tangent(v_boundary, tvec, no_face, &va_other, &vb_other);
/* create offset vert */
/* similar to code above but different angle calc */
fac = 1.0f;
if (verts_relfac) {
fac *= verts_relfac[v_boundary_index];
}
fac_shell = fac;
if (use_even_offset) {
if (va_other) { /* for verts with only one boundary edge - this will be nullptr */
fac_shell *= shell_angle_to_dist(
(float(M_PI) - angle_on_axis_v3v3v3_v3(
va_other->co, v_boundary->co, vb_other->co, no_face)) *
0.5f);
}
}
madd_v3_v3v3fl(tvec, v_boundary->co, tvec, inset * fac_shell);
if (offset != 0.0f) {
madd_v3_v3fl(tvec, v_boundary->no, ofs_mid * fac);
}
verts_boundary[v_boundary_index] = BM_vert_create(
bm, tvec, v_boundary, BM_CREATE_NOP);
}
}
}
}
verts_loop_tot++;
}
}
bm->elem_index_dirty |= BM_LOOP;
BM_ITER_MESH (f_src, &iter, bm, BM_FACES_OF_MESH) {
/* skip recently added faces */
if (BM_elem_index_get(f_src) == -1) {
continue;
}
if (use_tag && !BM_elem_flag_test(f_src, BM_ELEM_TAG)) {
continue;
}
BM_elem_flag_disable(f_src, BM_ELEM_TAG);
BM_ITER_ELEM (l, &itersub, f_src, BM_LOOPS_OF_FACE) {
BMFace *f_new;
BMLoop *l_new;
BMLoop *l_next = l->next;
BMVert *v_l1 = verts_loop[BM_elem_index_get(l)];
BMVert *v_l2 = verts_loop[BM_elem_index_get(l_next)];
BMVert *v_src_l1 = l->v;
BMVert *v_src_l2 = l_next->v;
const int i_1 = BM_elem_index_get(v_src_l1);
const int i_2 = BM_elem_index_get(v_src_l2);
BMVert *v_neg1 = verts_neg[i_1];
BMVert *v_neg2 = verts_neg[i_2];
BMVert *v_pos1 = verts_pos[i_1];
BMVert *v_pos2 = verts_pos[i_2];
f_new = BM_face_create_quad_tri(bm, v_l1, v_l2, v_neg2, v_neg1, f_src, BM_CREATE_NOP);
if (mat_offset) {
f_new->mat_nr = std::clamp(f_new->mat_nr + mat_offset, 0, mat_max);
}
BM_elem_flag_enable(f_new, BM_ELEM_TAG);
l_new = BM_FACE_FIRST_LOOP(f_new);
BM_elem_attrs_copy(bm, l, l_new);
BM_elem_attrs_copy(bm, l, l_new->prev);
BM_elem_attrs_copy(bm, l_next, l_new->next);
BM_elem_attrs_copy(bm, l_next, l_new->next->next);
f_new = BM_face_create_quad_tri(bm, v_l2, v_l1, v_pos1, v_pos2, f_src, BM_CREATE_NOP);
if (mat_offset) {
f_new->mat_nr = std::clamp(f_new->mat_nr + mat_offset, 0, mat_max);
}
BM_elem_flag_enable(f_new, BM_ELEM_TAG);
l_new = BM_FACE_FIRST_LOOP(f_new);
BM_elem_attrs_copy(bm, l_next, l_new);
BM_elem_attrs_copy(bm, l_next, l_new->prev);
BM_elem_attrs_copy(bm, l, l_new->next);
BM_elem_attrs_copy(bm, l, l_new->next->next);
if (use_boundary) {
if (BM_elem_flag_test(l->e, BM_ELEM_TAG)) {
/* we know its a boundary and this is the only face user (which is being wire'd) */
/* we know we only touch this edge/face once */
BMVert *v_b1 = verts_boundary[i_1];
BMVert *v_b2 = verts_boundary[i_2];
f_new = BM_face_create_quad_tri(bm, v_b2, v_b1, v_neg1, v_neg2, f_src, BM_CREATE_NOP);
if (mat_offset) {
f_new->mat_nr = std::clamp(f_new->mat_nr + mat_offset, 0, mat_max);
}
BM_elem_flag_enable(f_new, BM_ELEM_TAG);
l_new = BM_FACE_FIRST_LOOP(f_new);
BM_elem_attrs_copy(bm, l_next, l_new);
BM_elem_attrs_copy(bm, l_next, l_new->prev);
BM_elem_attrs_copy(bm, l, l_new->next);
BM_elem_attrs_copy(bm, l, l_new->next->next);
f_new = BM_face_create_quad_tri(bm, v_b1, v_b2, v_pos2, v_pos1, f_src, BM_CREATE_NOP);
if (mat_offset) {
f_new->mat_nr = std::clamp(f_new->mat_nr + mat_offset, 0, mat_max);
}
BM_elem_flag_enable(f_new, BM_ELEM_TAG);
l_new = BM_FACE_FIRST_LOOP(f_new);
BM_elem_attrs_copy(bm, l, l_new);
BM_elem_attrs_copy(bm, l, l_new->prev);
BM_elem_attrs_copy(bm, l_next, l_new->next);
BM_elem_attrs_copy(bm, l_next, l_new->next->next);
if (use_crease) {
BMEdge *e_new;
e_new = BM_edge_exists(v_pos1, v_b1);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_pos2, v_b2);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_neg1, v_b1);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_neg2, v_b2);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
}
}
}
if (use_crease) {
BMEdge *e_new;
e_new = BM_edge_exists(v_pos1, v_l1);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_pos2, v_l2);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_neg1, v_l1);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
e_new = BM_edge_exists(v_neg2, v_l2);
BM_ELEM_CD_SET_FLOAT(e_new, cd_edge_crease_offset, crease_weight);
}
}
}
if (use_boundary) {
MEM_delete(verts_boundary);
}
if (verts_relfac) {
MEM_delete(verts_relfac);
}
if (use_replace) {
if (use_tag) {
/* only remove faces which are original and used to make wire,
* use 'verts_pos' and 'verts_neg' to avoid a feedback loop. */
/* vertex must be from 'verts_src' */
#define VERT_DUPE_TEST_ORIG(v) (verts_neg[BM_elem_index_get(v)] != nullptr)
#define VERT_DUPE_TEST(v) (verts_pos[BM_elem_index_get(v)] != nullptr)
#define VERT_DUPE_CLEAR(v) \
{ \
verts_pos[BM_elem_index_get(v)] = nullptr; \
} \
(void)0
/* first ensure we keep all verts which are used in faces that weren't
* entirely made into wire. */
BM_ITER_MESH (f_src, &iter, bm, BM_FACES_OF_MESH) {
int mix_flag = 0;
BMLoop *l_iter, *l_first;
/* skip new faces */
if (BM_elem_index_get(f_src) == -1) {
continue;
}
l_iter = l_first = BM_FACE_FIRST_LOOP(f_src);
do {
mix_flag |= (VERT_DUPE_TEST_ORIG(l_iter->v) ? 1 : 2);
if (mix_flag == (1 | 2)) {
break;
}
} while ((l_iter = l_iter->next) != l_first);
if (mix_flag == (1 | 2)) {
l_iter = l_first = BM_FACE_FIRST_LOOP(f_src);
do {
VERT_DUPE_CLEAR(l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* now remove any verts which were made into wire by all faces */
for (i = 0; i < totvert_orig; i++) {
v_src = verts_src[i];
BLI_assert(i == BM_elem_index_get(v_src));
if (VERT_DUPE_TEST(v_src)) {
BM_vert_kill(bm, v_src);
}
}
#undef VERT_DUPE_TEST_ORIG
#undef VERT_DUPE_TEST
#undef VERT_DUPE_CLEAR
}
else {
/* simple case, no tags - replace all */
for (i = 0; i < totvert_orig; i++) {
BM_vert_kill(bm, verts_src[i]);
}
}
}
MEM_delete(verts_src);
MEM_delete(verts_neg);
MEM_delete(verts_pos);
MEM_delete(verts_loop);
}
} // namespace blender

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Wire Frame.
*/
#pragma once
#include "bmesh_class.hh"
namespace blender {
/**
* \param defgrp_index: Vertex group index, -1 for no vertex groups.
*
* \note All edge tags must be cleared.
* \note Behavior matches `MOD_solidify.cc`.
*/
void BM_mesh_wireframe(BMesh *bm,
float offset,
float offset_fac,
float offset_fac_vg,
bool use_replace,
bool use_boundary,
bool use_even_offset,
bool use_relative_offset,
bool use_crease,
float crease_weight,
int defgrp_index,
bool defgrp_invert,
short mat_offset,
int mat_max,
bool use_tag);
} // namespace blender