Add Chromium-only Blender WebEngine parity work
This commit is contained in:
399
blender-5.2.0/source/blender/bmesh/tools/bmesh_beautify.cc
Normal file
399
blender-5.2.0/source/blender/bmesh/tools/bmesh_beautify.cc
Normal 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
|
||||
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_beautify.hh
Normal file
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_beautify.hh
Normal 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
|
||||
8485
blender-5.2.0/source/blender/bmesh/tools/bmesh_bevel.cc
Normal file
8485
blender-5.2.0/source/blender/bmesh/tools/bmesh_bevel.cc
Normal file
File diff suppressed because it is too large
Load Diff
55
blender-5.2.0/source/blender/bmesh/tools/bmesh_bevel.hh
Normal file
55
blender-5.2.0/source/blender/bmesh/tools/bmesh_bevel.hh
Normal 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
|
||||
560
blender-5.2.0/source/blender/bmesh/tools/bmesh_bisect_plane.cc
Normal file
560
blender-5.2.0/source/blender/bmesh/tools/bmesh_bisect_plane.cc
Normal 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
|
||||
@@ -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
|
||||
498
blender-5.2.0/source/blender/bmesh/tools/bmesh_boolean.cc
Normal file
498
blender-5.2.0/source/blender/bmesh/tools/bmesh_boolean.cc
Normal 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
|
||||
49
blender-5.2.0/source/blender/bmesh/tools/bmesh_boolean.hh
Normal file
49
blender-5.2.0/source/blender/bmesh/tools/bmesh_boolean.hh
Normal 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
|
||||
59
blender-5.2.0/source/blender/bmesh/tools/bmesh_decimate.hh
Normal file
59
blender-5.2.0/source/blender/bmesh/tools/bmesh_decimate.hh
Normal 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
|
||||
1550
blender-5.2.0/source/blender/bmesh/tools/bmesh_decimate_collapse.cc
Normal file
1550
blender-5.2.0/source/blender/bmesh/tools/bmesh_decimate_collapse.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
482
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgenet.cc
Normal file
482
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgenet.cc
Normal 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
|
||||
26
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgenet.hh
Normal file
26
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgenet.hh
Normal 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
|
||||
120
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgesplit.cc
Normal file
120
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgesplit.cc
Normal 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
|
||||
22
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgesplit.hh
Normal file
22
blender-5.2.0/source/blender/bmesh/tools/bmesh_edgesplit.hh
Normal 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
|
||||
1670
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect.cc
Normal file
1670
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect.cc
Normal file
File diff suppressed because it is too large
Load Diff
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect.hh
Normal file
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect.hh
Normal 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
|
||||
1057
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect_edges.cc
Normal file
1057
blender-5.2.0/source/blender/bmesh/tools/bmesh_intersect_edges.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
596
blender-5.2.0/source/blender/bmesh/tools/bmesh_path.cc
Normal file
596
blender-5.2.0/source/blender/bmesh/tools/bmesh_path.cc
Normal 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
|
||||
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_path.hh
Normal file
46
blender-5.2.0/source/blender/bmesh/tools/bmesh_path.hh
Normal 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
|
||||
482
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_region.cc
Normal file
482
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_region.cc
Normal 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
|
||||
@@ -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
|
||||
505
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_region_uv.cc
Normal file
505
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_region_uv.cc
Normal 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
|
||||
@@ -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
|
||||
619
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_uv.cc
Normal file
619
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_uv.cc
Normal 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
|
||||
49
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_uv.hh
Normal file
49
blender-5.2.0/source/blender/bmesh/tools/bmesh_path_uv.hh
Normal 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
|
||||
1461
blender-5.2.0/source/blender/bmesh/tools/bmesh_region_match.cc
Normal file
1461
blender-5.2.0/source/blender/bmesh/tools/bmesh_region_match.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
110
blender-5.2.0/source/blender/bmesh/tools/bmesh_separate.cc
Normal file
110
blender-5.2.0/source/blender/bmesh/tools/bmesh_separate.cc
Normal 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
|
||||
21
blender-5.2.0/source/blender/bmesh/tools/bmesh_separate.hh
Normal file
21
blender-5.2.0/source/blender/bmesh/tools/bmesh_separate.hh
Normal 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
|
||||
159
blender-5.2.0/source/blender/bmesh/tools/bmesh_triangulate.cc
Normal file
159
blender-5.2.0/source/blender/bmesh/tools/bmesh_triangulate.cc
Normal 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
|
||||
@@ -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
|
||||
590
blender-5.2.0/source/blender/bmesh/tools/bmesh_wireframe.cc
Normal file
590
blender-5.2.0/source/blender/bmesh/tools/bmesh_wireframe.cc
Normal 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
|
||||
39
blender-5.2.0/source/blender/bmesh/tools/bmesh_wireframe.hh
Normal file
39
blender-5.2.0/source/blender/bmesh/tools/bmesh_wireframe.hh
Normal 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
|
||||
Reference in New Issue
Block a user