Files
test/source/blender/blenkernel/intern/pbvh_bmesh.cc
Sergey Sharybin 92a19747f0 Sculpt: Add timing information for BMesh PBVH
The goal is to make it possible to have more or less reliable way
to perform performance comparisons of changes in algorithms used
in dynamic topology.

This change allows to run Blender with the following arguments:

  blender --log "sculpt.detail,pbvh.bmesh" --log-level 2

and see timing of dynamic topology flood-fill operation, as well as
individual timings of edge subdivision/collapsing.

Pull Request: https://projects.blender.org/blender/blender/pulls/114099
2023-10-24 14:37:54 +02:00

2242 lines
66 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_heap_simple.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_memarena.h"
#include "BLI_span.hh"
#include "BLI_utildefines.h"
#include "BKE_DerivedMesh.h"
#include "BKE_ccg.h"
#include "BKE_pbvh_api.hh"
#include "DRW_pbvh.hh"
#include "bmesh.h"
#include "pbvh_intern.hh"
#include "PIL_time.h"
#include "CLG_log.h"
static CLG_LogRef LOG = {"pbvh.bmesh"};
using blender::Array;
using blender::IndexRange;
using blender::Span;
using blender::Vector;
/* Avoid skinny faces */
#define USE_EDGEQUEUE_EVEN_SUBDIV
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
# include "BKE_global.h"
#endif
/* Support for only operating on front-faces. */
#define USE_EDGEQUEUE_FRONTFACE
/* Don't add edges into the queue multiple times. */
#define USE_EDGEQUEUE_TAG
/**
* Ensure we don't have dirty tags for the edge queue, and that they are left cleared.
* (slow, even for debug mode, so leave disabled for now).
*/
#if defined(USE_EDGEQUEUE_TAG) && 0
# if !defined(NDEBUG)
# define USE_EDGEQUEUE_TAG_VERIFY
# endif
#endif
// #define USE_VERIFY
#ifdef USE_VERIFY
static void pbvh_bmesh_verify(PBVH *pbvh);
#endif
/* -------------------------------------------------------------------- */
/** \name BMesh Utility API
*
* Use some local functions which assume triangles.
* \{ */
/**
* Typically using BM_LOOPS_OF_VERT and BM_FACES_OF_VERT iterators are fine,
* however this is an area where performance matters so do it in-line.
*
* Take care since 'break' won't works as expected within these macros!
*/
#define BM_LOOPS_OF_VERT_ITER_BEGIN(l_iter_radial_, v_) \
{ \
struct { \
BMVert *v; \
BMEdge *e_iter, *e_first; \
BMLoop *l_iter_radial; \
} _iter; \
_iter.v = v_; \
if (_iter.v->e) { \
_iter.e_iter = _iter.e_first = _iter.v->e; \
do { \
if (_iter.e_iter->l) { \
_iter.l_iter_radial = _iter.e_iter->l; \
do { \
if (_iter.l_iter_radial->v == _iter.v) { \
l_iter_radial_ = _iter.l_iter_radial;
#define BM_LOOPS_OF_VERT_ITER_END \
} \
} \
while ((_iter.l_iter_radial = _iter.l_iter_radial->radial_next) != _iter.e_iter->l) \
; \
} \
} \
while ((_iter.e_iter = BM_DISK_EDGE_NEXT(_iter.e_iter, _iter.v)) != _iter.e_first) \
; \
} \
} \
((void)0)
#define BM_FACES_OF_VERT_ITER_BEGIN(f_iter_, v_) \
{ \
BMLoop *l_iter_radial_; \
BM_LOOPS_OF_VERT_ITER_BEGIN (l_iter_radial_, v_) { \
f_iter_ = l_iter_radial_->f;
#define BM_FACES_OF_VERT_ITER_END \
} \
BM_LOOPS_OF_VERT_ITER_END; \
} \
((void)0)
static std::array<BMEdge *, 3> bm_edges_from_tri(BMesh *bm, const blender::Span<BMVert *> v_tri)
{
return {
BM_edge_create(bm, v_tri[0], v_tri[1], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, v_tri[1], v_tri[2], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, v_tri[2], v_tri[0], nullptr, BM_CREATE_NO_DOUBLE),
};
}
BLI_INLINE void bm_face_as_array_index_tri(BMFace *f, int r_index[3])
{
BMLoop *l = BM_FACE_FIRST_LOOP(f);
BLI_assert(f->len == 3);
r_index[0] = BM_elem_index_get(l->v);
l = l->next;
r_index[1] = BM_elem_index_get(l->v);
l = l->next;
r_index[2] = BM_elem_index_get(l->v);
}
/**
* A version of #BM_face_exists, optimized for triangles
* when we know the loop and the opposite vertex.
*
* Check if any triangle is formed by (l_radial_first->v, l_radial_first->next->v, v_opposite),
* at either winding (since its a triangle no special checks are needed).
*
* <pre>
* l_radial_first->v & l_radial_first->next->v
* +---+
* | /
* | /
* + v_opposite
* </pre>
*
* Its assumed that \a l_radial_first is never forming the target face.
*/
static BMFace *bm_face_exists_tri_from_loop_vert(BMLoop *l_radial_first, BMVert *v_opposite)
{
BLI_assert(
!ELEM(v_opposite, l_radial_first->v, l_radial_first->next->v, l_radial_first->prev->v));
if (l_radial_first->radial_next != l_radial_first) {
BMLoop *l_radial_iter = l_radial_first->radial_next;
do {
BLI_assert(l_radial_iter->f->len == 3);
if (l_radial_iter->prev->v == v_opposite) {
return l_radial_iter->f;
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first);
}
return nullptr;
}
/**
* Uses a map of vertices to lookup the final target.
* References can't point to previous items (would cause infinite loop).
*/
static BMVert *bm_vert_hash_lookup_chain(GHash *deleted_verts, BMVert *v)
{
while (true) {
BMVert **v_next_p = (BMVert **)BLI_ghash_lookup_p(deleted_verts, v);
if (v_next_p == nullptr) {
/* Not remapped. */
return v;
}
if (*v_next_p == nullptr) {
/* Removed and not remapped. */
return nullptr;
}
/* Remapped. */
v = *v_next_p;
}
}
/** \} */
/****************************** Building ******************************/
/* Update node data after splitting. */
static void pbvh_bmesh_node_finalize(PBVH *pbvh,
const int node_index,
const int cd_vert_node_offset,
const int cd_face_node_offset)
{
PBVHNode *n = &pbvh->nodes[node_index];
bool has_visible = false;
BB_reset(&n->vb);
for (BMFace *f : n->bm_faces) {
/* Update ownership of faces. */
BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index);
/* Update vertices. */
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BMVert *v = l_iter->v;
if (!n->bm_unique_verts.contains(v)) {
if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) {
n->bm_other_verts.add(v);
}
else {
n->bm_unique_verts.add(v);
BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index);
}
}
/* Update node bounding box. */
BB_expand(&n->vb, v->co);
} while ((l_iter = l_iter->next) != l_first);
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
has_visible = true;
}
}
BLI_assert(n->vb.bmin[0] <= n->vb.bmax[0] && n->vb.bmin[1] <= n->vb.bmax[1] &&
n->vb.bmin[2] <= n->vb.bmax[2]);
n->orig_vb = n->vb;
/* Build GPU buffers for new node and update vertex normals. */
BKE_pbvh_node_mark_rebuild_draw(n);
BKE_pbvh_node_fully_hidden_set(n, !has_visible);
n->flag |= PBVH_UpdateNormals;
}
/* Recursively split the node if it exceeds the leaf_limit. */
static void pbvh_bmesh_node_split(PBVH *pbvh, const Span<BBC> bbc_array, int node_index)
{
const int cd_vert_node_offset = pbvh->cd_vert_node_offset;
const int cd_face_node_offset = pbvh->cd_face_node_offset;
PBVHNode *n = &pbvh->nodes[node_index];
if (n->bm_faces.size() <= pbvh->leaf_limit) {
/* Node limit not exceeded. */
pbvh_bmesh_node_finalize(pbvh, node_index, cd_vert_node_offset, cd_face_node_offset);
return;
}
/* Calculate bounding box around primitive centroids. */
BB cb;
BB_reset(&cb);
for (BMFace *f : n->bm_faces) {
const BBC *bbc = &bbc_array[BM_elem_index_get(f)];
BB_expand(&cb, bbc->bcentroid);
}
/* Find widest axis and its midpoint. */
const int axis = BB_widest_axis(&cb);
const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f;
/* Add two new child nodes. */
const int children = pbvh->nodes.size();
n->children_offset = children;
pbvh_grow_nodes(pbvh, pbvh->nodes.size() + 2);
/* Array reallocated, update current node pointer. */
n = &pbvh->nodes[node_index];
/* Initialize children */
PBVHNode *c1 = &pbvh->nodes[children], *c2 = &pbvh->nodes[children + 1];
c1->flag |= PBVH_Leaf;
c2->flag |= PBVH_Leaf;
c1->bm_faces.reserve(n->bm_faces.size() / 2);
c2->bm_faces.reserve(n->bm_faces.size() / 2);
/* Partition the parent node's faces between the two children. */
for (BMFace *f : n->bm_faces) {
const BBC *bbc = &bbc_array[BM_elem_index_get(f)];
if (bbc->bcentroid[axis] < mid) {
c1->bm_faces.add(f);
}
else {
c2->bm_faces.add(f);
}
}
/* Enforce at least one primitive in each node */
blender::Set<BMFace *, 0> *empty = nullptr;
blender::Set<BMFace *, 0> *other;
if (c1->bm_faces.is_empty()) {
empty = &c1->bm_faces;
other = &c2->bm_faces;
}
else if (c2->bm_faces.is_empty()) {
empty = &c2->bm_faces;
other = &c1->bm_faces;
}
if (empty) {
for (BMFace *f : *other) {
empty->add(f);
other->remove(f);
break;
}
}
/* Clear this node */
/* Mark this node's unique verts as unclaimed. */
for (BMVert *v : n->bm_unique_verts) {
BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE);
}
/* Unclaim faces. */
for (BMFace *f : n->bm_faces) {
BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE);
}
n->bm_faces.clear_and_shrink();
if (n->layer_disp) {
MEM_freeN(n->layer_disp);
}
n->layer_disp = nullptr;
if (n->draw_batches) {
DRW_pbvh_node_free(n->draw_batches);
}
n->flag &= ~PBVH_Leaf;
/* Recurse. */
pbvh_bmesh_node_split(pbvh, bbc_array, children);
pbvh_bmesh_node_split(pbvh, bbc_array, children + 1);
/* Array maybe reallocated, update current node pointer */
n = &pbvh->nodes[node_index];
/* Update bounding box. */
BB_reset(&n->vb);
BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset].vb);
BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset + 1].vb);
n->orig_vb = n->vb;
}
/* Recursively split the node if it exceeds the leaf_limit. */
static bool pbvh_bmesh_node_limit_ensure(PBVH *pbvh, int node_index)
{
PBVHNode &node = pbvh->nodes[node_index];
const int faces_num = node.bm_faces.size();
if (faces_num <= pbvh->leaf_limit) {
/* Node limit not exceeded */
return false;
}
/* Trigger draw manager cache invalidation. */
pbvh->draw_cache_invalid = true;
/* For each BMFace, store the AABB and AABB centroid. */
Array<BBC> bbc_array(faces_num);
int i = 0;
for (BMFace *f : node.bm_faces) {
BBC *bbc = &bbc_array[i];
BB_reset((BB *)bbc);
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BB_expand((BB *)bbc, l_iter->v->co);
} while ((l_iter = l_iter->next) != l_first);
BBC_update_centroid(bbc);
/* So we can do direct lookups on 'bbc_array'. */
BM_elem_index_set(f, i); /* set_dirty! */
i++;
}
/* Likely this is already dirty. */
pbvh->header.bm->elem_index_dirty |= BM_FACE;
pbvh_bmesh_node_split(pbvh, bbc_array, node_index);
return true;
}
/**********************************************************************/
BLI_INLINE int pbvh_bmesh_node_index_from_vert(PBVH *pbvh, const BMVert *key)
{
const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_vert_node_offset);
BLI_assert(node_index != DYNTOPO_NODE_NONE);
BLI_assert(node_index < pbvh->nodes.size());
return node_index;
}
BLI_INLINE int pbvh_bmesh_node_index_from_face(PBVH *pbvh, const BMFace *key)
{
const int node_index = BM_ELEM_CD_GET_INT((const BMElem *)key, pbvh->cd_face_node_offset);
BLI_assert(node_index != DYNTOPO_NODE_NONE);
BLI_assert(node_index < pbvh->nodes.size());
return node_index;
}
BLI_INLINE PBVHNode *pbvh_bmesh_node_from_vert(PBVH *pbvh, const BMVert *key)
{
return &pbvh->nodes[pbvh_bmesh_node_index_from_vert(pbvh, key)];
}
BLI_INLINE PBVHNode *pbvh_bmesh_node_from_face(PBVH *pbvh, const BMFace *key)
{
return &pbvh->nodes[pbvh_bmesh_node_index_from_face(pbvh, key)];
}
static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh,
const BMVert *v1,
const BMVert *v2,
const int node_index,
const float co[3],
const float no[3],
const int cd_vert_mask_offset)
{
PBVHNode *node = &pbvh->nodes[node_index];
BLI_assert((pbvh->nodes.size() == 1 || node_index) && node_index <= pbvh->nodes.size());
/* Avoid initializing customdata because its quite involved. */
BMVert *v = BM_vert_create(pbvh->header.bm, co, nullptr, BM_CREATE_NOP);
BM_data_interp_from_verts(pbvh->header.bm, v1, v2, v, 0.5f);
/* This value is logged below. */
copy_v3_v3(v->no, no);
node->bm_unique_verts.add(v);
BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, node_index);
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated;
/* Log the new vertex. */
BM_log_vert_added(pbvh->bm_log, v, cd_vert_mask_offset);
return v;
}
/**
* \note Callers are responsible for checking if the face exists before adding.
*/
static BMFace *pbvh_bmesh_face_create(PBVH *pbvh,
int node_index,
const blender::Span<BMVert *> v_tri,
const blender::Span<BMEdge *> e_tri,
const BMFace *f_example)
{
PBVHNode *node = &pbvh->nodes[node_index];
/* Ensure we never add existing face. */
BLI_assert(!BM_face_exists(v_tri.data(), 3));
BMFace *f = BM_face_create(
pbvh->header.bm, v_tri.data(), e_tri.data(), 3, f_example, BM_CREATE_NOP);
f->head.hflag = f_example->head.hflag;
node->bm_faces.add(f);
BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, node_index);
node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated;
node->flag &= ~PBVH_FullyHidden;
/* Log the new face. */
BM_log_face_added(pbvh->bm_log, f);
return f;
}
#define pbvh_bmesh_node_vert_use_count_is_equal(pbvh, node, v, n) \
(pbvh_bmesh_node_vert_use_count_at_most(pbvh, node, v, (n) + 1) == n)
static int pbvh_bmesh_node_vert_use_count_at_most(PBVH *pbvh,
PBVHNode *node,
BMVert *v,
const int count_max)
{
int count = 0;
BMFace *f;
BM_FACES_OF_VERT_ITER_BEGIN (f, v) {
PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f);
if (f_node == node) {
count++;
if (count == count_max) {
return count;
}
}
}
BM_FACES_OF_VERT_ITER_END;
return count;
}
/* Return a node that uses vertex 'v' other than its current owner. */
static PBVHNode *pbvh_bmesh_vert_other_node_find(PBVH *pbvh, BMVert *v)
{
PBVHNode *current_node = pbvh_bmesh_node_from_vert(pbvh, v);
BMFace *f;
BM_FACES_OF_VERT_ITER_BEGIN (f, v) {
PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f);
if (f_node != current_node) {
return f_node;
}
}
BM_FACES_OF_VERT_ITER_END;
return nullptr;
}
static void pbvh_bmesh_vert_ownership_transfer(PBVH *pbvh, PBVHNode *new_owner, BMVert *v)
{
PBVHNode *current_owner = pbvh_bmesh_node_from_vert(pbvh, v);
current_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated;
BLI_assert(current_owner != new_owner);
/* Remove current ownership. */
current_owner->bm_unique_verts.remove(v);
/* Set new ownership */
BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, new_owner - pbvh->nodes.data());
new_owner->bm_unique_verts.add(v);
new_owner->bm_other_verts.remove(v);
BLI_assert(!new_owner->bm_other_verts.contains(v));
new_owner->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated;
}
static void pbvh_bmesh_vert_remove(PBVH *pbvh, BMVert *v)
{
/* Never match for first time. */
int f_node_index_prev = DYNTOPO_NODE_NONE;
PBVHNode *v_node = pbvh_bmesh_node_from_vert(pbvh, v);
v_node->bm_unique_verts.remove(v);
BM_ELEM_CD_SET_INT(v, pbvh->cd_vert_node_offset, DYNTOPO_NODE_NONE);
/* Have to check each neighboring face's node. */
BMFace *f;
BM_FACES_OF_VERT_ITER_BEGIN (f, v) {
const int f_node_index = pbvh_bmesh_node_index_from_face(pbvh, f);
/* Faces often share the same node, quick check to avoid redundant #BLI_gset_remove calls. */
if (f_node_index_prev != f_node_index) {
f_node_index_prev = f_node_index;
PBVHNode *f_node = &pbvh->nodes[f_node_index];
f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated;
/* Remove current ownership. */
f_node->bm_other_verts.remove(v);
BLI_assert(!f_node->bm_unique_verts.contains(v));
BLI_assert(!f_node->bm_other_verts.contains(v));
}
}
BM_FACES_OF_VERT_ITER_END;
}
static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f)
{
PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, f);
/* Check if any of this face's vertices need to be removed from the node. */
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BMVert *v = l_iter->v;
if (pbvh_bmesh_node_vert_use_count_is_equal(pbvh, f_node, v, 1)) {
if (f_node->bm_unique_verts.contains(v)) {
/* Find a different node that uses 'v'. */
PBVHNode *new_node;
new_node = pbvh_bmesh_vert_other_node_find(pbvh, v);
BLI_assert(new_node || BM_vert_face_count_is_equal(v, 1));
if (new_node) {
pbvh_bmesh_vert_ownership_transfer(pbvh, new_node, v);
}
}
else {
/* Remove from other verts. */
f_node->bm_other_verts.remove(v);
}
}
} while ((l_iter = l_iter->next) != l_first);
/* Remove face from node and top level. */
f_node->bm_faces.remove(f);
BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE);
/* Log removed face. */
BM_log_face_removed(pbvh->bm_log, f);
/* Mark node for update. */
f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated;
}
static Array<BMLoop *> pbvh_bmesh_edge_loops(BMEdge *e)
{
/* Fast-path for most common case where an edge has 2 faces no need to iterate twice. */
std::array<BMLoop *, 2> manifold_loops;
if (LIKELY(BM_edge_loop_pair(e, &manifold_loops[0], &manifold_loops[1]))) {
return Array<BMLoop *>(Span(manifold_loops));
}
Array<BMLoop *> loops(BM_edge_face_count(e));
BM_iter_as_array(
nullptr, BM_LOOPS_OF_EDGE, e, reinterpret_cast<void **>(loops.data()), loops.size());
return loops;
}
static void pbvh_bmesh_node_drop_orig(PBVHNode *node)
{
MEM_SAFE_FREE(node->bm_orco);
MEM_SAFE_FREE(node->bm_ortri);
MEM_SAFE_FREE(node->bm_orvert);
node->bm_tot_ortri = 0;
}
/****************************** EdgeQueue *****************************/
struct EdgeQueue {
HeapSimple *heap;
const float *center;
float center_proj[3]; /* For when we use projected coords. */
float radius_squared;
float limit_len_squared;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
float limit_len;
#endif
bool (*edge_queue_tri_in_range)(const EdgeQueue *q, BMFace *f);
const float *view_normal;
#ifdef USE_EDGEQUEUE_FRONTFACE
uint use_view_normal : 1;
#endif
};
struct EdgeQueueContext {
EdgeQueue *q;
BLI_mempool *pool;
BMesh *bm;
int cd_vert_mask_offset;
int cd_vert_node_offset;
int cd_face_node_offset;
};
/* Only tagged edges are in the queue. */
#ifdef USE_EDGEQUEUE_TAG
# define EDGE_QUEUE_TEST(e) BM_elem_flag_test((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG)
# define EDGE_QUEUE_ENABLE(e) \
BM_elem_flag_enable((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG)
# define EDGE_QUEUE_DISABLE(e) \
BM_elem_flag_disable((CHECK_TYPE_INLINE(e, BMEdge *), e), BM_ELEM_TAG)
#endif
#ifdef USE_EDGEQUEUE_TAG_VERIFY
/* simply check no edges are tagged
* (it's a requirement that edges enter and leave a clean tag state) */
static void pbvh_bmesh_edge_tag_verify(PBVH *pbvh)
{
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if (node->bm_faces) {
GSetIterator gs_iter;
GSET_ITER (gs_iter, node->bm_faces) {
BMFace *f = BLI_gsetIterator_getKey(&gs_iter);
BMEdge *e_tri[3];
BMLoop *l_iter;
BLI_assert(f->len == 3);
l_iter = BM_FACE_FIRST_LOOP(f);
e_tri[0] = l_iter->e;
l_iter = l_iter->next;
e_tri[1] = l_iter->e;
l_iter = l_iter->next;
e_tri[2] = l_iter->e;
BLI_assert((EDGE_QUEUE_TEST(e_tri[0]) == false) && (EDGE_QUEUE_TEST(e_tri[1]) == false) &&
(EDGE_QUEUE_TEST(e_tri[2]) == false));
}
}
}
}
#endif
static bool edge_queue_tri_in_sphere(const EdgeQueue *q, BMFace *f)
{
BMVert *v_tri[3];
float c[3];
/* Get closest point in triangle to sphere center. */
BM_face_as_array_vert_tri(f, v_tri);
closest_on_tri_to_point_v3(c, q->center, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co);
/* Check if triangle intersects the sphere. */
return len_squared_v3v3(q->center, c) <= q->radius_squared;
}
static bool edge_queue_tri_in_circle(const EdgeQueue *q, BMFace *f)
{
BMVert *v_tri[3];
float c[3];
float tri_proj[3][3];
/* Get closest point in triangle to sphere center. */
BM_face_as_array_vert_tri(f, v_tri);
project_plane_normalized_v3_v3v3(tri_proj[0], v_tri[0]->co, q->view_normal);
project_plane_normalized_v3_v3v3(tri_proj[1], v_tri[1]->co, q->view_normal);
project_plane_normalized_v3_v3v3(tri_proj[2], v_tri[2]->co, q->view_normal);
closest_on_tri_to_point_v3(c, q->center_proj, tri_proj[0], tri_proj[1], tri_proj[2]);
/* Check if triangle intersects the sphere. */
return len_squared_v3v3(q->center_proj, c) <= q->radius_squared;
}
/* Return true if the vertex mask is less than 1.0, false otherwise. */
static bool check_mask(EdgeQueueContext *eq_ctx, BMVert *v)
{
return BM_ELEM_CD_GET_FLOAT(v, eq_ctx->cd_vert_mask_offset) < 1.0f;
}
static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priority)
{
/* Don't let topology update affect fully masked vertices. This used to
* have a 50% mask cutoff, with the reasoning that you can't do a 50%
* topology update. But this gives an ugly border in the mesh. The mask
* should already make the brush move the vertices only 50%, which means
* that topology updates will also happen less frequent, that should be
* enough. */
if (((eq_ctx->cd_vert_mask_offset == -1) ||
(check_mask(eq_ctx, e->v1) || check_mask(eq_ctx, e->v2))) &&
!(BM_elem_flag_test_bool(e->v1, BM_ELEM_HIDDEN) ||
BM_elem_flag_test_bool(e->v2, BM_ELEM_HIDDEN)))
{
BMVert **pair = static_cast<BMVert **>(BLI_mempool_alloc(eq_ctx->pool));
pair[0] = e->v1;
pair[1] = e->v2;
BLI_heapsimple_insert(eq_ctx->q->heap, priority, pair);
#ifdef USE_EDGEQUEUE_TAG
BLI_assert(EDGE_QUEUE_TEST(e) == false);
EDGE_QUEUE_ENABLE(e);
#endif
}
}
static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
#ifdef USE_EDGEQUEUE_TAG
if (EDGE_QUEUE_TEST(e) == false)
#endif
{
const float len_sq = BM_edge_calc_length_squared(e);
if (len_sq > eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, -len_sq);
}
}
}
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
static void long_edge_queue_edge_add_recursive(
EdgeQueueContext *eq_ctx, BMLoop *l_edge, BMLoop *l_end, const float len_sq, float limit_len)
{
BLI_assert(len_sq > square_f(limit_len));
# ifdef USE_EDGEQUEUE_FRONTFACE
if (eq_ctx->q->use_view_normal) {
if (dot_v3v3(l_edge->f->no, eq_ctx->q->view_normal) < 0.0f) {
return;
}
}
# endif
# ifdef USE_EDGEQUEUE_TAG
if (EDGE_QUEUE_TEST(l_edge->e) == false)
# endif
{
edge_queue_insert(eq_ctx, l_edge->e, -len_sq);
}
/* temp support previous behavior! */
if (UNLIKELY(G.debug_value == 1234)) {
return;
}
if (l_edge->radial_next != l_edge) {
/* How much longer we need to be to consider for subdividing
* (avoids subdividing faces which are only *slightly* skinny). */
# define EVEN_EDGELEN_THRESHOLD 1.2f
/* How much the limit increases per recursion
* (avoids performing subdivisions too far away). */
# define EVEN_GENERATION_SCALE 1.6f
const float len_sq_cmp = len_sq * EVEN_EDGELEN_THRESHOLD;
limit_len *= EVEN_GENERATION_SCALE;
const float limit_len_sq = square_f(limit_len);
BMLoop *l_iter = l_edge;
do {
BMLoop *l_adjacent[2] = {l_iter->next, l_iter->prev};
for (int i = 0; i < ARRAY_SIZE(l_adjacent); i++) {
float len_sq_other = BM_edge_calc_length_squared(l_adjacent[i]->e);
if (len_sq_other > max_ff(len_sq_cmp, limit_len_sq)) {
// edge_queue_insert(eq_ctx, l_adjacent[i]->e, -len_sq_other);
long_edge_queue_edge_add_recursive(
eq_ctx, l_adjacent[i]->radial_next, l_adjacent[i], len_sq_other, limit_len);
}
}
} while ((l_iter = l_iter->radial_next) != l_end);
# undef EVEN_EDGELEN_THRESHOLD
# undef EVEN_GENERATION_SCALE
}
}
#endif /* USE_EDGEQUEUE_EVEN_SUBDIV */
static void short_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
{
#ifdef USE_EDGEQUEUE_TAG
if (EDGE_QUEUE_TEST(e) == false)
#endif
{
const float len_sq = BM_edge_calc_length_squared(e);
if (len_sq < eq_ctx->q->limit_len_squared) {
edge_queue_insert(eq_ctx, e, len_sq);
}
}
}
static void long_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f)
{
#ifdef USE_EDGEQUEUE_FRONTFACE
if (eq_ctx->q->use_view_normal) {
if (dot_v3v3(f->no, eq_ctx->q->view_normal) < 0.0f) {
return;
}
}
#endif
if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) {
/* Check each edge of the face. */
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
const float len_sq = BM_edge_calc_length_squared(l_iter->e);
if (len_sq > eq_ctx->q->limit_len_squared) {
long_edge_queue_edge_add_recursive(
eq_ctx, l_iter->radial_next, l_iter, len_sq, eq_ctx->q->limit_len);
}
#else
long_edge_queue_edge_add(eq_ctx, l_iter->e);
#endif
} while ((l_iter = l_iter->next) != l_first);
}
}
static void short_edge_queue_face_add(EdgeQueueContext *eq_ctx, BMFace *f)
{
#ifdef USE_EDGEQUEUE_FRONTFACE
if (eq_ctx->q->use_view_normal) {
if (dot_v3v3(f->no, eq_ctx->q->view_normal) < 0.0f) {
return;
}
}
#endif
if (eq_ctx->q->edge_queue_tri_in_range(eq_ctx->q, f)) {
BMLoop *l_iter;
BMLoop *l_first;
/* Check each edge of the face. */
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
short_edge_queue_edge_add(eq_ctx, l_iter->e);
} while ((l_iter = l_iter->next) != l_first);
}
}
/**
* Create a priority queue containing vertex pairs connected by a long
* edge as defined by PBVH.bm_max_edge_len.
*
* Only nodes marked for topology update are checked, and in those
* nodes only edges used by a face intersecting the (center, radius)
* sphere are checked.
*
* The highest priority (lowest number) is given to the longest edge.
*/
static void long_edge_queue_create(EdgeQueueContext *eq_ctx,
PBVH *pbvh,
const float center[3],
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected)
{
eq_ctx->q->heap = BLI_heapsimple_new();
eq_ctx->q->center = center;
eq_ctx->q->radius_squared = radius * radius;
eq_ctx->q->limit_len_squared = pbvh->bm_max_edge_len * pbvh->bm_max_edge_len;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
eq_ctx->q->limit_len = pbvh->bm_max_edge_len;
#endif
eq_ctx->q->view_normal = view_normal;
#ifdef USE_EDGEQUEUE_FRONTFACE
eq_ctx->q->use_view_normal = use_frontface;
#else
UNUSED_VARS(use_frontface);
#endif
if (use_projected) {
eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_circle;
project_plane_normalized_v3_v3v3(eq_ctx->q->center_proj, center, view_normal);
}
else {
eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_sphere;
}
#ifdef USE_EDGEQUEUE_TAG_VERIFY
pbvh_bmesh_edge_tag_verify(pbvh);
#endif
for (PBVHNode &node : pbvh->nodes) {
/* Check leaf nodes marked for topology update. */
if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) &&
!(node.flag & PBVH_FullyHidden))
{
for (BMFace *f : node.bm_faces) {
long_edge_queue_face_add(eq_ctx, f);
}
}
}
}
/* Create a priority queue containing vertex pairs connected by a
* short edge as defined by PBVH.bm_min_edge_len.
*
* Only nodes marked for topology update are checked, and in those
* nodes only edges used by a face intersecting the (center, radius)
* sphere are checked.
*
* The highest priority (lowest number) is given to the shortest edge.
*/
static void short_edge_queue_create(EdgeQueueContext *eq_ctx,
PBVH *pbvh,
const float center[3],
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected)
{
eq_ctx->q->heap = BLI_heapsimple_new();
eq_ctx->q->center = center;
eq_ctx->q->radius_squared = radius * radius;
eq_ctx->q->limit_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len;
#ifdef USE_EDGEQUEUE_EVEN_SUBDIV
eq_ctx->q->limit_len = pbvh->bm_min_edge_len;
#endif
eq_ctx->q->view_normal = view_normal;
#ifdef USE_EDGEQUEUE_FRONTFACE
eq_ctx->q->use_view_normal = use_frontface;
#else
UNUSED_VARS(use_frontface);
#endif
if (use_projected) {
eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_circle;
project_plane_normalized_v3_v3v3(eq_ctx->q->center_proj, center, view_normal);
}
else {
eq_ctx->q->edge_queue_tri_in_range = edge_queue_tri_in_sphere;
}
for (PBVHNode &node : pbvh->nodes) {
/* Check leaf nodes marked for topology update */
if ((node.flag & PBVH_Leaf) && (node.flag & PBVH_UpdateTopology) &&
!(node.flag & PBVH_FullyHidden))
{
for (BMFace *f : node.bm_faces) {
short_edge_queue_face_add(eq_ctx, f);
}
}
}
}
/*************************** Topology update **************************/
/**
* Copy custom data from src to dst edge.
*
* \note The BM_ELEM_TAG is used to tell whether an edge is in the queue for collapse/split,
* so we do not copy this flag as we do not want the new edge to appear in the queue.
*/
static void copy_edge_data(BMesh &bm, BMEdge &dst, /*const*/ BMEdge &src)
{
dst.head.hflag = src.head.hflag & ~BM_ELEM_TAG;
CustomData_bmesh_copy_data(&bm.edata, &bm.edata, src.head.data, &dst.head.data);
}
static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *e)
{
BMesh *bm = pbvh->header.bm;
float co_mid[3], no_mid[3];
/* Get all faces adjacent to the edge. */
Array<BMLoop *> edge_loops = pbvh_bmesh_edge_loops(e);
/* Create a new vertex in current node at the edge's midpoint. */
mid_v3_v3v3(co_mid, e->v1->co, e->v2->co);
mid_v3_v3v3(no_mid, e->v1->no, e->v2->no);
normalize_v3(no_mid);
int node_index = BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset);
BMVert *v_new = pbvh_bmesh_vert_create(
pbvh, e->v1, e->v2, node_index, co_mid, no_mid, eq_ctx->cd_vert_mask_offset);
/* For each face, add two new triangles and delete the original. */
for (const int i : edge_loops.index_range()) {
BMLoop *l_adj = edge_loops[i];
BMFace *f_adj = l_adj->f;
BLI_assert(f_adj->len == 3);
int ni = BM_ELEM_CD_GET_INT(f_adj, eq_ctx->cd_face_node_offset);
/* Find the vertex not in the edge. */
BMVert *v_opp = l_adj->prev->v;
/* Get e->v1 and e->v2 in the order they appear in the existing face so that the new faces'
* winding orders match. */
BMVert *v1 = l_adj->v;
BMVert *v2 = l_adj->next->v;
if (ni != node_index && i == 0) {
pbvh_bmesh_vert_ownership_transfer(pbvh, &pbvh->nodes[ni], v_new);
}
/**
* The 2 new faces created and assigned to `f_new` have their
* verts & edges shuffled around.
*
* - faces wind anticlockwise in this example.
* - original edge is `(v1, v2)`
* - original face is `(v1, v2, v3)`
*
* <pre>
* + v_opp
* /|\
* / | \
* / | \
* e4/ | \ e3
* / |e5 \
* / | \
* / e1 | e2 \
* +-------+-------+
* v1 v_new v2
* (first) (second)
* </pre>
*
* - f_new (first): `v_tri=(v1, v_new, v_opp), e_tri=(e1, e5, e4)`
* - f_new (second): `v_tri=(v_new, v2, v_opp), e_tri=(e2, e3, e5)`
*/
/* Create first face (v1, v_new, v_opp). */
const std::array<BMVert *, 3> first_tri({v1, v_new, v_opp});
const std::array<BMEdge *, 3> first_edges = bm_edges_from_tri(bm, first_tri);
copy_edge_data(*bm, *first_edges[0], *e);
BMFace *f_new_first = pbvh_bmesh_face_create(pbvh, ni, first_tri, first_edges, f_adj);
long_edge_queue_face_add(eq_ctx, f_new_first);
/* Create second face (v_new, v2, v_opp). */
const std::array<BMVert *, 3> second_tri({v_new, v2, v_opp});
const std::array<BMEdge *, 3> second_edges{
BM_edge_create(bm, second_tri[0], second_tri[1], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, second_tri[1], second_tri[2], nullptr, BM_CREATE_NO_DOUBLE),
first_edges[1],
};
copy_edge_data(*bm, *second_edges[0], *e);
BMFace *f_new_second = pbvh_bmesh_face_create(pbvh, ni, second_tri, second_edges, f_adj);
long_edge_queue_face_add(eq_ctx, f_new_second);
/* Delete original */
pbvh_bmesh_face_remove(pbvh, f_adj);
BM_face_kill(bm, f_adj);
/* Ensure new vertex is in the node */
if (!pbvh->nodes[ni].bm_unique_verts.contains(v_new)) {
pbvh->nodes[ni].bm_other_verts.add(v_new);
}
if (BM_vert_edge_count_is_over(v_opp, 8)) {
BMIter bm_iter;
BMEdge *e2;
BM_ITER_ELEM (e2, &bm_iter, v_opp, BM_EDGES_OF_VERT) {
long_edge_queue_edge_add(eq_ctx, e2);
}
}
}
BM_edge_kill(bm, e);
}
static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh)
{
const double start_time = PIL_check_seconds_timer();
bool any_subdivided = false;
while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) {
BMVert **pair = static_cast<BMVert **>(BLI_heapsimple_pop_min(eq_ctx->q->heap));
BMVert *v1 = pair[0];
BMVert *v2 = pair[1];
BLI_mempool_free(eq_ctx->pool, pair);
pair = nullptr;
/* Check that the edge still exists */
BMEdge *e;
if (!(e = BM_edge_exists(v1, v2))) {
continue;
}
#ifdef USE_EDGEQUEUE_TAG
EDGE_QUEUE_DISABLE(e);
#endif
BLI_assert(len_squared_v3v3(v1->co, v2->co) > eq_ctx->q->limit_len_squared);
/* Check that the edge's vertices are still in the PBVH. It's
* possible that an edge collapse has deleted adjacent faces
* and the node has been split, thus leaving wire edges and
* associated vertices. */
if ((BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE) ||
(BM_ELEM_CD_GET_INT(e->v2, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE))
{
continue;
}
any_subdivided = true;
pbvh_bmesh_split_edge(eq_ctx, pbvh, e);
}
#ifdef USE_EDGEQUEUE_TAG_VERIFY
pbvh_bmesh_edge_tag_verify(pbvh);
#endif
CLOG_INFO(
&LOG, 2, "Long edge subdivision took %f seconds.", PIL_check_seconds_timer() - start_time);
return any_subdivided;
}
static void pbvh_bmesh_collapse_edge(
PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, GHash *deleted_verts, EdgeQueueContext *eq_ctx)
{
BMesh &bm = *pbvh->header.bm;
/* Prefer deleting the vertex that is less masked. */
BMVert *v_del;
BMVert *v_conn;
if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) <
BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset))
{
v_del = v1;
v_conn = v2;
}
else {
v_del = v2;
v_conn = v1;
}
/* Remove the merge vertex from the PBVH. */
pbvh_bmesh_vert_remove(pbvh, v_del);
/* Remove all faces adjacent to the edge. */
BMLoop *l_adj;
while ((l_adj = e->l)) {
BMFace *f_adj = l_adj->f;
pbvh_bmesh_face_remove(pbvh, f_adj);
BM_face_kill(&bm, f_adj);
}
/* Kill the edge. */
BLI_assert(BM_edge_is_wire(e));
BM_edge_kill(&bm, e);
/* For all remaining faces of v_del, create a new face that is the
* same except it uses v_conn instead of v_del */
/* NOTE: this could be done with BM_vert_splice(), but that
* requires handling other issues like duplicate edges, so doesn't
* really buy anything. */
Vector<BMFace *, 16> deleted_faces;
BMLoop *l;
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) {
/* Get vertices, replace use of v_del with v_conn */
BMFace *f = l->f;
/* Check if a face using these vertices already exists. If so, skip adding this face and mark
* the existing one for deletion as well. Prevents extraneous "flaps" from being created.
* Check is similar to #BM_face_exists. */
if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
deleted_faces.append(existing_face);
}
else {
const std::array<BMVert *, 3> v_tri{v_conn, l->next->v, l->prev->v};
BLI_assert(!BM_face_exists(v_tri.data(), 3));
PBVHNode *n = pbvh_bmesh_node_from_face(pbvh, f);
int ni = n - pbvh->nodes.data();
const std::array<BMEdge *, 3> e_tri = bm_edges_from_tri(&bm, v_tri);
pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f);
/* Ensure that v_conn is in the new face's node */
if (!n->bm_unique_verts.contains(v_conn)) {
n->bm_other_verts.add(v_conn);
}
}
deleted_faces.append(f);
}
BM_LOOPS_OF_VERT_ITER_END;
/* Delete the tagged faces. */
for (BMFace *f_del : deleted_faces) {
/* Get vertices and edges of face. */
BLI_assert(f_del->len == 3);
BMLoop *l_iter = BM_FACE_FIRST_LOOP(f_del);
const std::array<BMVert *, 3> v_tri{l_iter->v, l_iter->next->v, l_iter->next->next->v};
const std::array<BMEdge *, 3> e_tri{l_iter->e, l_iter->next->e, l_iter->next->next->e};
/* Remove the face */
pbvh_bmesh_face_remove(pbvh, f_del);
BM_face_kill(&bm, f_del);
/* Check if any of the face's edges are now unused by any
* face, if so delete them */
for (const int j : IndexRange(3)) {
if (BM_edge_is_wire(e_tri[j])) {
BM_edge_kill(&bm, e_tri[j]);
}
}
/* Check if any of the face's vertices are now unused, if so
* remove them from the PBVH */
for (const int j : IndexRange(3)) {
if ((v_tri[j] != v_del) && (v_tri[j]->e == nullptr)) {
pbvh_bmesh_vert_remove(pbvh, v_tri[j]);
BM_log_vert_removed(pbvh->bm_log, v_tri[j], eq_ctx->cd_vert_mask_offset);
if (v_tri[j] == v_conn) {
v_conn = nullptr;
}
BLI_ghash_insert(deleted_verts, v_tri[j], nullptr);
BM_vert_kill(&bm, v_tri[j]);
}
}
}
/* Move v_conn to the midpoint of v_conn and v_del (if v_conn still exists, it
* may have been deleted above). */
if (v_conn != nullptr) {
BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset);
mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co);
add_v3_v3(v_conn->no, v_del->no);
normalize_v3(v_conn->no);
/* Update bounding boxes attached to the connected vertex.
* Note that we can often get-away without this but causes #48779. */
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_conn) {
PBVHNode *f_node = pbvh_bmesh_node_from_face(pbvh, l->f);
f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_UpdateBB;
}
BM_LOOPS_OF_VERT_ITER_END;
}
/* Delete v_del */
BLI_assert(!BM_vert_face_check(v_del));
BM_log_vert_removed(pbvh->bm_log, v_del, eq_ctx->cd_vert_mask_offset);
/* v_conn == nullptr is OK */
BLI_ghash_insert(deleted_verts, v_del, v_conn);
BM_vert_kill(&bm, v_del);
}
static bool pbvh_bmesh_collapse_short_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh)
{
const double start_time = PIL_check_seconds_timer();
const float min_len_squared = pbvh->bm_min_edge_len * pbvh->bm_min_edge_len;
bool any_collapsed = false;
/* Deleted verts point to vertices they were merged into, or nullptr when removed. */
GHash *deleted_verts = BLI_ghash_ptr_new("deleted_verts");
while (!BLI_heapsimple_is_empty(eq_ctx->q->heap)) {
BMVert **pair = static_cast<BMVert **>(BLI_heapsimple_pop_min(eq_ctx->q->heap));
BMVert *v1 = pair[0];
BMVert *v2 = pair[1];
BLI_mempool_free(eq_ctx->pool, pair);
pair = nullptr;
/* Check the verts still exists. */
if (!(v1 = bm_vert_hash_lookup_chain(deleted_verts, v1)) ||
!(v2 = bm_vert_hash_lookup_chain(deleted_verts, v2)) || (v1 == v2))
{
continue;
}
/* Check that the edge still exists. */
BMEdge *e;
if (!(e = BM_edge_exists(v1, v2))) {
continue;
}
#ifdef USE_EDGEQUEUE_TAG
EDGE_QUEUE_DISABLE(e);
#endif
if (len_squared_v3v3(v1->co, v2->co) >= min_len_squared) {
continue;
}
/* Check that the edge's vertices are still in the PBVH. It's possible that an edge collapse
* has deleted adjacent faces and the node has been split, thus leaving wire edges and
* associated vertices. */
if ((BM_ELEM_CD_GET_INT(e->v1, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE) ||
(BM_ELEM_CD_GET_INT(e->v2, eq_ctx->cd_vert_node_offset) == DYNTOPO_NODE_NONE))
{
continue;
}
any_collapsed = true;
pbvh_bmesh_collapse_edge(pbvh, e, v1, v2, deleted_verts, eq_ctx);
}
BLI_ghash_free(deleted_verts, nullptr, nullptr);
CLOG_INFO(
&LOG, 2, "Short edge collapse took %f seconds.", PIL_check_seconds_timer() - start_time);
return any_collapsed;
}
/************************* Called from pbvh.cc *************************/
bool pbvh_bmesh_node_raycast(PBVHNode *node,
const float ray_start[3],
const float ray_normal[3],
IsectRayPrecalc *isect_precalc,
float *depth,
bool use_original,
PBVHVertRef *r_active_vertex,
float *r_face_normal)
{
bool hit = false;
float nearest_vertex_co[3] = {0.0f};
use_original = use_original && node->bm_tot_ortri;
if (use_original && node->bm_tot_ortri) {
for (int i = 0; i < node->bm_tot_ortri; i++) {
float *cos[3];
cos[0] = node->bm_orco[node->bm_ortri[i][0]];
cos[1] = node->bm_orco[node->bm_ortri[i][1]];
cos[2] = node->bm_orco[node->bm_ortri[i][2]];
if (ray_face_intersection_tri(ray_start, isect_precalc, cos[0], cos[1], cos[2], depth)) {
hit = true;
if (r_face_normal) {
normal_tri_v3(r_face_normal, cos[0], cos[1], cos[2]);
}
if (r_active_vertex) {
float location[3] = {0.0f};
madd_v3_v3v3fl(location, ray_start, ray_normal, *depth);
for (const int j : IndexRange(3)) {
if (j == 0 || len_squared_v3v3(location, cos[j]) <
len_squared_v3v3(location, nearest_vertex_co)) {
copy_v3_v3(nearest_vertex_co, cos[j]);
r_active_vertex->i = intptr_t(node->bm_orvert[node->bm_ortri[i][j]]);
}
}
}
}
}
}
else {
for (BMFace *f : node->bm_faces) {
BLI_assert(f->len == 3);
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
BMVert *v_tri[3];
BM_face_as_array_vert_tri(f, v_tri);
if (ray_face_intersection_tri(
ray_start, isect_precalc, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth))
{
hit = true;
if (r_face_normal) {
normal_tri_v3(r_face_normal, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co);
}
if (r_active_vertex) {
float location[3] = {0.0f};
madd_v3_v3v3fl(location, ray_start, ray_normal, *depth);
for (const int j : IndexRange(3)) {
if (j == 0 || len_squared_v3v3(location, v_tri[j]->co) <
len_squared_v3v3(location, nearest_vertex_co))
{
copy_v3_v3(nearest_vertex_co, v_tri[j]->co);
r_active_vertex->i = intptr_t(v_tri[j]);
}
}
}
}
}
}
}
return hit;
}
bool BKE_pbvh_bmesh_node_raycast_detail(PBVHNode *node,
const float ray_start[3],
IsectRayPrecalc *isect_precalc,
float *depth,
float *r_edge_length)
{
if (node->flag & PBVH_FullyHidden) {
return false;
}
bool hit = false;
BMFace *f_hit = nullptr;
for (BMFace *f : node->bm_faces) {
BLI_assert(f->len == 3);
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
BMVert *v_tri[3];
bool hit_local;
BM_face_as_array_vert_tri(f, v_tri);
hit_local = ray_face_intersection_tri(
ray_start, isect_precalc, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth);
if (hit_local) {
f_hit = f;
hit = true;
}
}
}
if (hit) {
BMVert *v_tri[3];
BM_face_as_array_vert_tri(f_hit, v_tri);
float len1 = len_squared_v3v3(v_tri[0]->co, v_tri[1]->co);
float len2 = len_squared_v3v3(v_tri[1]->co, v_tri[2]->co);
float len3 = len_squared_v3v3(v_tri[2]->co, v_tri[0]->co);
/* Detail returned will be set to the maximum allowed size, so take max here. */
*r_edge_length = sqrtf(max_fff(len1, len2, len3));
}
return hit;
}
bool pbvh_bmesh_node_nearest_to_ray(PBVHNode *node,
const float ray_start[3],
const float ray_normal[3],
float *depth,
float *dist_sq,
bool use_original)
{
bool hit = false;
if (use_original && node->bm_tot_ortri) {
for (int i = 0; i < node->bm_tot_ortri; i++) {
const int *t = node->bm_ortri[i];
hit |= ray_face_nearest_tri(ray_start,
ray_normal,
node->bm_orco[t[0]],
node->bm_orco[t[1]],
node->bm_orco[t[2]],
depth,
dist_sq);
}
}
else {
for (BMFace *f : node->bm_faces) {
BLI_assert(f->len == 3);
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
BMVert *v_tri[3];
BM_face_as_array_vert_tri(f, v_tri);
hit |= ray_face_nearest_tri(
ray_start, ray_normal, v_tri[0]->co, v_tri[1]->co, v_tri[2]->co, depth, dist_sq);
}
}
}
return hit;
}
void pbvh_bmesh_normals_update(Span<PBVHNode *> nodes)
{
for (PBVHNode *node : nodes) {
if (node->flag & PBVH_UpdateNormals) {
for (BMFace *face : node->bm_faces) {
BM_face_normal_update(face);
}
for (BMVert *vert : node->bm_unique_verts) {
BM_vert_normal_update(vert);
}
for (BMVert *vert : node->bm_other_verts) {
BM_vert_normal_update(vert);
}
node->flag &= ~PBVH_UpdateNormals;
}
}
}
struct FastNodeBuildInfo {
int totface; /* Number of faces. */
int start; /* Start of faces in array. */
FastNodeBuildInfo *child1;
FastNodeBuildInfo *child2;
};
/**
* Recursively split the node if it exceeds the leaf_limit.
* This function is multi-thread-able since each invocation applies
* to a sub part of the arrays.
*/
static void pbvh_bmesh_node_limit_ensure_fast(
PBVH *pbvh, BMFace **nodeinfo, BBC *bbc_array, FastNodeBuildInfo *node, MemArena *arena)
{
FastNodeBuildInfo *child1, *child2;
if (node->totface <= pbvh->leaf_limit) {
return;
}
/* Calculate bounding box around primitive centroids. */
BB cb;
BB_reset(&cb);
for (int i = 0; i < node->totface; i++) {
BMFace *f = nodeinfo[i + node->start];
BBC *bbc = &bbc_array[BM_elem_index_get(f)];
BB_expand(&cb, bbc->bcentroid);
}
/* Initialize the children. */
/* Find widest axis and its midpoint. */
const int axis = BB_widest_axis(&cb);
const float mid = (cb.bmax[axis] + cb.bmin[axis]) * 0.5f;
int num_child1 = 0, num_child2 = 0;
/* Split vertices along the middle line. */
const int end = node->start + node->totface;
for (int i = node->start; i < end - num_child2; i++) {
BMFace *f = nodeinfo[i];
BBC *bbc = &bbc_array[BM_elem_index_get(f)];
if (bbc->bcentroid[axis] > mid) {
int i_iter = end - num_child2 - 1;
int candidate = -1;
/* Found a face that should be part of another node, look for a face to substitute with. */
for (; i_iter > i; i_iter--) {
BMFace *f_iter = nodeinfo[i_iter];
const BBC *bbc_iter = &bbc_array[BM_elem_index_get(f_iter)];
if (bbc_iter->bcentroid[axis] <= mid) {
candidate = i_iter;
break;
}
num_child2++;
}
if (candidate != -1) {
BMFace *tmp = nodeinfo[i];
nodeinfo[i] = nodeinfo[candidate];
nodeinfo[candidate] = tmp;
/* Increase both counts. */
num_child1++;
num_child2++;
}
else {
/* Not finding candidate means second half of array part is full of
* second node parts, just increase the number of child nodes for it. */
num_child2++;
}
}
else {
num_child1++;
}
}
/* Ensure at least one child in each node. */
if (num_child2 == 0) {
num_child2++;
num_child1--;
}
else if (num_child1 == 0) {
num_child1++;
num_child2--;
}
/* At this point, faces should have been split along the array range sequentially,
* each sequential part belonging to one node only. */
BLI_assert((num_child1 + num_child2) == node->totface);
node->child1 = child1 = static_cast<FastNodeBuildInfo *>(
BLI_memarena_alloc(arena, sizeof(FastNodeBuildInfo)));
node->child2 = child2 = static_cast<FastNodeBuildInfo *>(
BLI_memarena_alloc(arena, sizeof(FastNodeBuildInfo)));
child1->totface = num_child1;
child1->start = node->start;
child2->totface = num_child2;
child2->start = node->start + num_child1;
child1->child1 = child1->child2 = child2->child1 = child2->child2 = nullptr;
pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, child1, arena);
pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, child2, arena);
}
static void pbvh_bmesh_create_nodes_fast_recursive(
PBVH *pbvh, BMFace **nodeinfo, BBC *bbc_array, FastNodeBuildInfo *node, int node_index)
{
PBVHNode *n = &pbvh->nodes[node_index];
/* Two cases, node does not have children or does have children. */
if (node->child1) {
int children_offset = pbvh->nodes.size();
n->children_offset = children_offset;
pbvh_grow_nodes(pbvh, pbvh->nodes.size() + 2);
pbvh_bmesh_create_nodes_fast_recursive(
pbvh, nodeinfo, bbc_array, node->child1, children_offset);
pbvh_bmesh_create_nodes_fast_recursive(
pbvh, nodeinfo, bbc_array, node->child2, children_offset + 1);
n = &pbvh->nodes[node_index];
/* Update bounding box. */
BB_reset(&n->vb);
BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset].vb);
BB_expand_with_bb(&n->vb, &pbvh->nodes[n->children_offset + 1].vb);
n->orig_vb = n->vb;
}
else {
/* Node does not have children so it's a leaf node, populate with faces and tag accordingly
* this is an expensive part but it's not so easily thread-able due to vertex node indices. */
const int cd_vert_node_offset = pbvh->cd_vert_node_offset;
const int cd_face_node_offset = pbvh->cd_face_node_offset;
bool has_visible = false;
n->flag = PBVH_Leaf;
n->bm_faces.reserve(node->totface);
BB_reset(&n->vb);
const int end = node->start + node->totface;
for (int i = node->start; i < end; i++) {
BMFace *f = nodeinfo[i];
BBC *bbc = &bbc_array[BM_elem_index_get(f)];
/* Update ownership of faces. */
n->bm_faces.add_new(f);
BM_ELEM_CD_SET_INT(f, cd_face_node_offset, node_index);
/* Update vertices. */
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
do {
BMVert *v = l_iter->v;
if (!n->bm_unique_verts.contains(v)) {
if (BM_ELEM_CD_GET_INT(v, cd_vert_node_offset) != DYNTOPO_NODE_NONE) {
n->bm_other_verts.add(v);
}
else {
n->bm_unique_verts.add(v);
BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, node_index);
}
}
/* Update node bounding box. */
} while ((l_iter = l_iter->next) != l_first);
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
has_visible = true;
}
BB_expand_with_bb(&n->vb, (BB *)bbc);
}
BLI_assert(n->vb.bmin[0] <= n->vb.bmax[0] && n->vb.bmin[1] <= n->vb.bmax[1] &&
n->vb.bmin[2] <= n->vb.bmax[2]);
n->orig_vb = n->vb;
/* Build GPU buffers for new node and update vertex normals. */
BKE_pbvh_node_mark_rebuild_draw(n);
BKE_pbvh_node_fully_hidden_set(n, !has_visible);
n->flag |= PBVH_UpdateNormals;
}
}
/***************************** Public API *****************************/
void BKE_pbvh_update_bmesh_offsets(PBVH *pbvh, int cd_vert_node_offset, int cd_face_node_offset)
{
pbvh->cd_vert_node_offset = cd_vert_node_offset;
pbvh->cd_face_node_offset = cd_face_node_offset;
}
void BKE_pbvh_build_bmesh(PBVH *pbvh,
BMesh *bm,
BMLog *log,
const int cd_vert_node_offset,
const int cd_face_node_offset)
{
pbvh->header.bm = bm;
BKE_pbvh_bmesh_detail_size_set(pbvh, 0.75);
pbvh->header.type = PBVH_BMESH;
pbvh->bm_log = log;
/* TODO: choose leaf limit better. */
pbvh->leaf_limit = 400;
BKE_pbvh_update_bmesh_offsets(pbvh, cd_vert_node_offset, cd_face_node_offset);
/* bounding box array of all faces, no need to recalculate every time. */
BBC *bbc_array = static_cast<BBC *>(MEM_mallocN(sizeof(BBC) * bm->totface, "BBC"));
BMFace **nodeinfo = static_cast<BMFace **>(
MEM_mallocN(sizeof(*nodeinfo) * bm->totface, "nodeinfo"));
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "fast PBVH node storage");
BMIter iter;
BMFace *f;
int i;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) {
BBC *bbc = &bbc_array[i];
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
BB_reset((BB *)bbc);
do {
BB_expand((BB *)bbc, l_iter->v->co);
} while ((l_iter = l_iter->next) != l_first);
BBC_update_centroid(bbc);
/* so we can do direct lookups on 'bbc_array' */
BM_elem_index_set(f, i); /* set_dirty! */
nodeinfo[i] = f;
BM_ELEM_CD_SET_INT(f, cd_face_node_offset, DYNTOPO_NODE_NONE);
}
/* Likely this is already dirty. */
bm->elem_index_dirty |= BM_FACE;
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_ELEM_CD_SET_INT(v, cd_vert_node_offset, DYNTOPO_NODE_NONE);
}
/* Set up root node. */
FastNodeBuildInfo rootnode = {0};
rootnode.totface = bm->totface;
/* Start recursion, assign faces to nodes accordingly. */
pbvh_bmesh_node_limit_ensure_fast(pbvh, nodeinfo, bbc_array, &rootnode, arena);
/* We now have all faces assigned to a node,
* next we need to assign those to the gsets of the nodes. */
/* Start with all faces in the root node. */
pbvh->nodes.append({});
/* Take root node and visit and populate children recursively. */
pbvh_bmesh_create_nodes_fast_recursive(pbvh, nodeinfo, bbc_array, &rootnode, 0);
BLI_memarena_free(arena);
MEM_freeN(bbc_array);
MEM_freeN(nodeinfo);
}
bool BKE_pbvh_bmesh_update_topology(PBVH *pbvh,
PBVHTopologyUpdateMode mode,
const float center[3],
const float view_normal[3],
float radius,
const bool use_frontface,
const bool use_projected)
{
const int cd_vert_mask_offset = CustomData_get_offset(&pbvh->header.bm->vdata, CD_PAINT_MASK);
const int cd_vert_node_offset = pbvh->cd_vert_node_offset;
const int cd_face_node_offset = pbvh->cd_face_node_offset;
bool modified = false;
if (view_normal) {
BLI_assert(len_squared_v3(view_normal) != 0.0f);
}
if (mode & PBVH_Collapse) {
EdgeQueue q;
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP);
EdgeQueueContext eq_ctx = {
&q,
queue_pool,
pbvh->header.bm,
cd_vert_mask_offset,
cd_vert_node_offset,
cd_face_node_offset,
};
short_edge_queue_create(
&eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected);
modified |= pbvh_bmesh_collapse_short_edges(&eq_ctx, pbvh);
BLI_heapsimple_free(q.heap, nullptr);
BLI_mempool_destroy(queue_pool);
}
if (mode & PBVH_Subdivide) {
EdgeQueue q;
BLI_mempool *queue_pool = BLI_mempool_create(sizeof(BMVert *) * 2, 0, 128, BLI_MEMPOOL_NOP);
EdgeQueueContext eq_ctx = {
&q,
queue_pool,
pbvh->header.bm,
cd_vert_mask_offset,
cd_vert_node_offset,
cd_face_node_offset,
};
long_edge_queue_create(
&eq_ctx, pbvh, center, view_normal, radius, use_frontface, use_projected);
modified |= pbvh_bmesh_subdivide_long_edges(&eq_ctx, pbvh);
BLI_heapsimple_free(q.heap, nullptr);
BLI_mempool_destroy(queue_pool);
}
/* Unmark nodes. */
for (PBVHNode &node : pbvh->nodes) {
if (node.flag & PBVH_Leaf && node.flag & PBVH_UpdateTopology) {
node.flag &= ~PBVH_UpdateTopology;
}
}
/* Go over all changed nodes and check if anything needs to be updated. */
for (PBVHNode &node : pbvh->nodes) {
if (node.flag & PBVH_Leaf && node.flag & PBVH_TopologyUpdated) {
node.flag &= ~PBVH_TopologyUpdated;
if (node.bm_ortri) {
/* Reallocate original triangle data. */
pbvh_bmesh_node_drop_orig(&node);
BKE_pbvh_bmesh_node_save_orig(pbvh->header.bm, pbvh->bm_log, &node, true);
}
}
}
#ifdef USE_VERIFY
pbvh_bmesh_verify(pbvh);
#endif
return modified;
}
void BKE_pbvh_bmesh_node_save_orig(BMesh *bm, BMLog *log, PBVHNode *node, bool use_original)
{
/* Skip if original coords/triangles are already saved. */
if (node->bm_orco) {
return;
}
const int totvert = node->bm_unique_verts.size() + node->bm_other_verts.size();
const int tottri = node->bm_faces.size();
node->bm_orco = static_cast<float(*)[3]>(
MEM_mallocN(sizeof(*node->bm_orco) * totvert, __func__));
node->bm_ortri = static_cast<int(*)[3]>(MEM_mallocN(sizeof(*node->bm_ortri) * tottri, __func__));
node->bm_orvert = static_cast<BMVert **>(
MEM_mallocN(sizeof(*node->bm_orvert) * totvert, __func__));
/* Copy out the vertices and assign a temporary index. */
int i = 0;
for (BMVert *v : node->bm_unique_verts) {
const float *origco = BM_log_original_vert_co(log, v);
if (use_original && origco) {
copy_v3_v3(node->bm_orco[i], origco);
}
else {
copy_v3_v3(node->bm_orco[i], v->co);
}
node->bm_orvert[i] = v;
BM_elem_index_set(v, i); /* set_dirty! */
i++;
}
for (BMVert *v : node->bm_other_verts) {
const float *origco = BM_log_original_vert_co(log, v);
if (use_original && origco) {
copy_v3_v3(node->bm_orco[i], BM_log_original_vert_co(log, v));
}
else {
copy_v3_v3(node->bm_orco[i], v->co);
}
node->bm_orvert[i] = v;
BM_elem_index_set(v, i); /* set_dirty! */
i++;
}
/* Likely this is already dirty. */
bm->elem_index_dirty |= BM_VERT;
/* Copy the triangles */
i = 0;
for (BMFace *f : node->bm_faces) {
if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
continue;
}
bm_face_as_array_index_tri(f, node->bm_ortri[i]);
i++;
}
node->bm_tot_ortri = i;
}
void BKE_pbvh_bmesh_after_stroke(PBVH *pbvh)
{
const int totnode = pbvh->nodes.size();
for (int i = 0; i < totnode; i++) {
PBVHNode *n = &pbvh->nodes[i];
if (n->flag & PBVH_Leaf) {
/* Free orco/ortri data. */
pbvh_bmesh_node_drop_orig(n);
/* Recursively split nodes that have gotten too many elements. */
pbvh_bmesh_node_limit_ensure(pbvh, i);
}
}
}
void BKE_pbvh_bmesh_detail_size_set(PBVH *pbvh, float detail_size)
{
pbvh->bm_max_edge_len = detail_size;
pbvh->bm_min_edge_len = pbvh->bm_max_edge_len * 0.4f;
}
void BKE_pbvh_node_mark_topology_update(PBVHNode *node)
{
node->flag |= PBVH_UpdateTopology;
}
const blender::Set<BMVert *, 0> &BKE_pbvh_bmesh_node_unique_verts(PBVHNode *node)
{
return node->bm_unique_verts;
}
const blender::Set<BMVert *, 0> &BKE_pbvh_bmesh_node_other_verts(PBVHNode *node)
{
return node->bm_other_verts;
}
const blender::Set<BMFace *, 0> &BKE_pbvh_bmesh_node_faces(PBVHNode *node)
{
return node->bm_faces;
}
/****************************** Debugging *****************************/
#if 0
static void pbvh_bmesh_print(PBVH *pbvh)
{
fprintf(stderr, "\npbvh=%p\n", pbvh);
fprintf(stderr, "bm_face_to_node:\n");
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) {
fprintf(
stderr, " %d -> %d\n", BM_elem_index_get(f), pbvh_bmesh_node_index_from_face(pbvh, f));
}
fprintf(stderr, "bm_vert_to_node:\n");
BMVert *v;
BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_FACES_OF_MESH) {
fprintf(
stderr, " %d -> %d\n", BM_elem_index_get(v), pbvh_bmesh_node_index_from_vert(pbvh, v));
}
for (int n = 0; n < pbvh->totnode; n++) {
PBVHNode *node = &pbvh->nodes[n];
if (!(node->flag & PBVH_Leaf)) {
continue;
}
GSetIterator gs_iter;
fprintf(stderr, "node %d\n faces:\n", n);
GSET_ITER (gs_iter, node->bm_faces)
fprintf(stderr, " %d\n", BM_elem_index_get((BMFace *)BLI_gsetIterator_getKey(&gs_iter)));
fprintf(stderr, " unique verts:\n");
GSET_ITER (gs_iter, node->bm_unique_verts)
fprintf(stderr, " %d\n", BM_elem_index_get((BMVert *)BLI_gsetIterator_getKey(&gs_iter)));
fprintf(stderr, " other verts:\n");
GSET_ITER (gs_iter, node->bm_other_verts)
fprintf(stderr, " %d\n", BM_elem_index_get((BMVert *)BLI_gsetIterator_getKey(&gs_iter)));
}
}
static void print_flag_factors(int flag)
{
printf("flag=0x%x:\n", flag);
for (int i = 0; i < 32; i++) {
if (flag & (1 << i)) {
printf(" %d (1 << %d)\n", 1 << i, i);
}
}
}
#endif
#ifdef USE_VERIFY
static void pbvh_bmesh_verify(PBVH *pbvh)
{
/* Build list of faces & verts to lookup. */
GSet *faces_all = BLI_gset_ptr_new_ex(__func__, pbvh->header.bm->totface);
BMIter iter;
{
BMFace *f;
BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) {
BLI_assert(BM_ELEM_CD_GET_INT(f, pbvh->cd_face_node_offset) != DYNTOPO_NODE_NONE);
BLI_gset_insert(faces_all, f);
}
}
GSet *verts_all = BLI_gset_ptr_new_ex(__func__, pbvh->header.bm->totvert);
{
BMVert *v;
BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) {
if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) != DYNTOPO_NODE_NONE) {
BLI_gset_insert(verts_all, v);
}
}
}
/* Check vert/face counts. */
{
int totface = 0, totvert = 0;
for (int i = 0; i < pbvh->totnode; i++) {
PBVHNode *n = &pbvh->nodes[i];
totface += n->bm_faces.is_empty() ? n->bm_faces.size() : 0;
totvert += n->bm_unique_verts ? n->bm_unique_verts.size() : 0;
}
BLI_assert(totface == BLI_gset_len(faces_all));
BLI_assert(totvert == BLI_gset_len(verts_all));
}
{
BMFace *f;
BM_ITER_MESH (f, &iter, pbvh->header.bm, BM_FACES_OF_MESH) {
BMIter bm_iter;
BMVert *v;
PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, f);
/* Check that the face's node is a leaf. */
BLI_assert(n->flag & PBVH_Leaf);
/* Check that the face's node knows it owns the face. */
BLI_assert(n->bm_faces.contains(f));
/* Check the face's vertices... */
BM_ITER_ELEM (v, &bm_iter, f, BM_VERTS_OF_FACE) {
PBVHNode *nv;
/* Check that the vertex is in the node. */
BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v) ^ BLI_gset_haskey(n->bm_other_verts, v));
/* Check that the vertex has a node owner. */
nv = pbvh_bmesh_node_lookup(pbvh, v);
/* Check that the vertex's node knows it owns the vert. */
BLI_assert(BLI_gset_haskey(nv->bm_unique_verts, v));
/* Check that the vertex isn't duplicated as an 'other' vert. */
BLI_assert(!BLI_gset_haskey(nv->bm_other_verts, v));
}
}
}
/* Check verts */
{
BMVert *v;
BM_ITER_MESH (v, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) {
/* Vertex isn't tracked. */
if (BM_ELEM_CD_GET_INT(v, pbvh->cd_vert_node_offset) == DYNTOPO_NODE_NONE) {
continue;
}
PBVHNode *n = pbvh_bmesh_node_lookup(pbvh, v);
/* Check that the vert's node is a leaf. */
BLI_assert(n->flag & PBVH_Leaf);
/* Check that the vert's node knows it owns the vert. */
BLI_assert(BLI_gset_haskey(n->bm_unique_verts, v));
/* Check that the vertex isn't duplicated as an 'other' vert. */
BLI_assert(!BLI_gset_haskey(n->bm_other_verts, v));
/* Check that the vert's node also contains one of the vert's adjacent faces. */
bool found = false;
BMIter bm_iter;
BMFace *f = nullptr;
BM_ITER_ELEM (f, &bm_iter, v, BM_FACES_OF_VERT) {
if (pbvh_bmesh_node_lookup(pbvh, f) == n) {
found = true;
break;
}
}
BLI_assert(found || f == nullptr);
# if 1
/* total freak stuff, check if node exists somewhere else */
/* Slow */
for (int i = 0; i < pbvh->totnode; i++) {
PBVHNode *n_other = &pbvh->nodes[i];
if ((n != n_other) && (n_other->bm_unique_verts)) {
BLI_assert(!BLI_gset_haskey(n_other->bm_unique_verts, v));
}
}
# endif
}
}
# if 0
/* check that every vert belongs somewhere */
/* Slow */
BM_ITER_MESH (vi, &iter, pbvh->header.bm, BM_VERTS_OF_MESH) {
bool has_unique = false;
for (int i = 0; i < pbvh->totnode; i++) {
PBVHNode *n = &pbvh->nodes[i];
if ((n->bm_unique_verts != nullptr) && BLI_gset_haskey(n->bm_unique_verts, vi)) {
has_unique = true;
}
}
BLI_assert(has_unique);
vert_count++;
}
/* If totvert differs from number of verts inside the hash. hash-totvert is checked above. */
BLI_assert(vert_count == pbvh->header.bm->totvert);
# endif
/* Check that node elements are recorded in the top level */
for (int i = 0; i < pbvh->totnode; i++) {
PBVHNode *n = &pbvh->nodes[i];
if (n->flag & PBVH_Leaf) {
GSetIterator gs_iter;
for (BMFace *f : n->bm_faces) {
PBVHNode *n_other = pbvh_bmesh_node_lookup(pbvh, f);
BLI_assert(n == n_other);
BLI_assert(BLI_gset_haskey(faces_all, f));
}
GSET_ITER (gs_iter, n->bm_unique_verts) {
BMVert *v = BLI_gsetIterator_getKey(&gs_iter);
PBVHNode *n_other = pbvh_bmesh_node_lookup(pbvh, v);
BLI_assert(!BLI_gset_haskey(n->bm_other_verts, v));
BLI_assert(n == n_other);
BLI_assert(BLI_gset_haskey(verts_all, v));
}
GSET_ITER (gs_iter, n->bm_other_verts) {
BMVert *v = BLI_gsetIterator_getKey(&gs_iter);
/* this happens sometimes and seems harmless */
// BLI_assert(!BM_vert_face_check(v));
BLI_assert(BLI_gset_haskey(verts_all, v));
}
}
}
BLI_gset_free(faces_all, nullptr);
BLI_gset_free(verts_all, nullptr);
}
#endif