Add Chromium-only Blender WebEngine parity work

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

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM element callback functions.
*/
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_callback_generic.hh"
namespace blender {
bool BM_elem_cb_check_hflag_ex(BMElem *ele, void *user_data)
{
const uint hflag_pair = POINTER_AS_INT(user_data);
const char hflag_p = (hflag_pair & 0xff);
const char hflag_n = (hflag_pair >> 8);
return ((BM_elem_flag_test(ele, hflag_p) != 0) && (BM_elem_flag_test(ele, hflag_n) == 0));
}
bool BM_elem_cb_check_hflag_enabled(BMElem *ele, void *user_data)
{
const char hflag = POINTER_AS_INT(user_data);
return (BM_elem_flag_test(ele, hflag) != 0);
}
bool BM_elem_cb_check_hflag_disabled(BMElem *ele, void *user_data)
{
const char hflag = POINTER_AS_INT(user_data);
return (BM_elem_flag_test(ele, hflag) == 0);
}
bool BM_elem_cb_check_elem_not_equal(BMElem *ele, void *user_data)
{
return (ele != user_data);
}
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct BMElem;
bool BM_elem_cb_check_hflag_enabled(BMElem *, void *user_data);
bool BM_elem_cb_check_hflag_disabled(BMElem *, void *user_data);
bool BM_elem_cb_check_hflag_ex(BMElem *, void *user_data);
bool BM_elem_cb_check_elem_not_equal(BMElem *ele, void *user_data);
#define BM_elem_cb_check_hflag_ex_simple(type, hflag_p, hflag_n) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_ex, \
POINTER_FROM_UINT(((hflag_p) | (hflag_n << 8)))
#define BM_elem_cb_check_hflag_enabled_simple(type, hflag_p) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_enabled, POINTER_FROM_UINT((hflag_p))
#define BM_elem_cb_check_hflag_disabled_simple(type, hflag_n) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_disabled, POINTER_FROM_UINT(hflag_n)
} // namespace blender

View File

@@ -0,0 +1,714 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM construction functions.
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "DNA_mesh_types.h"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
bool BM_verts_from_edges(BMVert **vert_arr, BMEdge **edge_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
vert_arr[i] = BM_edge_share_vert(edge_arr[i_prev], edge_arr[i]);
if (vert_arr[i] == nullptr) {
return false;
}
i_prev = i;
}
return true;
}
bool BM_edges_from_verts(BMEdge **edge_arr, BMVert **vert_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
edge_arr[i_prev] = BM_edge_exists(vert_arr[i_prev], vert_arr[i]);
if (edge_arr[i_prev] == nullptr) {
return false;
}
i_prev = i;
}
return true;
}
void BM_edges_from_verts_ensure(BMesh *bm, BMEdge **edge_arr, BMVert **vert_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
edge_arr[i_prev] = BM_edge_create(
bm, vert_arr[i_prev], vert_arr[i], nullptr, BM_CREATE_NO_DOUBLE);
i_prev = i;
}
}
BMFace *BM_face_create_quad_tri(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMVert *v3,
BMVert *v4,
const BMFace *f_example,
const eBMCreateFlag create_flag)
{
BMVert *vtar[4] = {v1, v2, v3, v4};
return BM_face_create_verts(bm, vtar, v4 ? 4 : 3, f_example, create_flag, true);
}
void BM_face_copy_shared(BMesh *bm, BMFace *f, BMLoopFilterFunc filter_fn, void *user_data)
{
BMLoop *l_first;
BMLoop *l_iter;
#ifndef NDEBUG
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BLI_assert(BM_ELEM_API_FLAG_TEST(l_iter, _FLAG_OVERLAP) == 0);
} while ((l_iter = l_iter->next) != l_first);
#endif
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_other = l_iter->radial_next;
if (l_other && l_other != l_iter) {
BMLoop *l_src[2];
BMLoop *l_dst[2] = {l_iter, l_iter->next};
uint j;
if (l_other->v == l_iter->v) {
l_src[0] = l_other;
l_src[1] = l_other->next;
}
else {
l_src[0] = l_other->next;
l_src[1] = l_other;
}
for (j = 0; j < 2; j++) {
BLI_assert(l_dst[j]->v == l_src[j]->v);
if (BM_ELEM_API_FLAG_TEST(l_dst[j], _FLAG_OVERLAP) == 0) {
if ((filter_fn == nullptr) || filter_fn(l_src[j], user_data)) {
CustomData_bmesh_copy_block(bm->ldata, l_src[j]->head.data, &l_dst[j]->head.data);
BM_ELEM_API_FLAG_ENABLE(l_dst[j], _FLAG_OVERLAP);
}
}
}
}
} while ((l_iter = l_iter->next) != l_first);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_ELEM_API_FLAG_DISABLE(l_iter, _FLAG_OVERLAP);
} while ((l_iter = l_iter->next) != l_first);
}
/**
* Given an array of edges,
* order them using the winding defined by \a v1 & \a v2
* into \a edges_sort & \a verts_sort.
*
* All arrays must be \a len long.
*/
static bool bm_edges_sort_winding(BMVert *v1,
BMVert *v2,
BMEdge **edges,
const int len,
BMEdge **edges_sort,
BMVert **verts_sort)
{
BMEdge *e_iter, *e_first;
BMVert *v_iter;
int i;
/* all flags _must_ be cleared on exit! */
for (i = 0; i < len; i++) {
BM_ELEM_API_FLAG_ENABLE(edges[i], _FLAG_MF);
BM_ELEM_API_FLAG_ENABLE(edges[i]->v1, _FLAG_MV);
BM_ELEM_API_FLAG_ENABLE(edges[i]->v2, _FLAG_MV);
}
/* find first edge */
i = 0;
v_iter = v1;
e_iter = e_first = v1->e;
do {
if (BM_ELEM_API_FLAG_TEST(e_iter, _FLAG_MF) && (BM_edge_other_vert(e_iter, v_iter) == v2)) {
i = 1;
break;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v_iter)) != e_first);
if (i == 0) {
goto error;
}
i = 0;
do {
/* entering loop will always succeed */
if (BM_ELEM_API_FLAG_TEST(e_iter, _FLAG_MF)) {
if (UNLIKELY(BM_ELEM_API_FLAG_TEST(v_iter, _FLAG_MV) == false)) {
/* vert is in loop multiple times */
goto error;
}
BM_ELEM_API_FLAG_DISABLE(e_iter, _FLAG_MF);
edges_sort[i] = e_iter;
BM_ELEM_API_FLAG_DISABLE(v_iter, _FLAG_MV);
verts_sort[i] = v_iter;
i += 1;
/* walk onto the next vertex */
v_iter = BM_edge_other_vert(e_iter, v_iter);
if (i == len) {
if (UNLIKELY(v_iter != verts_sort[0])) {
goto error;
}
break;
}
e_first = e_iter;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v_iter)) != e_first);
if (i == len) {
return true;
}
error:
for (i = 0; i < len; i++) {
BM_ELEM_API_FLAG_DISABLE(edges[i], _FLAG_MF);
BM_ELEM_API_FLAG_DISABLE(edges[i]->v1, _FLAG_MV);
BM_ELEM_API_FLAG_DISABLE(edges[i]->v2, _FLAG_MV);
}
return false;
}
BMFace *BM_face_create_ngon(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMEdge **edges,
const int len,
const BMFace *f_example,
const eBMCreateFlag create_flag)
{
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_sort(len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts_sort(len);
BLI_assert(len && v1 && v2 && edges && bm);
if (bm_edges_sort_winding(v1, v2, edges, len, edges_sort.data(), verts_sort.data())) {
return BM_face_create(bm, verts_sort.data(), edges_sort.data(), len, f_example, create_flag);
}
return nullptr;
}
BMFace *BM_face_create_ngon_verts(BMesh *bm,
BMVert **vert_arr,
const int len,
const BMFace *f_example,
const eBMCreateFlag create_flag,
const bool calc_winding,
const bool create_edges)
{
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edge_arr(len);
uint winding[2] = {0, 0};
int i, i_prev = len - 1;
BMVert *v_winding[2] = {vert_arr[i_prev], vert_arr[0]};
BLI_assert(len > 2);
for (i = 0; i < len; i++) {
if (create_edges) {
edge_arr[i] = BM_edge_create(
bm, vert_arr[i_prev], vert_arr[i], nullptr, BM_CREATE_NO_DOUBLE);
}
else {
edge_arr[i] = BM_edge_exists(vert_arr[i_prev], vert_arr[i]);
if (edge_arr[i] == nullptr) {
return nullptr;
}
}
if (calc_winding) {
/* the edge may exist already and be attached to a face
* in this case we can find the best winding to use for the new face */
if (edge_arr[i]->l) {
BMVert *test_v1, *test_v2;
/* we want to use the reverse winding to the existing order */
BM_edge_ordered_verts(edge_arr[i], &test_v2, &test_v1);
winding[(vert_arr[i_prev] == test_v2)]++;
BLI_assert(ELEM(vert_arr[i_prev], test_v2, test_v1));
}
}
i_prev = i;
}
/* --- */
if (calc_winding) {
if (winding[0] < winding[1]) {
winding[0] = 1;
winding[1] = 0;
}
else {
winding[0] = 0;
winding[1] = 1;
}
}
else {
winding[0] = 0;
winding[1] = 1;
}
/* --- */
/* create the face */
return BM_face_create_ngon(bm,
v_winding[winding[0]],
v_winding[winding[1]],
edge_arr.data(),
len,
f_example,
create_flag);
}
void BM_verts_sort_radial_plane(BMVert **vert_arr, int len)
{
using AngleIndex = std::pair<float, int>;
Array<AngleIndex, BM_DEFAULT_NGON_STACK_SIZE> vang(len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vert_arr_map(len);
float nor[3], cent[3];
int index_tangent = 0;
BM_verts_calc_normal_from_cloud_ex(vert_arr, len, nor, cent, &index_tangent);
const float *far = vert_arr[index_tangent]->co;
/* Now calculate every points angle around the normal (signed). */
for (int i = 0; i < len; i++) {
vang[i].first = angle_signed_on_axis_v3v3v3_v3(far, cent, vert_arr[i]->co, nor);
vang[i].second = i;
vert_arr_map[i] = vert_arr[i];
}
/* sort by angle and magic! - we have our ngon */
std::ranges::sort(vang,
[](const AngleIndex &a, const AngleIndex &b) { return a.first < b.first; });
/* --- */
for (int i = 0; i < len; i++) {
vert_arr[i] = vert_arr_map[vang[i].second];
}
}
/*************************************************************/
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMVert *src, BMVert *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->vdata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMEdge *src, BMEdge *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->edata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMFace *src, BMFace *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->pdata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
dst->mat_nr = src->mat_nr;
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMLoop *src, BMLoop *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->ldata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMVert *src, BMVert *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->vdata, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
}
void BM_elem_attrs_copy(BMesh *bm, const BMEdge *src, BMEdge *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->edata, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMFace *src, BMFace *dst)
{
BLI_assert(src != dst);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV;
CustomData_bmesh_copy_block(bm->pdata, src->head.data, &dst->head.data);
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
dst->mat_nr = src->mat_nr;
}
void BM_elem_attrs_copy(BMesh *bm, const BMLoop *src, BMLoop *dst)
{
BLI_assert(src != dst);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE;
CustomData_bmesh_copy_block(bm->ldata, src->head.data, &dst->head.data);
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v)
{
BMHeader *ele_dst = static_cast<BMHeader *>(ele_dst_v);
const BMHeader *ele_src = static_cast<const BMHeader *>(ele_src_v);
BLI_assert(ele_src->htype == ele_dst->htype);
if ((ele_src->hflag & BM_ELEM_SELECT) != (ele_dst->hflag & BM_ELEM_SELECT)) {
BM_elem_select_set(
bm_dst, reinterpret_cast<BMElem *>(ele_dst), (ele_src->hflag & BM_ELEM_SELECT) != 0);
}
}
/* helper function for 'BM_mesh_copy' */
static BMFace *bm_mesh_copy_new_face(BMesh *bm_new,
const BMCustomDataCopyMap &face_map,
const BMCustomDataCopyMap &loop_map,
BMVert **vtable,
BMEdge **etable,
BMFace *f)
{
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops(f->len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts(f->len);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges(f->len);
BMFace *f_new;
BMLoop *l_iter, *l_first;
int j;
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
loops[j] = l_iter;
verts[j] = vtable[BM_elem_index_get(l_iter->v)];
edges[j] = etable[BM_elem_index_get(l_iter->e)];
j++;
} while ((l_iter = l_iter->next) != l_first);
f_new = BM_face_create(bm_new, verts.data(), edges.data(), f->len, nullptr, BM_CREATE_SKIP_CD);
if (UNLIKELY(f_new == nullptr)) {
return nullptr;
}
/* use totface in case adding some faces fails */
BM_elem_index_set(f_new, (bm_new->totface - 1)); /* set_inline */
CustomData_bmesh_copy_block(bm_new->pdata, face_map, f->head.data, &f_new->head.data);
copy_v3_v3(f_new->no, f->no);
f_new->mat_nr = f->mat_nr;
f_new->head.hflag = f->head.hflag; /* Low level! don't do this for normal API use. */
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
do {
CustomData_bmesh_copy_block(bm_new->ldata, loop_map, loops[j]->head.data, &l_iter->head.data);
l_iter->head.hflag = loops[j]->head.hflag & ~BM_ELEM_SELECT;
j++;
} while ((l_iter = l_iter->next) != l_first);
return f_new;
}
static CustomData &get_bmesh_custom_data(BMesh &bm, const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return bm.vdata;
case bke::AttrDomain::Edge:
return bm.edata;
case bke::AttrDomain::Face:
return bm.pdata;
case bke::AttrDomain::Corner:
return bm.ldata;
default:
BLI_assert_unreachable();
return bm.vdata;
}
}
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
const Mesh *me_src_array[],
const int me_src_array_len,
const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
bke::GeometrySet::GatheredAttributes attribute_info;
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
for (const bke::Attribute &attr : me_src->attribute_storage.wrap()) {
if (BM_attribute_stored_in_bmesh_builtin(attr.name())) {
continue;
}
attribute_info.add(attr.name(), {attr.domain(), attr.data_type()});
}
}
for (const int i : attribute_info.names.index_range()) {
const StringRef name = attribute_info.names[i];
const bke::AttrDomain domain = attribute_info.kinds[i].domain;
const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(
attribute_info.kinds[i].data_type);
CustomData &custom_data = get_bmesh_custom_data(*bm_dst, domain);
CustomData_add_layer_named(&custom_data, data_type, CD_SET_DEFAULT, 0, name);
}
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
CustomData_merge_layout(
&me_src->vert_data, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->edge_data, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->face_data, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->corner_data, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
}
CustomData_bmesh_init_pool(&bm_dst->vdata, allocsize->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm_dst->edata, allocsize->totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm_dst->ldata, allocsize->totloop, BM_LOOP);
CustomData_bmesh_init_pool(&bm_dst->pdata, allocsize->totface, BM_FACE);
}
void BM_mesh_copy_init_customdata_from_mesh(BMesh *bm_dst,
const Mesh *me_src,
const BMAllocTemplate *allocsize)
{
BM_mesh_copy_init_customdata_from_mesh_array(bm_dst, &me_src, 1, allocsize);
}
void BM_mesh_copy_init_customdata(BMesh *bm_dst, BMesh *bm_src, const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
CustomData_init_layout_from(
&bm_src->vdata, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->edata, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->ldata, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->pdata, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_bmesh_init_pool(&bm_dst->vdata, allocsize->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm_dst->edata, allocsize->totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm_dst->ldata, allocsize->totloop, BM_LOOP);
CustomData_bmesh_init_pool(&bm_dst->pdata, allocsize->totface, BM_FACE);
}
void BM_mesh_copy_init_customdata_all_layers(BMesh *bm_dst,
BMesh *bm_src,
const char htype,
const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
const char htypes[4] = {BM_VERT, BM_EDGE, BM_LOOP, BM_FACE};
BLI_assert(((&bm_dst->vdata + 1) == &bm_dst->edata) &&
((&bm_dst->vdata + 2) == &bm_dst->ldata) && ((&bm_dst->vdata + 3) == &bm_dst->pdata));
BLI_assert(((&allocsize->totvert + 1) == &allocsize->totedge) &&
((&allocsize->totvert + 2) == &allocsize->totloop) &&
((&allocsize->totvert + 3) == &allocsize->totface));
for (int i = 0; i < 4; i++) {
if (!(htypes[i] & htype)) {
continue;
}
CustomData *dst = &bm_dst->vdata + i;
CustomData *src = &bm_src->vdata + i;
const int size = *(&allocsize->totvert + i);
for (int l = 0; l < src->totlayer; l++) {
CustomData_add_layer_named(
dst, eCustomDataType(src->layers[l].type), CD_SET_DEFAULT, 0, src->layers[l].name);
/* Needed to keep this a working shape key layer (see also #customdata_merge_internal). */
dst->layers[l].uid = src->layers[l].uid;
}
CustomData_bmesh_init_pool(dst, size, htypes[i]);
}
}
BMesh *BM_mesh_copy(BMesh *bm_old)
{
BMesh *bm_new;
BMVert *v, *v_new, **vtable = nullptr;
BMEdge *e, *e_new, **etable = nullptr;
BMFace *f, *f_new, **ftable = nullptr;
BMElem **eletable;
BMIter iter;
int i;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_BM(bm_old);
/* allocate a bmesh */
BMeshCreateParams params{};
params.use_toolflags = bm_old->use_toolflags;
bm_new = BM_mesh_create(&allocsize, &params);
BM_mesh_copy_init_customdata(bm_new, bm_old, &allocsize);
const BMCustomDataCopyMap vert_map = CustomData_bmesh_copy_map_calc(bm_old->vdata,
bm_new->vdata);
const BMCustomDataCopyMap edge_map = CustomData_bmesh_copy_map_calc(bm_old->edata,
bm_new->edata);
const BMCustomDataCopyMap face_map = CustomData_bmesh_copy_map_calc(bm_old->pdata,
bm_new->pdata);
const BMCustomDataCopyMap loop_map = CustomData_bmesh_copy_map_calc(bm_old->ldata,
bm_new->ldata);
vtable = MEM_new_array_uninitialized<BMVert *>(bm_old->totvert, "BM_mesh_copy vtable");
etable = MEM_new_array_uninitialized<BMEdge *>(bm_old->totedge, "BM_mesh_copy etable");
ftable = MEM_new_array_uninitialized<BMFace *>(bm_old->totface, "BM_mesh_copy ftable");
BM_ITER_MESH_INDEX (v, &iter, bm_old, BM_VERTS_OF_MESH, i) {
/* copy between meshes so can't use 'example' argument */
v_new = BM_vert_create(bm_new, v->co, nullptr, BM_CREATE_SKIP_CD);
CustomData_bmesh_copy_block(bm_new->vdata, vert_map, v->head.data, &v_new->head.data);
copy_v3_v3(v_new->no, v->no);
v_new->head.hflag = v->head.hflag; /* Low level! don't do this for normal API use. */
vtable[i] = v_new;
BM_elem_index_set(v, i); /* set_inline */
BM_elem_index_set(v_new, i); /* set_inline */
}
bm_old->elem_index_dirty &= ~BM_VERT;
bm_new->elem_index_dirty &= ~BM_VERT;
/* safety check */
BLI_assert(i == bm_old->totvert);
BM_ITER_MESH_INDEX (e, &iter, bm_old, BM_EDGES_OF_MESH, i) {
e_new = BM_edge_create(bm_new,
vtable[BM_elem_index_get(e->v1)],
vtable[BM_elem_index_get(e->v2)],
e,
BM_CREATE_SKIP_CD);
CustomData_bmesh_copy_block(bm_new->edata, edge_map, e->head.data, &e_new->head.data);
e_new->head.hflag = e->head.hflag; /* Low level! don't do this for normal API use. */
etable[i] = e_new;
BM_elem_index_set(e, i); /* set_inline */
BM_elem_index_set(e_new, i); /* set_inline */
}
bm_old->elem_index_dirty &= ~BM_EDGE;
bm_new->elem_index_dirty &= ~BM_EDGE;
/* safety check */
BLI_assert(i == bm_old->totedge);
BM_ITER_MESH_INDEX (f, &iter, bm_old, BM_FACES_OF_MESH, i) {
BM_elem_index_set(f, i); /* set_inline */
f_new = bm_mesh_copy_new_face(bm_new, face_map, loop_map, vtable, etable, f);
ftable[i] = f_new;
if (f == bm_old->act_face) {
bm_new->act_face = f_new;
}
}
bm_old->elem_index_dirty &= ~BM_FACE;
bm_new->elem_index_dirty &= ~BM_FACE;
/* Low level! don't do this for normal API use. */
bm_new->totvertsel = bm_old->totvertsel;
bm_new->totedgesel = bm_old->totedgesel;
bm_new->totfacesel = bm_old->totfacesel;
/* safety check */
BLI_assert(i == bm_old->totface);
/* copy over edit selection history */
for (BMEditSelection &ese : bm_old->selected) {
BMElem *ele = nullptr;
switch (ese.htype) {
case BM_VERT:
eletable = reinterpret_cast<BMElem **>(vtable);
break;
case BM_EDGE:
eletable = reinterpret_cast<BMElem **>(etable);
break;
case BM_FACE:
eletable = reinterpret_cast<BMElem **>(ftable);
break;
default:
eletable = nullptr;
break;
}
if (eletable) {
ele = eletable[BM_elem_index_get(ese.ele)];
if (ele) {
BM_select_history_store(bm_new, ele);
}
}
}
MEM_delete(etable);
MEM_delete(vtable);
MEM_delete(ftable);
/* Copy various settings. */
bm_new->shapenr = bm_old->shapenr;
bm_new->selectmode = bm_old->selectmode;
return bm_new;
}
} // namespace blender

View File

@@ -0,0 +1,190 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "bmesh_core.hh"
namespace blender {
struct BMAllocTemplate;
struct BMCustomDataCopyMap;
struct Mesh;
/**
* Fill in a vertex array from an edge array.
*
* \returns false if any verts aren't found.
*/
bool BM_verts_from_edges(BMVert **vert_arr, BMEdge **edge_arr, int len);
/**
* Fill in an edge array from a vertex array (connected polygon loop).
*
* \returns false if any edges aren't found.
*/
bool BM_edges_from_verts(BMEdge **edge_arr, BMVert **vert_arr, int len);
/**
* Fill in an edge array from a vertex array (connected polygon loop).
* Creating edges as-needed.
*/
void BM_edges_from_verts_ensure(BMesh *bm, BMEdge **edge_arr, BMVert **vert_arr, int len);
/**
* Makes an NGon from an un-ordered set of verts.
*
* Assumes:
* - that verts are only once in the list.
* - that the verts have roughly planer bounds
* - that the verts are roughly circular
*
* There can be concave areas but overlapping folds from the center point will fail.
*
* A brief explanation of the method used
* - find the center point
* - find the normal of the vertex-cloud
* - order the verts around the face based on their angle to the normal vector at the center point.
*
* \note Since this is a vertex-cloud there is no direction.
*/
void BM_verts_sort_radial_plane(BMVert **vert_arr, int len);
/**
* \brief Make Quad/Triangle
*
* Creates a new quad or triangle from a list of 3 or 4 vertices.
* If \a no_double is true, then a check is done to see if a face
* with these vertices already exists and returns it instead.
*
* If a pointer to an example face is provided, its custom data
* and properties will be copied to the new face.
*
* \note The winding of the face is determined by the order
* of the vertices in the vertex array.
*/
BMFace *BM_face_create_quad_tri(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMVert *v3,
BMVert *v4,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* \brief copies face loop data from shared adjacent faces.
*
* \param filter_fn: A function that filters the source loops before copying
* (don't always want to copy all).
*
* \note when a matching edge is found, both loops of that edge are copied
* this is done since the face may not be completely surrounded by faces,
* this way: a quad with 2 connected quads on either side will still get all 4 loops updated
*/
void BM_face_copy_shared(BMesh *bm, BMFace *f, BMLoopFilterFunc filter_fn, void *user_data);
/**
* \brief Make NGon
*
* Makes an ngon from an unordered list of edges.
* Verts \a v1 and \a v2 define the winding of the new face.
*
* \a edges are not required to be ordered, simply to form
* a single closed loop as a whole.
*
* \note While this function will work fine when the edges
* are already sorted, if the edges are always going to be sorted,
* #BM_face_create should be considered over this function as it
* avoids some unnecessary work.
*/
BMFace *BM_face_create_ngon(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMEdge **edges,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* Create an ngon from an array of sorted verts
*
* Special features this has over other functions.
* - Optionally calculate winding based on surrounding edges.
* - Optionally create edges between vertices.
* - Uses verts so no need to find edges (handy when you only have verts)
*/
BMFace *BM_face_create_ngon_verts(BMesh *bm,
BMVert **vert_arr,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag,
bool calc_winding,
bool create_edges);
/**
* Copy attributes between elements with a precalculated map of copy operations. This significantly
* improves performance when copying, since all the work of finding common layers doesn't have to
* be done for every element.
*/
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMVert *src,
BMVert *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMEdge *src,
BMEdge *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMFace *src,
BMFace *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMLoop *src,
BMLoop *dst);
/** Copy attributes between elements in the same BMesh. */
void BM_elem_attrs_copy(BMesh *bm, const BMVert *src, BMVert *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMEdge *src, BMEdge *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMFace *src, BMFace *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMLoop *src, BMLoop *dst);
void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v);
/**
* Initialize the `bm_dst` layers in preparation for populating its contents with multiple meshes.
* Typically done using multiple calls to #BM_mesh_bm_from_me with the same `bm` argument.
*
* \note While the custom-data layers of all meshes are created, the active layers are set
* by the first instance mesh containing that layer type.
* This means the first mesh should always be the main mesh (from the user perspective),
* as this is the mesh they have control over (active UV layer for rendering for example).
*/
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
const Mesh *me_src_array[],
int me_src_array_len,
const BMAllocTemplate *allocsize);
void BM_mesh_copy_init_customdata_from_mesh(BMesh *bm_dst,
const Mesh *me_src,
const BMAllocTemplate *allocsize);
void BM_mesh_copy_init_customdata(BMesh *bm_dst, BMesh *bm_src, const BMAllocTemplate *allocsize);
/**
* Similar to #BM_mesh_copy_init_customdata but copies all layers ignoring
* flags like #CD_FLAG_NOCOPY.
*
* \param bm_dst: BMesh whose custom-data layers will be added.
* \param bm_src: BMesh whose custom-data layers will be copied.
* \param htype: Specifies which custom-data layers will be initiated.
* \param allocsize: Initialize the memory-pool before use (may be an estimate).
*/
void BM_mesh_copy_init_customdata_all_layers(BMesh *bm_dst,
BMesh *bm_src,
char htype,
const BMAllocTemplate *allocsize);
BMesh *BM_mesh_copy(BMesh *bm_old);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,488 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "DNA_listBase.h"
#include "BKE_customdata.hh"
#include "bmesh_class.hh"
struct BMLoopList;
namespace blender {
/**
* When copying between different BMesh objects,
* `copy_verts` & `copy_edges` should always be true.
*/
BMFace *BM_face_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_face_map,
const BMCustomDataCopyMap &cd_loop_map,
BMFace *f,
bool copy_verts,
bool copy_edges);
BMFace *BM_face_copy(BMesh *bm, BMFace *f, bool copy_verts, bool copy_edges);
enum eBMCreateFlag {
BM_CREATE_NOP = 0,
/** Faces and edges only. */
BM_CREATE_NO_DOUBLE = (1 << 1),
/**
* Skip custom-data - for all element types data,
* use if we immediately write custom-data into the element so this skips copying from 'example'
* arguments or setting defaults, speeds up conversion when data is converted all at once.
*/
BM_CREATE_SKIP_CD = (1 << 2),
};
/**
* \brief Main function for creating a new vertex.
*/
BMVert *BM_vert_create(BMesh *bm,
const float co[3],
const BMVert *v_example,
eBMCreateFlag create_flag);
/**
* \brief Main function for creating a new edge.
*
* \note Duplicate edges are supported by the API however users should _never_ see them.
* so unless you need a unique edge or know the edge won't exist,
* you should call with \a no_double = true.
*/
BMEdge *BM_edge_create(
BMesh *bm, BMVert *v1, BMVert *v2, const BMEdge *e_example, eBMCreateFlag create_flag);
/**
* Main face creation function
*
* \param bm: The mesh
* \param verts: A sorted array of verts size of len
* \param edges: A sorted array of edges size of len
* \param len: Length of the face
* \param create_flag: Options for creating the face
*/
BMFace *BM_face_create(BMesh *bm,
BMVert *const *verts,
BMEdge *const *edges,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* Wrapper for #BM_face_create when you don't have an edge array
*/
BMFace *BM_face_create_verts(BMesh *bm,
BMVert **vert_arr,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag,
bool create_edges);
/**
* Kills all edges associated with \a f, along with any other faces containing those edges.
*/
void BM_face_edges_kill(BMesh *bm, BMFace *f);
/**
* kills all verts associated with \a f, along with any other faces containing
* those vertices
*/
void BM_face_verts_kill(BMesh *bm, BMFace *f);
/**
* A version of #BM_face_kill which removes edges and verts
* which have no remaining connected geometry.
*/
void BM_face_kill_loose(BMesh *bm, BMFace *f);
/**
* Kills \a f and its loops.
*/
void BM_face_kill(BMesh *bm, BMFace *f);
/**
* Kills \a e and all faces that use it.
*/
void BM_edge_kill(BMesh *bm, BMEdge *e);
/**
* Kills \a v and all edges that use it.
*/
void BM_vert_kill(BMesh *bm, BMVert *v);
/**
* \brief Splice Edge
*
* Splice two unique edges which share the same two vertices into one edge.
* (\a e_src into \a e_dst, removing e_src).
*
* \return Success
*
* \note Edges must already have the same vertices.
*/
bool BM_edge_splice(BMesh *bm, BMEdge *e_dst, BMEdge *e_src);
/**
* \brief Splice Vert
*
* Merges two verts into one
* (\a v_src into \a v_dst, removing \a v_src).
*
* \return Success
*
* \warning This doesn't work for collapsing edges,
* where \a v and \a vtarget are connected by an edge
* (assert checks for this case).
*
* \note To check if collapsing would create duplicate geometry, see:
* - #BM_vert_splice_check_double_edge.
* - #BM_vert_splice_check_double_face.
*/
bool BM_vert_splice(BMesh *bm, BMVert *v_dst, BMVert *v_src);
/**
* Check if splicing vertices would create any double edges.
*
* \note assume caller will handle case where verts share an edge.
*/
bool BM_vert_splice_check_double_edge(BMVert *v_a, BMVert *v_b);
/**
* Check if splicing vertices would create any double faces.
*
* \return true if calling #BM_vert_splice on the vertex pair would create a duplicate face.
*/
bool BM_vert_splice_check_double_face(BMVert *v_a, BMVert *v_b);
/**
* Check if collapsing `v_collapse`.would create duplicate faces.
*
* \param v_collapse: A vertex with exactly two connected edges (see #BM_vert_is_edge_pair).
*
* \return true if calling #BM_vert_collapse on `v_collapse` would create a duplicate face.
*/
bool BM_vert_collapse_check_double_face(BMVert *v_collapse);
/**
* Check if splitting a face between `l_a->v` & `l_b->v` would create
* a duplicate face on either side of the split.
*
* \note Arguments `(l_a, l_b, f_len)` are equivalent to `(l_b, l_a, (l_a->f->len - f_len) + 2)`,
* that is to say - the side of the face checked isn't important.
*/
bool BM_face_split_check_double_face(BMLoop *l_a, BMLoop *l_b, int f_len);
/**
* Check two faces share the same vertices over a partial span of their loops,
* comparing `l_a` -> `l_a_end` against `l_b` -> `l_b_end` (inclusive).
* Both spans must have the same topological length,
* and `l_a` & `l_b` must belong to different faces.
*
* Useful to check whether collapsing, splicing or splitting would create a duplicate face.
*/
bool BM_face_pair_overlap_check_subset_same_winding(const BMLoop *l_a,
const BMLoop *l_a_end,
const BMLoop *l_b,
const BMLoop *l_b_end);
/**
* A version of #BM_face_pair_overlap_check_subset_same_winding that walks `l_b` -> `l_b_end`
* in the reverse direction (for a candidate face of opposite winding).
*/
bool BM_face_pair_overlap_check_subset_swap_winding(const BMLoop *l_a,
const BMLoop *l_a_end,
const BMLoop *l_b,
const BMLoop *l_b_end);
/**
* \brief Loop Reverse
*
* Changes the winding order of a face from CW to CCW or vice versa.
*
* \param cd_loop_mdisp_offset: Cached result of `CustomData_get_offset(&bm->ldata, CD_MDISPS)`.
* \param use_loop_mdisp_flip: When set, flip the Z-depth of the mdisp,
* (use when flipping normals, disable when mirroring, eg: symmetrize).
*/
void bmesh_kernel_loop_reverse(BMesh *bm,
BMFace *f,
int cd_loop_mdisp_offset,
bool use_loop_mdisp_flip);
/**
* Avoid calling this where possible,
* low level function so both face pointers remain intact but point to swapped data.
* \note must be from the same bmesh.
*/
void bmesh_face_swap_data(BMFace *f_a, BMFace *f_b);
/**
* \brief Join Connected Faces
*
* Joins a collected group of faces into one. Only restriction on
* the input data is that the faces must be connected to each other.
*
* \return The newly created combine BMFace.
*
* \note If a pair of faces share multiple edges,
* the pair of faces will be joined at every edge.
*
* \param bm: The bmesh.
* \param faces: An array of faces to join.
* \param totface: The length of the face array to join.
* \param do_del: if true, remove the original faces, internal edges, and internal verts such that
* they are replaced by the new face.
* \param r_double: A pointer to a BMFace* that is controls processing of doubled faces.
* - When `r_double` is nullptr:
* - If a new face would be made which would double an existing face, then instead of creating a
* new face, the existing face will be reused and returned instead.
* - The calling function must not make ANY assumption about whether the returned BMFace* is
* new, or a reused face that may already have set header flags, contain custom data, etc.
* - When `r_double` is a pointer to a BMFace*:
* - If the new join face is not a double of an existing face, then `r_double` is set to nullptr.
* - If the new join face doubles an existing face, then `r_double` is set to the existing face,
* and the return value is the newly created face. The double will NOT be removed, meaning the
* BMesh is in an invalid state, and the calling function must fix that inconsistency.
* - If an error occurs and nullptr is returned, `r_double` will be set to nullptr as well.
*
* \note this is a generic, flexible join faces function,
* almost everything uses this, including #BM_faces_join_pair
*
* \note On callers asserting when `*r_double != nullptr`.
* For some callers the existing algorithm does not check for or handle double faces.
* This can result in invalid meshes being returned.
* The returned value in `r_double` should be examined and if found,
* the algorithm should be adjusted. Until this is changed, at least warn.
* This comment can be removed when all callers handle this case.
*/
BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, bool do_del, BMFace **r_double);
/**
* High level function which wraps both #bmesh_kernel_vert_separate and #bmesh_kernel_edge_separate
*/
void BM_vert_separate(BMesh *bm,
BMVert *v,
BMEdge **e_in,
int e_in_len,
bool copy_select,
BMVert ***r_vout,
int *r_vout_len);
/**
* A version of #BM_vert_separate which takes a flag.
*/
void BM_vert_separate_hflag(
BMesh *bm, BMVert *v, char hflag, bool copy_select, BMVert ***r_vout, int *r_vout_len);
void BM_vert_separate_tested_edges(
BMesh *bm, BMVert *v_dst, BMVert *v_src, bool (*testfn)(BMEdge *, void *arg), void *arg);
/**
* BMesh Kernel: For modifying structure.
*
* Names are on the verbose side but these are only for low-level access.
*/
/**
* \brief Separate Vert
*
* Separates all disjoint fans that meet at a vertex, making a unique
* vertex for each region. returns an array of all resulting vertices.
*
* \note this is a low level function, bm_edge_separate needs to run on edges first
* or, the faces sharing verts must not be sharing edges for them to split at least.
*
* \return Success
*/
void bmesh_kernel_vert_separate(
BMesh *bm, BMVert *v, BMVert ***r_vout, int *r_vout_len, bool copy_select);
/**
* \brief Separate Edge
*
* Separates a single edge into two edge: the original edge and
* a new edge that has only \a l_sep in its radial.
*
* \return Success
*
* \note Does nothing if \a l_sep is already the only loop in the
* edge radial.
*/
void bmesh_kernel_edge_separate(BMesh *bm, BMEdge *e, BMLoop *l_sep, bool copy_select);
/**
* \brief Split Face Make Edge (SFME)
*
* \warning this is a low level function, most likely you want to use #BM_face_split()
*
* Takes as input two vertices in a single face.
* An edge is created which divides the original face into two distinct regions.
* One of the regions is assigned to the original face and it is closed off.
* The second region has a new face assigned to it.
*
* \par Examples:
* <pre>
* Before: After:
* +--------+ +--------+
* | | | |
* | | | f1 |
* v1 f1 v2 v1======v2
* | | | f2 |
* | | | |
* +--------+ +--------+
* </pre>
*
* \note the input vertices can be part of the same edge. This will
* result in a two edged face. This is desirable for advanced construction
* tools and particularly essential for edge bevel. Because of this it is
* up to the caller to decide what to do with the extra edge.
*
* \note If \a holes is NULL, then both faces will lose
* all holes from the original face. Also, you cannot split between
* a hole vert and a boundary vert; that case is handled by higher-
* level wrapping functions (when holes are fully implemented, anyway).
*
* \note that holes represents which holes goes to the new face, and of
* course this requires removing them from the existing face first, since
* you cannot have linked list links inside multiple lists.
*
* \return A BMFace pointer
*/
BMFace *bmesh_kernel_split_face_make_edge(BMesh *bm,
BMFace *f,
BMLoop *l_v1,
BMLoop *l_v2,
BMLoop **r_l,
#ifdef USE_BMESH_HOLES
ListBaseT<BMLoopList> *holes,
#endif
BMEdge *example,
bool no_double);
/**
* \brief Split Edge Make Vert (SEMV)
*
* Takes \a e edge and splits it into two, creating a new vert.
* \a tv should be one end of \a e : the newly created edge
* will be attached to that end and is returned in \a r_e.
*
* \par Examples:
*
* <pre>
* E
* Before: OV-------------TV
* E RE
* After: OV------NV-----TV
* </pre>
*
* \return The newly created BMVert pointer.
*/
BMVert *bmesh_kernel_split_edge_make_vert(BMesh *bm, BMVert *tv, BMEdge *e, BMEdge **r_e);
/**
* \brief Join Edge Kill Vert (JEKV)
*
* Takes an edge \a e_kill and pointer to one of its vertices \a v_kill
* and collapses the edge on that vertex.
*
* \par Examples:
*
* <pre>
* Before: e_old e_kill
* +-------+-------+
* | | |
* v_old v_kill v_target
*
* After: e_old
* +---------------+
* | |
* v_old v_target
* </pre>
*
* \par Restrictions:
* KV is a vertex that must have a valance of exactly two. Furthermore
* both edges in KV's disk cycle (OE and KE) must be unique (no double edges).
*
* \return The resulting edge, NULL for failure.
*
* \note This euler has the possibility of creating
* faces with just 2 edges. It is up to the caller to decide what to do with
* these faces.
*/
BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool check_edge_exists,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* \brief Join Vert Kill Edge (JVKE)
*
* Collapse an edge, merging surrounding data.
*
* Unlike #BM_vert_collapse_edge & #bmesh_kernel_join_edge_kill_vert
* which only handle 2 valence verts,
* this can handle any number of connected edges/faces.
*
* <pre>
* Before: -> After:
* +-+-+-+ +-+-+-+
* | | | | | \ / |
* +-+-+-+ +--+--+
* | | | | | / \ |
* +-+-+-+ +-+-+-+
* </pre>
*/
BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool check_edge_exists,
bool kill_degenerate_faces);
/**
* \brief Join Face Kill Edge (JFKE)
*
* Takes two faces joined by a single 2-manifold edge and fuses them together.
* The edge shared by the faces must not be connected to any other edges which have
* Both faces in its radial cycle
*
* \par Examples:
* <pre>
* A B
* +--------+ +--------+
* | | | |
* | f1 | | f1 |
* v1========v2 = Ok! v1==V2==v3 == Wrong!
* | f2 | | f2 |
* | | | |
* +--------+ +--------+
* </pre>
*
* In the example A, faces \a f1 and \a f2 are joined by a single edge,
* and the euler can safely be used.
* In example B however, \a f1 and \a f2 are joined by multiple edges and will produce an error.
* The caller in this case should call #bmesh_kernel_join_edge_kill_vert on the extra edges
* before attempting to fuse \a f1 and \a f2.
*
* \note The order of arguments decides whether or not certain per-face attributes are present
* in the resultant face. For instance vertex winding, material index, smooth flags,
* etc are inherited from \a f1, not \a f2.
*
* \return A BMFace pointer
*/
BMFace *bmesh_kernel_join_face_kill_edge(BMesh *bm, BMFace *f1, BMFace *f2, BMEdge *e);
/**
* \brief Un-glue Region Make Vert (URMV)
*
* Disconnects a face from its vertex fan at loop \a l_sep
*
* \return The newly created BMVert
*
* \note Will be a no-op and return original vertex if only two edges at that vertex.
*/
BMVert *bmesh_kernel_unglue_region_make_vert(BMesh *bm, BMLoop *l_sep);
/**
* A version of #bmesh_kernel_unglue_region_make_vert that disconnects multiple loops at once.
* The loops must all share the same vertex, can be in any order
* and are all moved to use a single new vertex - which is returned.
*
* This function handles the details of finding fans boundaries.
*/
BMVert *bmesh_kernel_unglue_region_make_vert_multi(BMesh *bm, BMLoop **larr, int larr_len);
/**
* This function assumes l_sep is a part of a larger fan which has already been
* isolated by calling #bmesh_kernel_edge_separate to segregate it radially.
*/
BMVert *bmesh_kernel_unglue_region_make_vert_multi_isolated(BMesh *bm, BMLoop *l_sep);
} // namespace blender

View File

@@ -0,0 +1,365 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM remove functions.
*/
#include "bmesh.hh"
namespace blender {
/* BMO functions */
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Delete Functions
* \{ */
/**
* Called by operators to remove elements that they have marked for
* removal.
*/
static void bmo_remove_tagged_faces(BMesh *bm, const short oflag)
{
BMFace *f, *f_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (f, f_next, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, oflag)) {
BM_face_kill(bm, f);
}
}
}
static void bmo_remove_tagged_edges(BMesh *bm, const short oflag)
{
BMEdge *e, *e_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, oflag)) {
BM_edge_kill(bm, e);
}
}
}
static void bmo_remove_tagged_verts(BMesh *bm, const short oflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag)) {
BM_vert_kill(bm, v);
}
}
}
static void bmo_remove_tagged_verts_loose(BMesh *bm, const short oflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag) && (v->e == nullptr)) {
BM_vert_kill(bm, v);
}
}
}
void BMO_mesh_delete_oflag_tagged(BMesh *bm, const short oflag, const char htype)
{
if (htype & BM_FACE) {
bmo_remove_tagged_faces(bm, oflag);
}
if (htype & BM_EDGE) {
bmo_remove_tagged_edges(bm, oflag);
}
if (htype & BM_VERT) {
bmo_remove_tagged_verts(bm, oflag);
}
}
void BMO_mesh_delete_oflag_context(BMesh *bm,
const short oflag,
const int type,
FunctionRef<void()> prepare_fn)
{
BMEdge *e;
BMIter eiter;
BMIter fiter;
switch (type) {
case DEL_VERTS: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_verts(bm, oflag);
break;
}
case DEL_EDGES: {
/* flush down to vert */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, oflag)) {
BMO_vert_flag_enable(bm, e->v1, oflag);
BMO_vert_flag_enable(bm, e->v2, oflag);
}
}
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_edges(bm, oflag);
bmo_remove_tagged_verts_loose(bm, oflag);
break;
}
case DEL_EDGESFACES: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_edges(bm, oflag);
break;
}
case DEL_ONLYFACES: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_faces(bm, oflag);
break;
}
case DEL_ONLYTAGGED: {
if (prepare_fn) {
prepare_fn();
}
BMO_mesh_delete_oflag_tagged(bm, oflag, BM_ALL_NOLOOP);
break;
}
case DEL_FACES:
case DEL_FACES_KEEP_BOUNDARY: {
/* go through and mark all edges and all verts of all faces for delete */
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, oflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_enable(bm, l_iter->v, oflag);
BMO_edge_flag_enable(bm, l_iter->e, oflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* now go through and mark all remaining faces all edges for keeping */
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BMO_face_flag_test(bm, f, oflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_disable(bm, l_iter->v, oflag);
BMO_edge_flag_disable(bm, l_iter->e, oflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* also mark all the vertices of remaining edges for keeping */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
/* Only exception to normal 'DEL_FACES' logic. */
if (type == DEL_FACES_KEEP_BOUNDARY) {
if (BM_edge_is_boundary(e)) {
BMO_edge_flag_disable(bm, e, oflag);
}
}
if (!BMO_edge_flag_test(bm, e, oflag)) {
BMO_vert_flag_disable(bm, e->v1, oflag);
BMO_vert_flag_disable(bm, e->v2, oflag);
}
}
if (prepare_fn) {
prepare_fn();
}
/* now delete marked face */
bmo_remove_tagged_faces(bm, oflag);
/* delete marked edge */
bmo_remove_tagged_edges(bm, oflag);
/* remove loose vertices */
bmo_remove_tagged_verts(bm, oflag);
break;
}
}
}
/** \} */
/* BM functions
*
* NOTE: this is just a duplicate of the code above (bad!)
* but for now keep in sync, its less hassle than having to create bmesh operator flags,
* each time we need to remove some geometry.
*/
/* -------------------------------------------------------------------- */
/** \name BMesh Delete Functions (no oflags)
* \{ */
static void bm_remove_tagged_faces(BMesh *bm, const char hflag)
{
BMFace *f, *f_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (f, f_next, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, hflag)) {
BM_face_kill(bm, f);
}
}
}
static void bm_remove_tagged_edges(BMesh *bm, const char hflag)
{
BMEdge *e, *e_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, hflag)) {
BM_edge_kill(bm, e);
}
}
}
static void bm_remove_tagged_verts(BMesh *bm, const char hflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, hflag)) {
BM_vert_kill(bm, v);
}
}
}
static void bm_remove_tagged_verts_loose(BMesh *bm, const char hflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, hflag) && (v->e == nullptr)) {
BM_vert_kill(bm, v);
}
}
}
void BM_mesh_delete_hflag_tagged(BMesh *bm, const char hflag, const char htype)
{
if (htype & BM_FACE) {
bm_remove_tagged_faces(bm, hflag);
}
if (htype & BM_EDGE) {
bm_remove_tagged_edges(bm, hflag);
}
if (htype & BM_VERT) {
bm_remove_tagged_verts(bm, hflag);
}
}
void BM_mesh_delete_hflag_context(BMesh *bm, const char hflag, const int type)
{
BMIter eiter;
BMIter fiter;
switch (type) {
case DEL_VERTS: {
bm_remove_tagged_verts(bm, hflag);
break;
}
case DEL_EDGES: {
/* flush down to vert */
BMEdge *e;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, hflag)) {
BM_elem_flag_enable(e->v1, hflag);
BM_elem_flag_enable(e->v2, hflag);
}
}
bm_remove_tagged_edges(bm, hflag);
bm_remove_tagged_verts_loose(bm, hflag);
break;
}
case DEL_EDGESFACES: {
bm_remove_tagged_edges(bm, hflag);
break;
}
case DEL_ONLYFACES: {
bm_remove_tagged_faces(bm, hflag);
break;
}
case DEL_ONLYTAGGED: {
BM_mesh_delete_hflag_tagged(bm, hflag, BM_ALL_NOLOOP);
break;
}
case DEL_FACES: {
/* go through and mark all edges and all verts of all faces for delete */
BMFace *f;
BMEdge *e;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, hflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BM_elem_flag_enable(l_iter->v, hflag);
BM_elem_flag_enable(l_iter->e, hflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* now go through and mark all remaining faces all edges for keeping */
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, hflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BM_elem_flag_disable(l_iter->v, hflag);
BM_elem_flag_disable(l_iter->e, hflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* also mark all the vertices of remaining edges for keeping */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(e, hflag)) {
BM_elem_flag_disable(e->v1, hflag);
BM_elem_flag_disable(e->v2, hflag);
}
}
/* now delete marked face */
bm_remove_tagged_faces(bm, hflag);
/* delete marked edge */
bm_remove_tagged_edges(bm, hflag);
/* remove loose vertices */
bm_remove_tagged_verts(bm, hflag);
break;
}
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "BLI_function_ref.hh"
namespace blender {
void BMO_mesh_delete_oflag_tagged(BMesh *bm, short oflag, char htype);
void BM_mesh_delete_hflag_tagged(BMesh *bm, char hflag, char htype);
/**
* \param oflag: Geometry tagged with this operator flag is deleted.
* This flag applies to different types in some contexts, not just the type being removed.
*
* \param prepare_fn: Optional callback that runs before deleting geometry,
* use this to execute any logic that needs to ensure references to deleted geometry
* aren't held by the caller.
*/
void BMO_mesh_delete_oflag_context(BMesh *bm,
short oflag,
int type,
FunctionRef<void()> prepare_fn);
/**
* \param hflag: Geometry tagged with this operator flag is deleted.
* This flag applies to different types in some contexts, not just the type being removed.
*/
void BM_mesh_delete_hflag_context(BMesh *bm, char hflag, int type);
} // namespace blender

View File

@@ -0,0 +1,811 @@
/* SPDX-FileCopyrightText: 2013 by Campbell Barton. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Generic utility functions for getting edge loops from a mesh.
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_mempool.h"
#include "BLI_set.hh"
#include "BLI_stack.h"
#include "BLI_utildefines_iter.h"
#include "bmesh.hh"
#include "bmesh_edgeloop.hh" /* own include */
namespace blender {
struct BMEdgeLoopStore {
BMEdgeLoopStore *next, *prev;
ListBaseT<LinkData> verts;
int flag;
int len;
/* Optional values to calculate. */
float co[3], no[3];
};
#define BM_EDGELOOP_IS_CLOSED (1 << 0)
/* Use a small value since we need normals even for very small loops. */
#define EDGELOOP_EPS 1e-10f
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_find & Utility Functions. */
static int bm_vert_other_tag(BMVert *v, BMVert *v_prev, BMEdge **r_e)
{
BMIter iter;
BMEdge *e, *e_next = nullptr;
uint count = 0;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMVert *v_other = BM_edge_other_vert(e, v);
if (v_other != v_prev) {
e_next = e;
count++;
}
}
}
*r_e = e_next;
return count;
}
/**
* \return success
*/
static bool bm_loop_build(BMEdgeLoopStore *el_store, BMVert *v_prev, BMVert *v, int dir)
{
void (*add_fn)(ListBase *, void *) = dir == 1 ? BLI_addhead : BLI_addtail;
BMEdge *e_next;
BMVert *v_next;
BMVert *v_first = v;
BLI_assert(abs(dir) == 1);
if (!BM_elem_flag_test(v, BM_ELEM_INTERNAL_TAG)) {
return true;
}
while (v) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
int count;
node->data = v;
add_fn(&el_store->verts, node);
el_store->len++;
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
count = bm_vert_other_tag(v, v_prev, &e_next);
if (count == 1) {
v_next = BM_edge_other_vert(e_next, v);
BM_elem_flag_disable(e_next, BM_ELEM_INTERNAL_TAG);
if (UNLIKELY(v_next == v_first)) {
el_store->flag |= BM_EDGELOOP_IS_CLOSED;
v_next = nullptr;
}
}
else if (count == 0) {
/* pass */
v_next = nullptr;
}
else {
v_next = nullptr;
return false;
}
v_prev = v;
v = v_next;
}
return true;
}
int BM_mesh_edgeloops_find(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data)
{
BMIter iter;
BMEdge *e;
BMVert *v;
int count = 0;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
}
/* first flush edges to tags, and tag verts */
BLI_Stack *edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BLI_assert(!BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG));
if (test_fn(e, user_data)) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
BLI_stack_push(edge_stack, static_cast<void *>(&e));
}
else {
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
}
}
const uint edges_len = BLI_stack_count(edge_stack);
BMEdge **edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BLI_stack_pop_n_reverse(edge_stack, edges, BLI_stack_count(edge_stack));
BLI_stack_free(edge_stack);
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
/* add both directions */
if (bm_loop_build(el_store, e->v1, e->v2, 1) && bm_loop_build(el_store, e->v2, e->v1, -1) &&
el_store->len > 1)
{
BLI_addtail(r_eloops, el_store);
count++;
}
else {
BM_edgeloop_free(el_store);
}
}
}
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_INTERNAL_TAG);
}
MEM_delete(edges);
return count;
}
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_find_path & Util Functions. */
/**
* Find s single, open edge loop - given 2 vertices.
* Add to
*/
struct VertStep {
VertStep *next, *prev;
BMVert *v;
};
static void vs_add(
BLI_mempool *vs_pool, ListBaseT<VertStep> *lb, BMVert *v, BMEdge *e_prev, const int iter_tot)
{
VertStep *vs_new = static_cast<VertStep *>(BLI_mempool_alloc(vs_pool));
vs_new->v = v;
BM_elem_index_set(v, iter_tot); /* set_dirty */
/* This edge stores a direct path back to the original vertex so we can
* backtrack without having to store an array of previous verts. */
/* WARNING: Setting the edge is not common practice but currently harmless, take care. */
BLI_assert(BM_vert_in_edge(e_prev, v));
v->e = e_prev;
BLI_addtail(lb, vs_new);
}
static bool bm_loop_path_build_step(BLI_mempool *vs_pool,
ListBaseT<VertStep> *lb,
const int dir,
BMVert *v_match[2])
{
ListBaseT<VertStep> lb_tmp = {nullptr, nullptr};
VertStep *vs, *vs_next;
BLI_assert(abs(dir) == 1);
for (vs = static_cast<VertStep *>(lb->first); vs; vs = vs_next) {
BMIter iter;
BMEdge *e;
/* these values will be the same every iteration */
const int vs_iter_tot = BM_elem_index_get(vs->v);
const int vs_iter_next = vs_iter_tot + dir;
vs_next = vs->next;
BM_ITER_ELEM (e, &iter, vs->v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMVert *v_next = BM_edge_other_vert(e, vs->v);
const int v_next_index = BM_elem_index_get(v_next);
/* not essential to clear flag but prevents more checking next time round */
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
if (v_next_index == 0) {
vs_add(vs_pool, &lb_tmp, v_next, e, vs_iter_next);
}
else if ((dir < 0) == (v_next_index < 0)) {
/* on the same side - do nothing */
}
else {
/* we have met out match! (vertices from different sides meet) */
if (dir == 1) {
v_match[0] = vs->v;
v_match[1] = v_next;
}
else {
v_match[0] = v_next;
v_match[1] = vs->v;
}
/* normally we would manage memory of remaining items in (lb, lb_tmp),
* but search is done, vs_pool will get destroyed immediately */
return true;
}
}
}
BLI_mempool_free(vs_pool, vs);
}
/* Commented because used in a loop, and this flag has already been set. */
// bm->elem_index_dirty |= BM_VERT;
/* `lb` is now full of freed items, overwrite. */
*lb = lb_tmp;
return (lb->is_empty() == false);
}
bool BM_mesh_edgeloops_find_path(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data,
BMVert *v_src,
BMVert *v_dst)
{
BMIter iter;
BMEdge *e;
bool found = false;
BLI_assert(v_src != v_dst);
{
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_index_set(v, 0);
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
}
}
bm->elem_index_dirty |= BM_VERT;
/* first flush edges to tags, and tag verts */
int edges_len;
BMEdge **edges;
if (test_fn) {
BLI_Stack *edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (test_fn(e, user_data)) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
BLI_stack_push(edge_stack, static_cast<void *>(&e));
}
else {
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
}
}
edges_len = BLI_stack_count(edge_stack);
edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BLI_stack_pop_n_reverse(edge_stack, edges, BLI_stack_count(edge_stack));
BLI_stack_free(edge_stack);
}
else {
int i = 0;
edges_len = bm->totedge;
edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
edges[i] = e;
}
}
/* prime the lists and begin search */
{
BMVert *v_match[2] = {nullptr, nullptr};
ListBaseT<VertStep> lb_src = {nullptr, nullptr};
ListBaseT<VertStep> lb_dst = {nullptr, nullptr};
BLI_mempool *vs_pool = BLI_mempool_create(sizeof(VertStep), 0, 512, BLI_MEMPOOL_NOP);
/* edge args are dummy */
vs_add(vs_pool, &lb_src, v_src, v_src->e, 1);
vs_add(vs_pool, &lb_dst, v_dst, v_dst->e, -1);
bm->elem_index_dirty |= BM_VERT;
do {
if ((bm_loop_path_build_step(vs_pool, &lb_src, 1, v_match) == false) || v_match[0]) {
break;
}
if ((bm_loop_path_build_step(vs_pool, &lb_dst, -1, v_match) == false) || v_match[0]) {
break;
}
} while (true);
BLI_mempool_destroy(vs_pool);
if (v_match[0]) {
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
BMVert *v;
/* build loop from edge pointers */
v = v_match[0];
while (true) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v;
BLI_addhead(&el_store->verts, node);
el_store->len++;
if (v == v_src) {
break;
}
v = BM_edge_other_vert(v->e, v);
}
v = v_match[1];
while (true) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v;
BLI_addtail(&el_store->verts, node);
el_store->len++;
if (v == v_dst) {
break;
}
v = BM_edge_other_vert(v->e, v);
}
BLI_addtail(r_eloops, el_store);
found = true;
}
}
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_INTERNAL_TAG);
}
MEM_delete(edges);
return found;
}
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_xxx utility function */
void BM_mesh_edgeloops_free(ListBaseT<BMEdgeLoopStore> *eloops)
{
while (BMEdgeLoopStore *el_store = static_cast<BMEdgeLoopStore *>(BLI_pophead(eloops))) {
BM_edgeloop_free(el_store);
}
}
void BM_mesh_edgeloops_calc_center(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops)
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_center(bm, &el_store);
}
}
void BM_mesh_edgeloops_calc_normal(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops)
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_normal(bm, &el_store);
}
}
void BM_mesh_edgeloops_calc_normal_aligned(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *eloops,
const float no_align[3])
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_normal_aligned(bm, &el_store, no_align);
}
}
void BM_mesh_edgeloops_calc_order(BMesh * /*bm*/,
ListBaseT<BMEdgeLoopStore> *eloops,
const bool use_normals)
{
ListBaseT<BMEdgeLoopStore> eloops_ordered = {nullptr};
float cent[3];
int tot = 0;
zero_v3(cent);
/* assumes we calculated centers already */
for (BMEdgeLoopStore &el_store : *eloops) {
if (!is_finite_v3(el_store.co)) [[unlikely]] {
continue;
}
add_v3_v3(cent, el_store.co);
tot += 1;
}
if (tot > 0) {
mul_v3_fl(cent, 1.0f / float(tot));
if (!is_finite_v3(cent)) {
zero_v3(cent);
}
}
/* Find the furthest out loop. */
{
BMEdgeLoopStore *el_store_best = nullptr;
float len_best_sq = -1.0f;
for (BMEdgeLoopStore &el_store : *eloops) {
const float len_sq = len_squared_v3v3(cent, el_store.co);
/* Null check to account for non-finite distances. */
if ((len_sq > len_best_sq) || (el_store_best == nullptr)) {
len_best_sq = len_sq;
el_store_best = &el_store;
}
}
BLI_remlink(eloops, el_store_best);
BLI_addtail(&eloops_ordered, el_store_best);
}
/* not so efficient re-ordering */
while (eloops->first) {
BMEdgeLoopStore *el_store_best = nullptr;
const float *co = (static_cast<BMEdgeLoopStore *>(eloops_ordered.last))->co;
const float *no = (static_cast<BMEdgeLoopStore *>(eloops_ordered.last))->no;
float len_best_sq = FLT_MAX;
if (use_normals) {
BLI_ASSERT_UNIT_V3(no);
}
for (BMEdgeLoopStore &el_store : *eloops) {
float len_sq;
if (use_normals) {
/* Scale the length by how close the loops are to pointing at each other. */
float dir[3];
sub_v3_v3v3(dir, co, el_store.co);
len_sq = normalize_v3(dir);
len_sq = len_sq *
((1.0f - fabsf(dot_v3v3(dir, no))) + (1.0f - fabsf(dot_v3v3(dir, el_store.no))));
}
else {
len_sq = len_squared_v3v3(co, el_store.co);
}
/* Null check to account for non-finite distances. */
if ((len_sq < len_best_sq) || (el_store_best == nullptr)) {
len_best_sq = len_sq;
el_store_best = &el_store;
}
}
BLI_remlink(eloops, el_store_best);
BLI_addtail(&eloops_ordered, el_store_best);
}
*eloops = eloops_ordered;
}
/* -------------------------------------------------------------------- */
/* BM_edgeloop_*** functions */
BMEdgeLoopStore *BM_edgeloop_copy(BMEdgeLoopStore *el_store)
{
BMEdgeLoopStore *el_store_copy = MEM_new_uninitialized<BMEdgeLoopStore>(__func__);
*el_store_copy = *el_store;
BLI_duplicatelist(&el_store_copy->verts, &el_store->verts);
return el_store_copy;
}
BMEdgeLoopStore *BM_edgeloop_from_verts(BMVert **v_arr, const int v_arr_tot, bool is_closed)
{
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
int i;
for (i = 0; i < v_arr_tot; i++) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v_arr[i];
BLI_addtail(&el_store->verts, node);
}
el_store->len = v_arr_tot;
if (is_closed) {
el_store->flag |= BM_EDGELOOP_IS_CLOSED;
}
return el_store;
}
void BM_edgeloop_free(BMEdgeLoopStore *el_store)
{
el_store->verts.free_no_destruct();
MEM_delete(el_store);
}
bool BM_edgeloop_is_closed(BMEdgeLoopStore *el_store)
{
return (el_store->flag & BM_EDGELOOP_IS_CLOSED) != 0;
}
ListBaseT<LinkData> *BM_edgeloop_verts_get(BMEdgeLoopStore *el_store)
{
return &el_store->verts;
}
int BM_edgeloop_length_get(BMEdgeLoopStore *el_store)
{
return el_store->len;
}
const float *BM_edgeloop_normal_get(BMEdgeLoopStore *el_store)
{
return el_store->no;
}
const float *BM_edgeloop_center_get(BMEdgeLoopStore *el_store)
{
return el_store->co;
}
#define NODE_AS_V(n) ((BMVert *)((LinkData *)n)->data)
#define NODE_AS_CO(n) ((BMVert *)((LinkData *)n)->data)->co
void BM_edgeloop_edges_get(BMEdgeLoopStore *el_store, BMEdge **e_arr)
{
LinkData *node;
int i = 0;
for (node = static_cast<LinkData *>(el_store->verts.first); node && node->next;
node = node->next)
{
e_arr[i++] = BM_edge_exists(NODE_AS_V(node), NODE_AS_V(node->next));
BLI_assert(e_arr[i - 1] != nullptr);
}
if (el_store->flag & BM_EDGELOOP_IS_CLOSED) {
e_arr[i] = BM_edge_exists(NODE_AS_V(el_store->verts.first), NODE_AS_V(el_store->verts.last));
BLI_assert(e_arr[i] != nullptr);
}
BLI_assert(el_store->len == i + 1);
}
void BM_edgeloop_calc_center(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.last);
LinkData *node_prev = (static_cast<LinkData *>(el_store->verts.last))->prev;
LinkData *node_first = static_cast<LinkData *>(el_store->verts.first);
LinkData *node_next = node_first;
const float *v_prev = NODE_AS_CO(node_prev);
const float *v_curr = NODE_AS_CO(node_curr);
const float *v_next = NODE_AS_CO(node_next);
float totw = 0.0f;
float w_prev;
zero_v3(el_store->co);
w_prev = len_v3v3(v_prev, v_curr);
do {
const float w_curr = len_v3v3(v_curr, v_next);
const float w = (w_curr + w_prev);
madd_v3_v3fl(el_store->co, v_curr, w);
totw += w;
w_prev = w_curr;
node_prev = node_curr;
node_curr = node_next;
node_next = node_next->next;
if (node_next == nullptr) {
break;
}
v_prev = v_curr;
v_curr = v_next;
v_next = NODE_AS_CO(node_next);
} while (true);
if (totw != 0.0f) {
mul_v3_fl(el_store->co, 1.0f / totw);
}
}
bool BM_edgeloop_calc_normal(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
const float *v_prev = NODE_AS_CO(el_store->verts.last);
const float *v_curr = NODE_AS_CO(node_curr);
zero_v3(el_store->no);
/* Newell's Method */
do {
add_newell_cross_v3_v3v3(el_store->no, v_prev, v_curr);
if ((node_curr = node_curr->next)) {
v_prev = v_curr;
v_curr = NODE_AS_CO(node_curr);
}
else {
break;
}
} while (true);
if (UNLIKELY(normalize_v3(el_store->no) < EDGELOOP_EPS)) {
el_store->no[2] = 1.0f; /* other axis set to 0.0 */
return false;
}
return true;
}
bool BM_edgeloop_calc_normal_aligned(BMesh * /*bm*/,
BMEdgeLoopStore *el_store,
const float no_align[3])
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
const float *v_prev = NODE_AS_CO(el_store->verts.last);
const float *v_curr = NODE_AS_CO(node_curr);
zero_v3(el_store->no);
/* Own Method */
do {
float cross[3], no[3], dir[3];
sub_v3_v3v3(dir, v_curr, v_prev);
cross_v3_v3v3(cross, no_align, dir);
cross_v3_v3v3(no, dir, cross);
add_v3_v3(el_store->no, no);
if ((node_curr = node_curr->next)) {
v_prev = v_curr;
v_curr = NODE_AS_CO(node_curr);
}
else {
break;
}
} while (true);
if (UNLIKELY(normalize_v3(el_store->no) < EDGELOOP_EPS)) {
el_store->no[2] = 1.0f; /* other axis set to 0.0 */
return false;
}
return true;
}
void BM_edgeloop_flip(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
negate_v3(el_store->no);
BLI_listbase_reverse(&el_store->verts);
}
void BM_edgeloop_expand(
BMesh *bm, BMEdgeLoopStore *el_store, int el_store_len, bool split, Set<BMEdge *> *split_edges)
{
bool split_swap = true;
#define EDGE_SPLIT(node_copy, node_other) \
{ \
BMVert *v_split, *v_other = static_cast<BMVert *>((node_other)->data); \
BMEdge *e_split, \
*e_other = BM_edge_exists(static_cast<BMVert *>((node_copy)->data), v_other); \
v_split = BM_edge_split(bm, \
e_other, \
static_cast<BMVert *>(split_swap ? (node_copy)->data : v_other), \
&e_split, \
0.0f); \
v_split->e = e_split; \
BLI_assert(v_split == e_split->v2); \
split_edges->add(e_split); \
(node_copy)->data = v_split; \
} \
((void)0)
/* first double until we are more than half as big */
while ((el_store->len * 2) < el_store_len) {
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
while (node_curr) {
LinkData *node_curr_copy = MEM_dupalloc(node_curr);
if (split == false) {
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
if (node_curr->next || (el_store->flag & BM_EDGELOOP_IS_CLOSED)) {
EDGE_SPLIT(node_curr_copy,
node_curr->next ? node_curr->next : (LinkData *)el_store->verts.first);
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
EDGE_SPLIT(node_curr_copy, node_curr->prev);
BLI_insertlinkbefore(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr->next;
}
split_swap = !split_swap;
}
el_store->len++;
}
split_swap = !split_swap;
}
if (el_store->len < el_store_len) {
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
int iter_prev = 0;
BLI_FOREACH_SPARSE_RANGE (el_store->len, (el_store_len - el_store->len), iter) {
while (iter_prev < iter) {
node_curr = node_curr->next;
iter_prev += 1;
}
LinkData *node_curr_copy;
node_curr_copy = MEM_dupalloc(node_curr);
if (split == false) {
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
if (node_curr->next || (el_store->flag & BM_EDGELOOP_IS_CLOSED)) {
EDGE_SPLIT(node_curr_copy,
node_curr->next ? node_curr->next : (LinkData *)el_store->verts.first);
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
EDGE_SPLIT(node_curr_copy, node_curr->prev);
BLI_insertlinkbefore(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr->next;
}
split_swap = !split_swap;
}
el_store->len++;
iter_prev += 1;
}
}
#undef BKE_FOREACH_SUBSET_OF_RANGE
#undef EDGE_SPLIT
BLI_assert(el_store->len == el_store_len);
}
bool BM_edgeloop_overlap_check(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b)
{
/* A little more efficient if 'a' as smaller. */
if (el_store_a->len > el_store_b->len) {
std::swap(el_store_a, el_store_b);
}
/* init */
for (LinkData &node : el_store_a->verts) {
BM_elem_flag_enable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
for (LinkData &node : el_store_b->verts) {
BM_elem_flag_disable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
/* Check 'a' (clear as we go). */
for (LinkData &node : el_store_a->verts) {
if (!BM_elem_flag_test((BMVert *)node.data, BM_ELEM_INTERNAL_TAG)) {
/* Finish clearing 'a', leave tag clean. */
LinkData *remaining_node = &node;
while ((remaining_node = remaining_node->next)) {
BM_elem_flag_disable((BMVert *)remaining_node->data, BM_ELEM_INTERNAL_TAG);
}
return true;
}
BM_elem_flag_disable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,91 @@
/* SPDX-FileCopyrightText: 2013 by Campbell Barton. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "DNA_listBase.h"
#include "BLI_set.hh"
namespace blender {
struct BMEdgeLoopStore;
struct LinkData;
/* multiple edgeloops (ListBase) */
/**
* \return listbase of listbases, each linking to a vertex.
*/
int BM_mesh_edgeloops_find(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data);
bool BM_mesh_edgeloops_find_path(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data,
BMVert *v_src,
BMVert *v_dst);
void BM_mesh_edgeloops_free(ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_center(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_normal(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_normal_aligned(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *eloops,
const float no_align[3]);
void BM_mesh_edgeloops_calc_order(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops, bool use_normals);
/**
* Copy a single edge-loop.
* \return new edge-loops.
*/
BMEdgeLoopStore *BM_edgeloop_copy(BMEdgeLoopStore *el_store);
BMEdgeLoopStore *BM_edgeloop_from_verts(BMVert **v_arr, int v_arr_tot, bool is_closed);
void BM_edgeloop_free(BMEdgeLoopStore *el_store);
bool BM_edgeloop_is_closed(BMEdgeLoopStore *el_store);
int BM_edgeloop_length_get(BMEdgeLoopStore *el_store);
ListBaseT<LinkData> *BM_edgeloop_verts_get(BMEdgeLoopStore *el_store);
const float *BM_edgeloop_normal_get(BMEdgeLoopStore *el_store);
const float *BM_edgeloop_center_get(BMEdgeLoopStore *el_store);
/**
* Edges are assigned to one vert -> the next.
*/
void BM_edgeloop_edges_get(BMEdgeLoopStore *el_store, BMEdge **e_arr);
void BM_edgeloop_calc_center(BMesh *bm, BMEdgeLoopStore *el_store);
bool BM_edgeloop_calc_normal(BMesh *bm, BMEdgeLoopStore *el_store);
/**
* For open loops that are straight lines,
* calculating the normal as if it were a polygon is meaningless.
*
* Instead use an alignment vector and calculate the normal based on that.
*/
bool BM_edgeloop_calc_normal_aligned(BMesh *bm,
BMEdgeLoopStore *el_store,
const float no_align[3]);
void BM_edgeloop_flip(BMesh *bm, BMEdgeLoopStore *el_store);
void BM_edgeloop_expand(BMesh *bm,
BMEdgeLoopStore *el_store,
int el_store_len,
bool split,
Set<BMEdge *> *split_edges);
bool BM_edgeloop_overlap_check(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b);
#define BM_EDGELINK_NEXT(el_store, elink) \
(elink)->next ? \
(elink)->next : \
(BM_edgeloop_is_closed(el_store) ? (LinkData *)BM_edgeloop_verts_get(el_store)->first : \
NULL)
#define BM_EDGELOOP_NEXT(el_store) \
(CHECK_TYPE_INLINE(el_store, BMEdgeLoopStore *), (BMEdgeLoopStore *)((LinkData *)el_store)->next)
} // namespace blender

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_operator_api.hh"
#include "bmesh_class.hh"
namespace blender {
/*----------- BMOP error system ----------*/
/**
* \note More can be added as needed.
*/
enum eBMOpErrorLevel {
/**
* Use when the operation could not succeed,
* typically from input that isn't sufficient for completing the operation.
*/
BMO_ERROR_CANCEL = 0,
/**
* Use this when one or more operations could not succeed,
* when the resulting mesh can be used (since some operations succeeded or no change was made).
* This is used by default.
*/
BMO_ERROR_WARN = 1,
/**
* The mesh resulting from this operation should not be used (where possible).
* It should not be left in a corrupt state either.
*
* See #BMBackup type & function calls.
*/
BMO_ERROR_FATAL = 2,
};
/**
* Pushes an error onto the bmesh error stack.
* if msg is null, then the default message for the `errcode` is used.
*/
void BMO_error_raise(BMesh *bm, BMOperator *owner, eBMOpErrorLevel level, const char *msg)
ATTR_NONNULL(1, 2, 4);
/**
* Gets the topmost error from the stack.
* returns error code or 0 if no error.
*/
bool BMO_error_get(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level);
bool BMO_error_get_at_level(BMesh *bm,
eBMOpErrorLevel level,
const char **r_msg,
BMOperator **r_op);
bool BMO_error_occurred_at_level(BMesh *bm, eBMOpErrorLevel level);
/* Same as #BMO_error_get, only pops the error off the stack as well. */
bool BMO_error_pop(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level);
void BMO_error_clear(BMesh *bm);
/* This is meant for handling errors, like self-intersection test failures.
* it's dangerous to handle errors in general though, so disabled for now. */
/* Catches an error raised by the op pointed to by catchop. */
/* Not yet implemented. */
// int BMO_error_catch_op(BMesh *bm, BMOperator *catchop, char **r_msg);
#define BM_ELEM_INDEX_VALIDATE(_bm, _msg_a, _msg_b) \
BM_mesh_elem_index_validate(_bm, __FILE__ ":" STRINGIFY(__LINE__), __func__, _msg_a, _msg_b)
/* BMESH_ASSERT */
#ifdef WITH_ASSERT_ABORT
# define _BMESH_DUMMY_ABORT abort
#else
# define _BMESH_DUMMY_ABORT() (void)0
#endif
/**
* This is meant to be higher level than BLI_assert(),
* its enabled even when in Release mode.
*/
#define BMESH_ASSERT(a) \
(void)((!(a)) ? ((fprintf(stderr, \
"BMESH_ASSERT failed: %s, %s(), %d at \'%s\'\n", \
__FILE__, \
__func__, \
__LINE__, \
STRINGIFY(a)), \
_BMESH_DUMMY_ABORT(), \
NULL)) : \
NULL)
} // namespace blender

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM Inline functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
namespace blender {
/* stuff for dealing with header flags */
#define BM_elem_flag_test(ele, hflag) _bm_elem_flag_test(&(ele)->head, hflag)
#define BM_elem_flag_test_bool(ele, hflag) _bm_elem_flag_test_bool(&(ele)->head, hflag)
#define BM_elem_flag_enable(ele, hflag) _bm_elem_flag_enable(&(ele)->head, hflag)
#define BM_elem_flag_disable(ele, hflag) _bm_elem_flag_disable(&(ele)->head, hflag)
#define BM_elem_flag_set(ele, hflag, val) _bm_elem_flag_set(&(ele)->head, hflag, val)
#define BM_elem_flag_toggle(ele, hflag) _bm_elem_flag_toggle(&(ele)->head, hflag)
#define BM_elem_flag_merge(ele_a, ele_b) _bm_elem_flag_merge(&(ele_a)->head, &(ele_b)->head)
#define BM_elem_flag_merge_ex(ele_a, ele_b, hflag_and) \
_bm_elem_flag_merge_ex(&(ele_a)->head, &(ele_b)->head, hflag_and)
#define BM_elem_flag_merge_into(ele, ele_a, ele_b) \
_bm_elem_flag_merge_into(&(ele)->head, &(ele_a)->head, &(ele_b)->head)
ATTR_WARN_UNUSED_RESULT
BLI_INLINE char _bm_elem_flag_test(const BMHeader *head, const char hflag)
{
return head->hflag & hflag;
}
ATTR_WARN_UNUSED_RESULT
BLI_INLINE bool _bm_elem_flag_test_bool(const BMHeader *head, const char hflag)
{
return (head->hflag & hflag) != 0;
}
BLI_INLINE void _bm_elem_flag_enable(BMHeader *head, const char hflag)
{
head->hflag |= hflag;
}
BLI_INLINE void _bm_elem_flag_disable(BMHeader *head, const char hflag)
{
head->hflag &= char(~hflag);
}
BLI_INLINE void _bm_elem_flag_set(BMHeader *head, const char hflag, const int val)
{
if (val) {
_bm_elem_flag_enable(head, hflag);
}
else {
_bm_elem_flag_disable(head, hflag);
}
}
BLI_INLINE void _bm_elem_flag_toggle(BMHeader *head, const char hflag)
{
head->hflag ^= hflag;
}
BLI_INLINE void _bm_elem_flag_merge(BMHeader *head_a, BMHeader *head_b)
{
head_a->hflag = head_b->hflag = head_a->hflag | head_b->hflag;
}
BLI_INLINE void _bm_elem_flag_merge_ex(BMHeader *head_a, BMHeader *head_b, const char hflag_and)
{
if (((head_a->hflag & head_b->hflag) & hflag_and) == 0) {
head_a->hflag &= ~hflag_and;
head_b->hflag &= ~hflag_and;
}
_bm_elem_flag_merge(head_a, head_b);
}
BLI_INLINE void _bm_elem_flag_merge_into(BMHeader *head,
const BMHeader *head_a,
const BMHeader *head_b)
{
head->hflag = head_a->hflag | head_b->hflag;
}
/**
* notes on #BM_elem_index_set(...) usage,
* Set index is sometimes abused as temp storage, other times we can't be
* sure if the index values are valid because certain operations have modified
* the mesh structure.
*
* To set the elements to valid indices 'BM_mesh_elem_index_ensure' should be used
* rather than adding inline loops, however there are cases where we still
* set the index directly
*
* In an attempt to manage this,
* here are 5 tags I'm adding to uses of #BM_elem_index_set
*
* - `set_inline` -- since the data is already being looped over set to a
* valid value inline.
*
* - `set_dirty!` -- intentionally sets the index to an invalid value,
* flagging `bm->elem_index_dirty` so we don't use it.
*
* - `set_ok` -- this is valid use since the part of the code is low level.
*
* - `set_ok_invalid` -- set to -1 on purpose since this should not be
* used without a full array re-index, do this on
* adding new vert/edge/faces since they may be added at
* the end of the array.
*
* - campbell */
#define BM_elem_index_get(ele) _bm_elem_index_get(&(ele)->head)
#define BM_elem_index_set(ele, index) _bm_elem_index_set(&(ele)->head, index)
BLI_INLINE void _bm_elem_index_set(BMHeader *head, const int index)
{
head->index = index;
}
ATTR_WARN_UNUSED_RESULT
BLI_INLINE int _bm_elem_index_get(const BMHeader *head)
{
return head->index;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_string_ref.hh"
#include "bmesh_class.hh"
namespace blender {
struct LinkNode;
struct MemArena;
namespace bke {
enum class AttrDomain : int8_t;
enum class AttrType : int16_t;
} // namespace bke
void BM_loop_interp_multires_ex(BMesh *bm,
BMLoop *l_dst,
const BMFace *f_src,
const float f_dst_center[3],
const float f_src_center[3],
int cd_loop_mdisp_offset);
/**
* Project the multi-resolution grid in target onto f_src's set of multi-resolution grids.
*/
void BM_loop_interp_multires(BMesh *bm, BMLoop *l_dst, const BMFace *f_src);
void BM_face_interp_multires_ex(BMesh *bm,
BMFace *f_dst,
const BMFace *f_src,
const float f_dst_center[3],
const float f_src_center[3],
int cd_loop_mdisp_offset);
void BM_face_interp_multires(BMesh *bm, BMFace *f_dst, const BMFace *f_src);
void BM_vert_interp_from_face(BMesh *bm, BMVert *v_dst, const BMFace *f_src);
/**
* \brief Data, Interpolate From Verts
*
* Interpolates per-vertex data from two sources to \a v_dst
*
* \note This is an exact match to #BM_data_interp_from_edges.
*/
void BM_data_interp_from_verts(
BMesh *bm, const BMVert *v_src_1, const BMVert *v_src_2, BMVert *v_dst, float fac);
/**
* \brief Data, Interpolate From Edges
*
* Interpolates per-edge data from two sources to \a e_dst.
*
* \note This is an exact match to #BM_data_interp_from_verts.
*/
void BM_data_interp_from_edges(
BMesh *bm, const BMEdge *e_src_1, const BMEdge *e_src_2, BMEdge *e_dst, float fac);
/**
* \brief Data Face-Vert Edge Interpolate
*
* Walks around the faces of \a e and interpolates
* the loop data between two sources.
*/
void BM_data_interp_face_vert_edge(
BMesh *bm, const BMVert *v_src_1, const BMVert *v_src_2, BMVert *v, BMEdge *e, float fac);
void BM_data_layer_add(BMesh *bm, CustomData *data, int type);
void BM_data_layer_add_named(BMesh *bm, CustomData *data, int type, StringRef name);
void BM_data_layer_ensure_named(BMesh *bm, CustomData *data, int type, StringRef name);
bool BM_data_layer_has_named(const BMesh *bm, const CustomData *data, int type, StringRef name);
void BM_data_layer_free(BMesh *bm, CustomData *data, int type);
/** Ensure the dependent boolean layers exist for all face corner #CD_PROP_FLOAT2 layers. */
void BM_uv_map_attr_pin_ensure_for_all_layers(BMesh *bm);
void BM_uv_map_attr_pin_ensure_named(BMesh *bm, StringRef uv_map_name);
bool BM_uv_map_attr_pin_exists(const BMesh *bm, StringRef uv_map_name);
/**
* Remove a named custom data layer, if it existed. Return true if the layer was removed.
*/
bool BM_data_layer_free_named(BMesh *bm, CustomData *data, StringRef name);
void BM_data_layer_free_n(BMesh *bm, CustomData *data, int type, int n);
void BM_data_layer_copy(BMesh *bm, CustomData *data, int type, int src_n, int dst_n);
/* See #BM_data_layer_lookup. */
struct BMDataLayerLookup {
const int offset = -1;
bke::AttrDomain domain;
bke::AttrType type;
const CustomDataLayer *layer = nullptr;
operator bool() const
{
return offset != -1;
}
};
/**
* Search for a named custom data layer on all attribute domains and return the domain and type.
* This is roughly analogous to #Mesh::attributes().lookup(...), but keep in mind that certain
* attributes stored on #Mesh are not stored as attributes on #BMesh.
*/
BMDataLayerLookup BM_data_layer_lookup(const BMesh &bm, const StringRef name);
float BM_elem_float_data_get(CustomData *cd, void *element, int type);
void BM_elem_float_data_set(CustomData *cd, void *element, int type, float val);
/**
* \brief Data Interpolate From Face
*
* Projects target onto source, and pulls interpolated custom-data from source.
*
* \note Only handles loop custom-data. multi-res is handled.
* \note Attributes such as selection, material & normals
* must be handled with a separate call to #BM_elem_attrs_copy.
*/
void BM_face_interp_from_face_ex(BMesh *bm,
BMFace *f_dst,
const BMFace *f_src,
bool do_vertex,
const void **blocks,
const void **blocks_v,
float (*cos_2d)[2],
float axis_mat[3][3]);
void BM_face_interp_from_face(BMesh *bm, BMFace *f_dst, const BMFace *f_src, bool do_vertex);
/**
* Projects a single loop, target, onto f_src for custom-data interpolation.
* multi-resolution is handled.
* \param do_vertex: When true the target's vert data will also get interpolated.
*/
void BM_loop_interp_from_face(
BMesh *bm, BMLoop *l_dst, const BMFace *f_src, bool do_vertex, bool do_multires);
/**
* Smooths boundaries between multi-res grids,
* including some borders in adjacent faces.
*/
void BM_face_multires_bounds_smooth(BMesh *bm, BMFace *f);
LinkNode *BM_vert_loop_groups_data_layer_create(
BMesh *bm, BMVert *v, int layer_n, const float *loop_weights, MemArena *arena);
/**
* Take existing custom data and merge each fan's data.
*/
void BM_vert_loop_groups_data_layer_merge(BMesh *bm, LinkNode *groups, int layer_n);
/**
* A version of #BM_vert_loop_groups_data_layer_merge
* that takes an array of loop-weights (aligned with #BM_LOOPS_OF_VERT iterator).
*/
void BM_vert_loop_groups_data_layer_merge_weights(BMesh *bm,
LinkNode *groups,
int layer_n,
const float *loop_weights);
} // namespace blender

View File

@@ -0,0 +1,661 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Functions to abstract looping over bmesh data structures.
*
* See: bmesh_iterators_inlin.c too, some functions are here for speed reasons.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "intern/bmesh_structure.hh"
namespace blender {
const char bm_iter_itype_htype_map[BM_ITYPE_MAX] = {
'\0',
BM_VERT, /* BM_VERTS_OF_MESH */
BM_EDGE, /* BM_EDGES_OF_MESH */
BM_FACE, /* BM_FACES_OF_MESH */
BM_EDGE, /* BM_EDGES_OF_VERT */
BM_FACE, /* BM_FACES_OF_VERT */
BM_LOOP, /* BM_LOOPS_OF_VERT */
BM_VERT, /* BM_VERTS_OF_EDGE */
BM_FACE, /* BM_FACES_OF_EDGE */
BM_VERT, /* BM_VERTS_OF_FACE */
BM_EDGE, /* BM_EDGES_OF_FACE */
BM_LOOP, /* BM_LOOPS_OF_FACE */
BM_LOOP, /* BM_LOOPS_OF_LOOP */
BM_LOOP, /* BM_LOOPS_OF_EDGE */
};
int BM_iter_mesh_count(const char itype, BMesh *bm)
{
int count;
switch (itype) {
case BM_VERTS_OF_MESH:
count = bm->totvert;
break;
case BM_EDGES_OF_MESH:
count = bm->totedge;
break;
case BM_FACES_OF_MESH:
count = bm->totface;
break;
default:
count = 0;
BLI_assert(0);
break;
}
return count;
}
void *BM_iter_at_index(BMesh *bm, const char itype, void *data, int index)
{
BMIter iter;
void *val;
int i;
/* sanity check */
if (index < 0) {
return nullptr;
}
val = BM_iter_new(&iter, bm, itype, data);
i = 0;
while (i < index) {
val = BM_iter_step(&iter);
i++;
}
return val;
}
int BM_iter_as_array(BMesh *bm, const char itype, void *data, void **array, const int len)
{
int i = 0;
/* sanity check */
if (len > 0) {
BMIter iter;
void *ele;
for (ele = BM_iter_new(&iter, bm, itype, data); ele; ele = BM_iter_step(&iter)) {
array[i] = ele;
i++;
if (i == len) {
return len;
}
}
}
return i;
}
int BMO_iter_as_array(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const char restrictmask,
void **array,
const int len)
{
int i = 0;
/* sanity check */
if (len > 0) {
BMOIter oiter;
void *ele;
for (ele = BMO_iter_new(&oiter, slot_args, slot_name, restrictmask); ele;
ele = BMO_iter_step(&oiter))
{
array[i] = ele;
i++;
if (i == len) {
return len;
}
}
}
return i;
}
void *BM_iter_as_arrayN(BMesh *bm,
const char itype,
void *data,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size)
{
BMIter iter;
BLI_assert(stack_array_size == 0 || (stack_array_size && stack_array));
/* We can't rely on #BMIter.count being set. */
switch (itype) {
case BM_VERTS_OF_MESH:
iter.count = bm->totvert;
break;
case BM_EDGES_OF_MESH:
iter.count = bm->totedge;
break;
case BM_FACES_OF_MESH:
iter.count = bm->totface;
break;
default:
break;
}
if (BM_iter_init(&iter, bm, itype, data) && iter.count > 0) {
BMElem *ele;
BMElem **array = iter.count > stack_array_size ?
MEM_new_array_uninitialized<BMElem *>(iter.count, __func__) :
reinterpret_cast<BMElem **>(stack_array);
int i = 0;
*r_len = iter.count; /* set before iterating */
while ((ele = static_cast<BMElem *>(BM_iter_step(&iter)))) {
array[i++] = ele;
}
return array;
}
*r_len = 0;
return nullptr;
}
void *BMO_iter_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const char restrictmask,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size)
{
BMOIter iter;
BMElem *ele;
const int slot_len = BMO_slot_buffer_len(slot_args, slot_name);
BLI_assert(stack_array_size == 0 || (stack_array_size && stack_array));
if ((ele = static_cast<BMElem *>(BMO_iter_new(&iter, slot_args, slot_name, restrictmask))) &&
slot_len > 0)
{
BMElem **array = slot_len > stack_array_size ?
MEM_new_array_uninitialized<BMElem *>(slot_len, __func__) :
reinterpret_cast<BMElem **>(stack_array);
int i = 0;
do {
array[i++] = ele;
} while ((ele = static_cast<BMElem *>(BMO_iter_step(&iter))));
BLI_assert(i <= slot_len);
if (i != slot_len) {
if (reinterpret_cast<void **>(array) != stack_array) {
array = static_cast<BMElem **>(MEM_realloc_uninitialized(array, sizeof(ele) * i));
}
}
*r_len = i;
return array;
}
*r_len = 0;
return nullptr;
}
int BM_iter_mesh_bitmap_from_filter(const char itype,
BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMElem *, void *user_data),
void *user_data)
{
BMIter iter;
BMElem *ele;
int i;
int bitmap_enabled = 0;
BM_ITER_MESH_INDEX (ele, &iter, bm, itype, i) {
if (test_fn(ele, user_data)) {
bitmap[i].set();
bitmap_enabled++;
}
else {
bitmap[i].reset();
}
}
return bitmap_enabled;
}
int BM_iter_mesh_bitmap_from_filter_tessface(BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMFace *, void *user_data),
void *user_data)
{
BMIter iter;
BMFace *f;
int i;
int j = 0;
int bitmap_enabled = 0;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) {
if (test_fn(f, user_data)) {
for (int tri = 2; tri < f->len; tri++) {
bitmap[j].set();
bitmap_enabled++;
j++;
}
}
else {
for (int tri = 2; tri < f->len; tri++) {
bitmap[j].reset();
j++;
}
}
}
return bitmap_enabled;
}
int BM_iter_elem_count_flag(const char itype, void *data, const char hflag, const bool value)
{
BMIter iter;
BMElem *ele;
int count = 0;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BM_elem_flag_test_bool(ele, hflag) == value) {
count++;
}
}
return count;
}
int BMO_iter_elem_count_flag(
BMesh *bm, const char itype, void *data, const short oflag, const bool value)
{
BMIter iter;
int count = 0;
/* loops have no header flags */
BLI_assert(bm_iter_itype_htype_map[itype] != BM_LOOP);
switch (bm_iter_itype_htype_map[itype]) {
case BM_VERT: {
BMVert *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_vert_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
case BM_EDGE: {
BMEdge *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_edge_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
case BM_FACE: {
BMFace *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_face_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
}
return count;
}
int BM_iter_mesh_count_flag(const char itype, BMesh *bm, const char hflag, const bool value)
{
BMIter iter;
BMElem *ele;
int count = 0;
BM_ITER_MESH (ele, &iter, bm, itype) {
if (BM_elem_flag_test_bool(ele, hflag) == value) {
count++;
}
}
return count;
}
/**
* Notes on iterator implementation:
*
* Iterators keep track of the next element in a sequence.
* When a step() callback is invoked the current value of 'next'
* is stored to be returned later and the next variable is incremented.
*
* When the end of a sequence is reached, next should always equal nullptr
*
* The 'bmiter__' prefix is used because these are used in
* bmesh_iterators_inine.c but should otherwise be seen as
* private.
*/
/*
* VERT OF MESH CALLBACKS
*/
/* see bug #36923 for why we need this,
* allow adding but not removing, this isn't _totally_ safe since
* you could add/remove within the same loop, but catches common cases
*/
#ifndef NDEBUG
# define USE_IMMUTABLE_ASSERT
#endif
void bmiter__elem_of_mesh_begin(BMIter__elem_of_mesh *iter)
{
#ifdef USE_IMMUTABLE_ASSERT
(reinterpret_cast<BMIter *>(iter))->count = BLI_mempool_len(iter->pooliter.pool);
#endif
BLI_mempool_iternew(iter->pooliter.pool, &iter->pooliter);
}
void *bmiter__elem_of_mesh_step(BMIter__elem_of_mesh *iter)
{
#ifdef USE_IMMUTABLE_ASSERT
BLI_assert(((BMIter *)iter)->count <= BLI_mempool_len(iter->pooliter.pool));
#endif
return BLI_mempool_iterstep(&iter->pooliter);
}
#ifdef USE_IMMUTABLE_ASSERT
# undef USE_IMMUTABLE_ASSERT
#endif
/*
* EDGE OF VERT CALLBACKS
*/
void bmiter__edge_of_vert_begin(BMIter__edge_of_vert *iter)
{
if (iter->vdata->e) {
iter->e_first = iter->vdata->e;
iter->e_next = iter->vdata->e;
}
else {
iter->e_first = nullptr;
iter->e_next = nullptr;
}
}
void *bmiter__edge_of_vert_step(BMIter__edge_of_vert *iter)
{
BMEdge *e_curr = iter->e_next;
if (iter->e_next) {
iter->e_next = bmesh_disk_edge_next(iter->e_next, iter->vdata);
if (iter->e_next == iter->e_first) {
iter->e_next = nullptr;
}
}
return e_curr;
}
/*
* FACE OF VERT CALLBACKS
*/
void bmiter__face_of_vert_begin(BMIter__face_of_vert *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = bmesh_disk_facevert_count(iter->vdata);
if ((reinterpret_cast<BMIter *>(iter))->count) {
iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata);
iter->e_first = iter->l_first->e;
iter->e_next = iter->e_first;
iter->l_next = iter->l_first;
}
else {
iter->l_first = iter->l_next = nullptr;
iter->e_first = iter->e_next = nullptr;
}
}
void *bmiter__face_of_vert_step(BMIter__face_of_vert *iter)
{
BMLoop *l_curr = iter->l_next;
if ((reinterpret_cast<BMIter *>(iter))->count && iter->l_next) {
(reinterpret_cast<BMIter *>(iter))->count--;
iter->l_next = bmesh_radial_faceloop_find_next(iter->l_next, iter->vdata);
if (iter->l_next == iter->l_first) {
iter->e_next = bmesh_disk_faceedge_find_next(iter->e_next, iter->vdata);
iter->l_first = bmesh_radial_faceloop_find_first(iter->e_next->l, iter->vdata);
iter->l_next = iter->l_first;
}
}
if (!(reinterpret_cast<BMIter *>(iter))->count) {
iter->l_next = nullptr;
}
return l_curr ? l_curr->f : nullptr;
}
/*
* LOOP OF VERT CALLBACKS
*/
void bmiter__loop_of_vert_begin(BMIter__loop_of_vert *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = bmesh_disk_facevert_count(iter->vdata);
if ((reinterpret_cast<BMIter *>(iter))->count) {
iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata);
iter->e_first = iter->l_first->e;
iter->e_next = iter->e_first;
iter->l_next = iter->l_first;
}
else {
iter->l_first = iter->l_next = nullptr;
iter->e_first = iter->e_next = nullptr;
}
}
void *bmiter__loop_of_vert_step(BMIter__loop_of_vert *iter)
{
BMLoop *l_curr = iter->l_next;
if ((reinterpret_cast<BMIter *>(iter))->count) {
(reinterpret_cast<BMIter *>(iter))->count--;
iter->l_next = bmesh_radial_faceloop_find_next(iter->l_next, iter->vdata);
if (iter->l_next == iter->l_first) {
iter->e_next = bmesh_disk_faceedge_find_next(iter->e_next, iter->vdata);
iter->l_first = bmesh_radial_faceloop_find_first(iter->e_next->l, iter->vdata);
iter->l_next = iter->l_first;
}
}
if (!(reinterpret_cast<BMIter *>(iter))->count) {
iter->l_next = nullptr;
}
/* nullptr on finish */
return l_curr;
}
/*
* LOOP OF EDGE CALLBACKS
*/
void bmiter__loop_of_edge_begin(BMIter__loop_of_edge *iter)
{
iter->l_first = iter->l_next = iter->edata->l;
}
void *bmiter__loop_of_edge_step(BMIter__loop_of_edge *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
/* nullptr on finish */
return l_curr;
}
/*
* LOOP OF LOOP CALLBACKS
*/
void bmiter__loop_of_loop_begin(BMIter__loop_of_loop *iter)
{
iter->l_first = iter->ldata;
iter->l_next = iter->l_first->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
void *bmiter__loop_of_loop_step(BMIter__loop_of_loop *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
/* nullptr on finish */
return l_curr;
}
/*
* FACE OF EDGE CALLBACKS
*/
void bmiter__face_of_edge_begin(BMIter__face_of_edge *iter)
{
iter->l_first = iter->l_next = iter->edata->l;
}
void *bmiter__face_of_edge_step(BMIter__face_of_edge *iter)
{
BMLoop *current = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return current ? current->f : nullptr;
}
/*
* VERTS OF EDGE CALLBACKS
*/
void bmiter__vert_of_edge_begin(BMIter__vert_of_edge *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = 0;
}
void *bmiter__vert_of_edge_step(BMIter__vert_of_edge *iter)
{
switch ((reinterpret_cast<BMIter *>(iter))->count++) {
case 0:
return iter->edata->v1;
case 1:
return iter->edata->v2;
default:
return nullptr;
}
}
/*
* VERT OF FACE CALLBACKS
*/
void bmiter__vert_of_face_begin(BMIter__vert_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__vert_of_face_step(BMIter__vert_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr ? l_curr->v : nullptr;
}
/*
* EDGE OF FACE CALLBACKS
*/
void bmiter__edge_of_face_begin(BMIter__edge_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__edge_of_face_step(BMIter__edge_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr ? l_curr->e : nullptr;
}
/*
* LOOP OF FACE CALLBACKS
*/
void bmiter__loop_of_face_begin(BMIter__loop_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__loop_of_face_step(BMIter__loop_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr;
}
} // namespace blender

View File

@@ -0,0 +1,295 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
/**
* \brief BMesh Iterators
*
* The functions and structures in this file
* provide a unified method for iterating over
* the elements of a mesh and answering simple
* adjacency queries. Tool authors should use
* the iterators provided in this file instead
* of inspecting the structure directly.
*/
#include "BLI_bit_span.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_mempool.h"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
/* these iterator over all elements of a specific
* type in the mesh.
*
* be sure to keep 'bm_iter_itype_htype_map' in sync with any changes
*/
namespace blender {
enum BMIterType {
BM_VERTS_OF_MESH = 1,
BM_EDGES_OF_MESH = 2,
BM_FACES_OF_MESH = 3,
/* these are topological iterators. */
BM_EDGES_OF_VERT = 4,
BM_FACES_OF_VERT = 5,
BM_LOOPS_OF_VERT = 6,
BM_VERTS_OF_EDGE = 7, /* just v1, v2: added so py can use generalized sequencer wrapper */
BM_FACES_OF_EDGE = 8,
BM_VERTS_OF_FACE = 9,
BM_EDGES_OF_FACE = 10,
BM_LOOPS_OF_FACE = 11,
/* returns elements from all boundaries, and returns
* the first element at the end to flag that we're entering
* a different face hole boundary. */
// BM_ALL_LOOPS_OF_FACE = 12,
/* iterate through loops around this loop, which are fetched
* from the other faces in the radial cycle surrounding the
* input loop's edge. */
BM_LOOPS_OF_LOOP = 12,
BM_LOOPS_OF_EDGE = 13,
};
#define BM_ITYPE_MAX 14
/* the iterator htype for each iterator */
extern const char bm_iter_itype_htype_map[BM_ITYPE_MAX];
/* -------------------------------------------------------------------- */
/** \name Defines for passing to #BM_iter_new.
*
* "OF" can be substituted for "around" so #BM_VERTS_OF_FACE means "vertices* around a face."
* \{ */
#define BM_ITER_MESH(ele, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_MESH_INDEX(ele, iter, bm, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL), indexvar = 0; ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
/* a version of BM_ITER_MESH which keeps the next item in storage
* so we can delete the current item, see bug #36923. */
#ifndef NDEBUG
# define BM_ITER_MESH_MUTABLE(ele, ele_next, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); \
ele ? ((void)((iter)->count = BM_iter_mesh_count(itype, bm)), \
(void)(BM_CHECK_TYPE_ELEM_ASSIGN(ele_next) = BM_iter_step(iter)), \
1) : \
0; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = ele_next)
#else
# define BM_ITER_MESH_MUTABLE(ele, ele_next, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); \
ele ? ((BM_CHECK_TYPE_ELEM_ASSIGN(ele_next) = BM_iter_step(iter)), 1) : 0; \
ele = ele_next)
#endif
#define BM_ITER_ELEM(ele, iter, data, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, NULL, itype, data); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_ELEM_INDEX(ele, iter, data, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, NULL, itype, data), indexvar = 0; ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
/** \} */
/* iterator type structs */
struct BMIter__elem_of_mesh {
BLI_mempool_iter pooliter;
};
struct BMIter__edge_of_vert {
BMVert *vdata;
BMEdge *e_first, *e_next;
};
struct BMIter__face_of_vert {
BMVert *vdata;
BMLoop *l_first, *l_next;
BMEdge *e_first, *e_next;
};
struct BMIter__loop_of_vert {
BMVert *vdata;
BMLoop *l_first, *l_next;
BMEdge *e_first, *e_next;
};
struct BMIter__loop_of_edge {
BMEdge *edata;
BMLoop *l_first, *l_next;
};
struct BMIter__loop_of_loop {
BMLoop *ldata;
BMLoop *l_first, *l_next;
};
struct BMIter__face_of_edge {
BMEdge *edata;
BMLoop *l_first, *l_next;
};
struct BMIter__vert_of_edge {
BMEdge *edata;
};
struct BMIter__vert_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
struct BMIter__edge_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
struct BMIter__loop_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
using BMIter__begin_cb = void (*)(void *);
using BMIter__step_cb = void *(*)(void *);
/* Iterator Structure */
/* NOTE: some of these vars are not used,
* so they have been commented to save stack space since this struct is used all over */
struct BMIter {
/* keep union first */
union {
BMIter__elem_of_mesh elem_of_mesh;
BMIter__edge_of_vert edge_of_vert;
BMIter__face_of_vert face_of_vert;
BMIter__loop_of_vert loop_of_vert;
BMIter__loop_of_edge loop_of_edge;
BMIter__loop_of_loop loop_of_loop;
BMIter__face_of_edge face_of_edge;
BMIter__vert_of_edge vert_of_edge;
BMIter__vert_of_face vert_of_face;
BMIter__edge_of_face edge_of_face;
BMIter__loop_of_face loop_of_face;
} data;
BMIter__begin_cb begin;
BMIter__step_cb step;
int count; /* NOTE: only some iterators set this, don't rely on it. */
char itype;
};
/**
* \note Use #BM_vert_at_index / #BM_edge_at_index / #BM_face_at_index for mesh arrays.
*/
void *BM_iter_at_index(BMesh *bm, char itype, void *data, int index) ATTR_WARN_UNUSED_RESULT;
/**
* \brief Iterator as Array
*
* Sometimes its convenient to get the iterator as an array
* to avoid multiple calls to #BM_iter_at_index.
*/
int BM_iter_as_array(BMesh *bm, char itype, void *data, void **array, int len);
/**
* \brief Iterator as Array
*
* Allocates a new array, has the advantage that you don't need to know the size ahead of time.
*
* Takes advantage of less common iterator usage to avoid counting twice,
* which you might end up doing when #BM_iter_as_array is used.
*
* Caller needs to free the array.
*/
void *BM_iter_as_arrayN(BMesh *bm,
char itype,
void *data,
int *r_len,
void **stack_array,
int stack_array_size) ATTR_WARN_UNUSED_RESULT;
/**
* \brief Operator Iterator as Array
*
* Sometimes its convenient to get the iterator as an array.
*/
int BMO_iter_as_array(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask,
void **array,
int len);
void *BMO_iter_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size);
int BM_iter_mesh_bitmap_from_filter(char itype,
BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMElem *, void *user_data),
void *user_data);
/**
* Needed when we want to check faces, but return a loop aligned array.
*/
int BM_iter_mesh_bitmap_from_filter_tessface(BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMFace *, void *user_data),
void *user_data);
/**
* \brief Elem Iter Flag Count
*
* Counts how many flagged / unflagged items are found in this element.
*/
int BM_iter_elem_count_flag(char itype, void *data, char hflag, bool value);
/**
* \brief Elem Iter Tool Flag Count
*
* Counts how many flagged / unflagged items are found in this element.
*/
int BMO_iter_elem_count_flag(BMesh *bm, char itype, void *data, short oflag, bool value);
/**
* Utility function.
*/
int BM_iter_mesh_count(char itype, BMesh *bm);
/**
* \brief Mesh Iter Flag Count
*
* Counts how many flagged / unflagged items are found in this mesh.
*/
int BM_iter_mesh_count_flag(char itype, BMesh *bm, char hflag, bool value);
/* private for bmesh_iterators_inline.c */
#define BMITER_CB_DEF(name) \
struct BMIter__##name; \
void bmiter__##name##_begin(struct BMIter__##name *iter); \
void *bmiter__##name##_step(struct BMIter__##name *iter)
BMITER_CB_DEF(elem_of_mesh);
BMITER_CB_DEF(edge_of_vert);
BMITER_CB_DEF(face_of_vert);
BMITER_CB_DEF(loop_of_vert);
BMITER_CB_DEF(loop_of_edge);
BMITER_CB_DEF(loop_of_loop);
BMITER_CB_DEF(face_of_edge);
BMITER_CB_DEF(vert_of_edge);
BMITER_CB_DEF(vert_of_face);
BMITER_CB_DEF(edge_of_face);
BMITER_CB_DEF(loop_of_face);
#undef BMITER_CB_DEF
} // namespace blender
#include "intern/bmesh_iterators_inline.hh" /* IWYU pragma: export */
#define BM_ITER_CHECK_TYPE_DATA(data) \
CHECK_TYPE_ANY(data, void *, BMFace *, BMEdge *, BMVert *, BMLoop *, BMElem *)
#define BM_iter_new(iter, bm, itype, data) \
(BM_ITER_CHECK_TYPE_DATA(data), BM_iter_new(iter, bm, itype, data))
#define BM_iter_init(iter, bm, itype, data) \
(BM_ITER_CHECK_TYPE_DATA(data), BM_iter_init(iter, bm, itype, data))

View File

@@ -0,0 +1,205 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline iterator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
namespace blender {
/* inline here optimizes out the switch statement when called with
* constant values (which is very common), nicer for loop-in-loop situations */
/**
* \brief Iterator Step
*
* Calls an iterators step function to return the next element.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE void *BM_iter_step(BMIter *iter)
{
return iter->step(iter);
}
/**
* \brief Iterator Init
*
* Takes a bmesh iterator structure and fills
* it with the appropriate function pointers based
* upon its type.
*/
ATTR_NONNULL(1) BLI_INLINE bool BM_iter_init(BMIter *iter, BMesh *bm, const char itype, void *data)
{
// int argtype;
iter->itype = itype;
/* inlining optimizes out this switch when called with the defined type */
switch (BMIterType(itype)) {
case BM_VERTS_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->vpool;
break;
case BM_EDGES_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->epool;
break;
case BM_FACES_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->fpool;
break;
case BM_EDGES_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__edge_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__edge_of_vert_step);
iter->data.edge_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_FACES_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__face_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__face_of_vert_step);
iter->data.face_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_LOOPS_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_vert_step);
iter->data.loop_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_VERTS_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__vert_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__vert_of_edge_step);
iter->data.vert_of_edge.edata = static_cast<BMEdge *>(data);
break;
case BM_FACES_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__face_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__face_of_edge_step);
iter->data.face_of_edge.edata = static_cast<BMEdge *>(data);
break;
case BM_VERTS_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__vert_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__vert_of_face_step);
iter->data.vert_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_EDGES_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__edge_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__edge_of_face_step);
iter->data.edge_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_LOOPS_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_face_step);
iter->data.loop_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_LOOPS_OF_LOOP:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_LOOP);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_loop_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_loop_step);
iter->data.loop_of_loop.ldata = static_cast<BMLoop *>(data);
break;
case BM_LOOPS_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_edge_step);
iter->data.loop_of_edge.edata = static_cast<BMEdge *>(data);
break;
default:
/* should never happen */
BLI_assert(0);
return false;
break;
}
iter->begin(iter);
return true;
}
/**
* \brief Iterator New
*
* Takes a bmesh iterator structure and fills
* it with the appropriate function pointers based
* upon its type and then calls BMeshIter_step()
* to return the first element of the iterator.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BM_iter_new(BMIter *iter, BMesh *bm, const char itype, void *data)
{
if (LIKELY(BM_iter_init(iter, bm, itype, data))) {
return BM_iter_step(iter);
}
else {
return nullptr;
}
}
/**
* \brief Parallel (threaded) iterator,
* only available for most basic iteration-types (verts/edges/faces of mesh).
*
* Uses #BLI_task_parallel_mempool to iterate over all items of underlying matching mempool.
*
* \note You have to include BLI_task.h before BMesh includes to be able to use this function!
*/
#ifdef __BLI_TASK_H__
ATTR_NONNULL(1)
BLI_INLINE void BM_iter_parallel(BMesh *bm,
const char itype,
TaskParallelMempoolFunc func,
void *userdata,
const TaskParallelSettings *settings)
{
/* inlining optimizes out this switch when called with the defined type */
switch (BMIterType(itype)) {
case BM_VERTS_OF_MESH:
BLI_task_parallel_mempool(bm->vpool, userdata, func, settings);
break;
case BM_EDGES_OF_MESH:
BLI_task_parallel_mempool(bm->epool, userdata, func, settings);
break;
case BM_FACES_OF_MESH:
BLI_task_parallel_mempool(bm->fpool, userdata, func, settings);
break;
default:
/* should never happen */
BLI_assert(0);
break;
}
}
#endif /* __BLI_TASK_H__ */
} // namespace blender

View File

@@ -0,0 +1,890 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* The BMLog is an interface for storing undo/redo steps as a BMesh is
* modified. It only stores changes to the BMesh, not full copies.
*
* Currently it supports the following types of changes:
*
* - Adding and removing vertices
* - Adding and removing faces
* - Moving vertices
* - Setting vertex paint-mask values
* - Setting vertex hflags
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_vector.h"
#include "BLI_pool.hh"
#include "BLI_utildefines.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "bmesh_log.hh"
#include "range_tree.h"
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
struct BMLogFace;
struct BMLogVert;
struct BMLogEntry {
BMLogEntry *next, *prev;
/* The following members map from an element ID to one of the log types above. */
/** Elements that were in the previous entry, but have been deleted. */
Map<uint, BMLogVert *, 0> deleted_verts;
Map<uint, BMLogFace *, 0> deleted_faces;
/** Elements that were not in the previous entry, but are in the result of this entry. */
Map<uint, BMLogVert *, 0> added_verts;
Map<uint, BMLogFace *, 0> added_faces;
/** Vertices whose coordinates, mask value, or hflag have changed. */
Map<uint, BMLogVert *, 0> modified_verts;
Map<uint, BMLogFace *, 0> modified_faces;
Pool<BMLogVert> vert_pool;
Pool<BMLogFace> face_pool;
Vector<BMLogVert *, 0> allocated_verts;
Vector<BMLogFace *, 0> allocated_faces;
/**
* This is only needed for dropping BMLogEntries while still in
* dynamic-topology mode, as that should release vert/face IDs
* back to the BMLog but no BMLog pointer is available at that time.
*
* This field is not guaranteed to be valid, any use of it should
* check for nullptr.
*/
BMLog *log;
};
struct BMLog {
/** Tree of free IDs */
RangeTreeUInt *unused_ids;
/**
* Mapping from unique IDs to vertices and faces
*
* Each vertex and face in the log gets a unique `uint`
* assigned. That ID is taken from the set managed by the
* unused_ids range tree.
*
* The ID is needed because element pointers will change as they
* are created and deleted.
*/
Map<uint, BMElem *, 0> id_to_elem;
Map<BMElem *, uint, 0> elem_to_id;
/** All #BMLogEntrys, ordered from earliest to most recent. */
ListBaseT<BMLogEntry> entries;
/**
* The current log entry from entries list
*
* If null, then the original mesh from before any of the log
* entries is current (i.e. there is nothing left to undo.)
*
* If equal to the last entry in the entries list, then all log
* entries have been applied (i.e. there is nothing left to redo.)
*/
BMLogEntry *current_entry;
};
struct BMLogVert {
float3 position;
float3 normal;
char hflag;
float mask;
};
struct BMLogFace {
std::array<uint, 3> v_ids;
char hflag;
};
/* -------------------------------------------------------------------- */
/** \name Get/Set Element IDs
* \{ */
/* Get the vertex's unique ID from the log */
static uint bm_log_vert_id_get(BMLog *log, BMVert *v)
{
return log->elem_to_id.lookup(reinterpret_cast<BMElem *>(v));
}
/* Set the vertex's unique ID in the log */
static void bm_log_vert_id_set(BMLog *log, BMVert *v, const uint id)
{
log->id_to_elem.add_overwrite(id, reinterpret_cast<BMElem *>(v));
log->elem_to_id.add_overwrite(reinterpret_cast<BMElem *>(v), id);
}
/* Get a vertex from its unique ID */
static BMVert *bm_log_vert_from_id(BMLog *log, const uint id)
{
return reinterpret_cast<BMVert *>(log->id_to_elem.lookup(id));
}
/* Get the face's unique ID from the log */
static uint bm_log_face_id_get(BMLog *log, BMFace *f)
{
return log->elem_to_id.lookup(reinterpret_cast<BMElem *>(f));
}
/* Set the face's unique ID in the log */
static void bm_log_face_id_set(BMLog *log, BMFace *f, const uint id)
{
log->id_to_elem.add_overwrite(id, reinterpret_cast<BMElem *>(f));
log->elem_to_id.add_overwrite(reinterpret_cast<BMElem *>(f), id);
}
/* Get a face from its unique ID */
static BMFace *bm_log_face_from_id(BMLog *log, const uint id)
{
return reinterpret_cast<BMFace *>(log->id_to_elem.lookup(id));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMLogVert / BMLogFace
* \{ */
/* Get a vertex's paint-mask value
*
* Returns zero if no paint-mask layer is present */
static float vert_mask_get(BMVert *v, const int cd_vert_mask_offset)
{
if (cd_vert_mask_offset != -1) {
return BM_ELEM_CD_GET_FLOAT(v, cd_vert_mask_offset);
}
return 0.0f;
}
/* Set a vertex's paint-mask value
*
* Has no effect is no paint-mask layer is present */
static void vert_mask_set(BMVert *v, const float new_mask, const int cd_vert_mask_offset)
{
if (cd_vert_mask_offset != -1) {
BM_ELEM_CD_SET_FLOAT(v, cd_vert_mask_offset, new_mask);
}
}
/* Update a BMLogVert with data from a BMVert */
static void bm_log_vert_bmvert_copy(BMLogVert *lv, BMVert *v, const int cd_vert_mask_offset)
{
copy_v3_v3(lv->position, v->co);
copy_v3_v3(lv->normal, v->no);
lv->mask = vert_mask_get(v, cd_vert_mask_offset);
lv->hflag = v->head.hflag;
}
/* Allocate and initialize a BMLogVert */
static BMLogVert *bm_log_vert_alloc(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
BMLogVert *lv = &entry->vert_pool.construct();
entry->allocated_verts.append(lv);
bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset);
return lv;
}
/* Allocate and initialize a BMLogFace */
static BMLogFace *bm_log_face_alloc(BMLog *log, BMFace *f)
{
BMLogEntry *entry = log->current_entry;
BMLogFace *lf = &entry->face_pool.construct();
entry->allocated_faces.append(lf);
BMVert *v[3];
BLI_assert(f->len == 3);
// BM_iter_as_array(nullptr, BM_VERTS_OF_FACE, f, (void **)v, 3);
BM_face_as_array_vert_tri(f, v);
lf->v_ids[0] = bm_log_vert_id_get(log, v[0]);
lf->v_ids[1] = bm_log_vert_id_get(log, v[1]);
lf->v_ids[2] = bm_log_vert_id_get(log, v[2]);
lf->hflag = f->head.hflag;
return lf;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Helpers for Undo/Redo
* \{ */
static void bm_log_verts_unmake(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMVert *v = bm_log_vert_from_id(log, item.key);
/* Ensure the log has the final values of the vertex before
* deleting it */
bm_log_vert_bmvert_copy(item.value, v, cd_vert_mask_offset);
BM_vert_kill(bm, v);
}
}
static void bm_log_faces_unmake(BMesh *bm, BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
for (const uint id : faces.keys()) {
BMFace *f = bm_log_face_from_id(log, id);
std::array<BMEdge *, 3> e_tri;
BMLoop *l_iter = BM_FACE_FIRST_LOOP(f);
for (uint i = 0; i < e_tri.size(); i++, l_iter = l_iter->next) {
e_tri[i] = l_iter->e;
}
/* Remove any unused edges */
BM_face_kill(bm, f);
for (uint i = 0; i < e_tri.size(); i++) {
if (BM_edge_is_wire(e_tri[i])) {
BM_edge_kill(bm, e_tri[i]);
}
}
}
}
static void bm_log_verts_restore(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMLogVert *lv = item.value;
BMVert *v = BM_vert_create(bm, lv->position, nullptr, BM_CREATE_NOP);
vert_mask_set(v, lv->mask, cd_vert_mask_offset);
v->head.hflag = lv->hflag;
copy_v3_v3(v->no, lv->normal);
bm_log_vert_id_set(log, v, item.key);
}
}
static void bm_log_faces_restore(BMesh *bm, BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
const int cd_face_sets = CustomData_get_offset_named(
&bm->pdata, CD_PROP_INT32, ".sculpt_face_set");
for (const auto item : faces.items()) {
BMLogFace *lf = item.value;
BMVert *v[3] = {
bm_log_vert_from_id(log, lf->v_ids[0]),
bm_log_vert_from_id(log, lf->v_ids[1]),
bm_log_vert_from_id(log, lf->v_ids[2]),
};
BMFace *f = BM_face_create_verts(bm, v, 3, nullptr, BM_CREATE_NOP, true);
f->head.hflag = lf->hflag;
bm_log_face_id_set(log, f, item.key);
/* Ensure face sets have valid values. Fixes #80174. */
if (cd_face_sets != -1) {
BM_ELEM_CD_SET_INT(f, cd_face_sets, 1);
}
}
}
static void bm_log_vert_values_swap(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMLogVert *lv = item.value;
BMVert *v = bm_log_vert_from_id(log, item.key);
swap_v3_v3(v->co, lv->position);
swap_v3_v3(v->no, lv->normal);
std::swap(v->head.hflag, lv->hflag);
float mask = lv->mask;
lv->mask = vert_mask_get(v, cd_vert_mask_offset);
vert_mask_set(v, mask, cd_vert_mask_offset);
}
}
static void bm_log_face_values_swap(BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
for (const auto item : faces.items()) {
BMLogFace *lf = item.value;
BMFace *f = bm_log_face_from_id(log, item.key);
std::swap(f->head.hflag, lf->hflag);
}
}
/* Assign unique IDs to all vertices and faces already in the BMesh */
static void bm_log_assign_ids(BMesh *bm, BMLog *log)
{
BMIter iter;
BMVert *v;
/* Generate vertex IDs */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
uint id = range_tree_uint_take_any(log->unused_ids);
bm_log_vert_id_set(log, v, id);
}
BMFace *f;
/* Generate face IDs */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
uint id = range_tree_uint_take_any(log->unused_ids);
bm_log_face_id_set(log, f, id);
}
}
/* Allocate an empty log entry */
static BMLogEntry *bm_log_entry_create()
{
BMLogEntry *entry = MEM_new<BMLogEntry>(__func__);
return entry;
}
/* Free the data in a log entry
*
* NOTE: does not free the log entry itself. */
static void bm_log_entry_free(BMLogEntry *entry)
{
BLI_assert(entry->vert_pool.size() == entry->allocated_verts.size());
BLI_assert(entry->face_pool.size() == entry->allocated_faces.size());
for (BMLogVert *log_vert : entry->allocated_verts) {
entry->vert_pool.destruct(*log_vert);
}
for (BMLogFace *log_face : entry->allocated_faces) {
entry->face_pool.destruct(*log_face);
}
BLI_assert(entry->vert_pool.is_empty());
BLI_assert(entry->face_pool.is_empty());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
BMLog *BM_log_create(BMesh *bm)
{
BMLog *log = MEM_new<BMLog>(__func__);
const uint reserve_num = uint(bm->totvert + bm->totface);
log->unused_ids = range_tree_uint_alloc(0, uint(-1));
log->id_to_elem.reserve(reserve_num);
log->elem_to_id.reserve(reserve_num);
/* Assign IDs to all existing vertices and faces */
bm_log_assign_ids(bm, log);
return log;
}
void BM_log_cleanup_entry(BMLogEntry *entry)
{
BMLog *log = entry->log;
if (log) {
/* Take all used IDs */
for (const uint id : entry->deleted_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->deleted_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
/* delete entries to avoid releasing ids in node cleanup */
entry->deleted_verts.clear();
entry->deleted_faces.clear();
entry->added_verts.clear();
entry->added_faces.clear();
entry->modified_verts.clear();
/* Is this last one needed? */
entry->modified_faces.clear();
}
}
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry)
{
BMLog *log = BM_log_create(bm);
if (entry->prev) {
log->current_entry = entry;
}
else {
log->current_entry = nullptr;
}
/* Let BMLog manage the entry list again */
log->entries.first = log->entries.last = entry;
{
while (entry->prev) {
entry = entry->prev;
log->entries.first = entry;
}
entry = static_cast<BMLogEntry *>(log->entries.last);
while (entry->next) {
entry = entry->next;
log->entries.last = entry;
}
}
for (entry = static_cast<BMLogEntry *>(log->entries.first); entry; entry = entry->next) {
entry->log = log;
/* Take all used IDs */
for (const uint id : entry->deleted_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->deleted_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
}
return log;
}
void BM_log_free(BMLog *log)
{
if (log->unused_ids) {
range_tree_uint_free(log->unused_ids);
}
/* Clear the BMLog references within each entry, but do not free
* the entries themselves */
for (BMLogEntry &entry : log->entries) {
entry.log = nullptr;
}
MEM_delete(log);
}
BMLogEntry *BM_log_entry_add(BMLog *log)
{
/* WARNING: Deleting any entries after the current one is now handled by the
* UndoSystem: BKE_UNDOSYS_TYPE_SCULPT freeing here causes unnecessary complications. */
/* Create and append the new entry */
BMLogEntry *entry = bm_log_entry_create();
BLI_addtail(&log->entries, entry);
entry->log = log;
log->current_entry = entry;
return entry;
}
void BM_log_entry_drop(BMLogEntry *entry)
{
BMLog *log = entry->log;
if (!log) {
/* Unlink */
BLI_assert(!(entry->prev && entry->next));
if (entry->prev) {
entry->prev->next = nullptr;
}
else if (entry->next) {
entry->next->prev = nullptr;
}
bm_log_entry_free(entry);
MEM_delete(entry);
return;
}
if (!entry->prev) {
/* Release IDs of elements that are deleted by this
* entry. Since the entry is at the beginning of the undo
* stack, and it's being deleted, those elements can never be
* restored. Their IDs can go back into the pool. */
/* This would never happen usually since first entry of log is
* usually dyntopo enable, which, when reverted will free the log
* completely. However, it is possible have a stroke instead of
* dyntopo enable as first entry if nodes have been cleaned up
* after sculpting on a different object than A, B.
*
* The steps are:
* A dyntopo enable - sculpt
* B dyntopo enable - sculpt - undo (A objects operators get cleaned up)
* A sculpt (now A's log has a sculpt operator as first entry)
*
* Causing a cleanup at this point will call the code below, however
* this will invalidate the state of the log since the deleted vertices
* have been reclaimed already on step 2 (see BM_log_cleanup_entry)
*
* Also, design wise, a first entry should not have any deleted vertices since it
* should not have anything to delete them -from-
*/
// bm_log_id_ghash_release(log, entry->deleted_faces);
// bm_log_id_ghash_release(log, entry->deleted_verts);
}
else if (!entry->next) {
/* Release IDs of elements that are added by this entry. Since
* the entry is at the end of the undo stack, and it's being
* deleted, those elements can never be restored. Their IDs
* can go back into the pool. */
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_release(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_release(log->unused_ids, id);
}
}
else {
BLI_assert_msg(0, "Cannot drop BMLogEntry from middle");
}
if (log->current_entry == entry) {
log->current_entry = entry->prev;
}
bm_log_entry_free(entry);
BLI_remlink(&log->entries, entry);
MEM_delete(entry);
}
void BM_log_undo(BMesh *bm, BMLog *log)
{
BMLogEntry *entry = log->current_entry;
if (entry) {
log->current_entry = entry->prev;
/* Delete added faces and verts */
bm_log_faces_unmake(bm, log, entry->added_faces);
bm_log_verts_unmake(bm, log, entry->added_verts);
/* Restore deleted verts and faces */
bm_log_verts_restore(bm, log, entry->deleted_verts);
bm_log_faces_restore(bm, log, entry->deleted_faces);
/* Restore vertex coordinates, mask, and hflag */
bm_log_vert_values_swap(bm, log, entry->modified_verts);
bm_log_face_values_swap(log, entry->modified_faces);
}
}
void BM_log_redo(BMesh *bm, BMLog *log)
{
BMLogEntry *entry = log->current_entry;
if (!entry) {
/* Currently at the beginning of the undo stack, move to first entry */
entry = static_cast<BMLogEntry *>(log->entries.first);
}
else if (entry->next) {
/* Move to next undo entry */
entry = entry->next;
}
else {
/* Currently at the end of the undo stack, nothing left to redo */
return;
}
log->current_entry = entry;
if (entry) {
/* Re-delete previously deleted faces and verts */
bm_log_faces_unmake(bm, log, entry->deleted_faces);
bm_log_verts_unmake(bm, log, entry->deleted_verts);
/* Restore previously added verts and faces */
bm_log_verts_restore(bm, log, entry->added_verts);
bm_log_faces_restore(bm, log, entry->added_faces);
/* Restore vertex coordinates, mask, and hflag */
bm_log_vert_values_swap(bm, log, entry->modified_verts);
bm_log_face_values_swap(log, entry->modified_faces);
}
}
void BM_log_vert_before_modified(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
/* Find or create the BMLogVert entry */
if (entry->added_verts.contains(v_id)) {
bm_log_vert_bmvert_copy(entry->added_verts.lookup(v_id), v, cd_vert_mask_offset);
}
else {
entry->modified_verts.lookup_or_add_cb(
v_id, [&] { return bm_log_vert_alloc(log, v, cd_vert_mask_offset); });
}
}
void BM_log_vert_added(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
const uint v_id = range_tree_uint_take_any(log->unused_ids);
bm_log_vert_id_set(log, v, v_id);
BMLogVert *lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset);
log->current_entry->added_verts.add(v_id, lv);
}
void BM_log_face_modified(BMLog *log, BMFace *f)
{
const uint f_id = bm_log_face_id_get(log, f);
BMLogFace *lf = bm_log_face_alloc(log, f);
log->current_entry->modified_faces.add(f_id, lf);
}
void BM_log_face_added(BMLog *log, BMFace *f)
{
const uint f_id = range_tree_uint_take_any(log->unused_ids);
/* Only triangles are supported for now */
BLI_assert(f->len == 3);
bm_log_face_id_set(log, f, f_id);
BMLogFace *lf = bm_log_face_alloc(log, f);
log->current_entry->added_faces.add(f_id, lf);
}
void BM_log_vert_removed(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
BLI_assert(!entry->added_verts.contains(v_id) ||
(entry->added_verts.contains(v_id) && entry->added_verts.lookup(v_id) != nullptr));
if (entry->added_verts.remove(v_id)) {
range_tree_uint_release(log->unused_ids, v_id);
}
else {
BMLogVert *lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset);
entry->deleted_verts.add(v_id, lv);
/* If the vertex was modified before deletion, ensure that the
* original vertex values are stored */
if (std::optional<BMLogVert *> lv_mod = entry->modified_verts.lookup_try(v_id)) {
*lv = *lv_mod.value();
entry->modified_verts.remove(v_id);
}
}
}
void BM_log_face_removed(BMLog *log, BMFace *f)
{
BMLogEntry *entry = log->current_entry;
const uint f_id = bm_log_face_id_get(log, f);
BLI_assert(!entry->added_faces.contains(f_id) ||
(entry->added_faces.contains(f_id) && entry->added_faces.lookup(f_id) != nullptr));
if (entry->added_faces.remove(f_id)) {
range_tree_uint_release(log->unused_ids, f_id);
}
else {
BMLogFace *lf = bm_log_face_alloc(log, f);
entry->deleted_faces.add(f_id, lf);
}
}
void BM_log_all_added(BMesh *bm, BMLog *log)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
/* avoid unnecessary resizing on initialization */
if (log->current_entry->added_verts.is_empty()) {
log->current_entry->added_verts.reserve(bm->totvert);
}
if (log->current_entry->added_faces.is_empty()) {
log->current_entry->added_faces.reserve(bm->totface);
}
BMIter bm_iter;
BMVert *v;
/* Log all vertices as newly created */
BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) {
BM_log_vert_added(log, v, cd_vert_mask_offset);
}
BMFace *f;
/* Log all faces as newly created */
BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) {
BM_log_face_added(log, f);
}
}
void BM_log_before_all_removed(BMesh *bm, BMLog *log)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
BMIter bm_iter;
BMFace *f;
/* Log deletion of all faces */
BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) {
BM_log_face_removed(log, f);
}
BMVert *v;
/* Log deletion of all vertices */
BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) {
BM_log_vert_removed(log, v, cd_vert_mask_offset);
}
}
const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
if (std::optional<BMLogVert *> log_vert = entry->modified_verts.lookup_try(v_id)) {
return log_vert.value()->position;
}
return nullptr;
}
const float *BM_log_find_original_vert_mask(BMLog *log, BMVert *v)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
if (std::optional<BMLogVert *> log_vert = entry->modified_verts.lookup_try(v_id)) {
return &log_vert.value()->mask;
}
return nullptr;
}
void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no)
{
BMLogEntry *entry = log->current_entry;
BLI_assert(entry);
const uint v_id = bm_log_vert_id_get(log, v);
BLI_assert(entry->modified_verts.contains(v_id));
const BMLogVert *lv = entry->modified_verts.lookup(v_id);
*r_co = lv->position;
*r_no = lv->normal;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging and Testing
* \{ */
#ifndef NDEBUG
BMLogEntry *BM_log_current_entry(BMLog *log)
{
return log->current_entry;
}
RangeTreeUInt *BM_log_unused_ids(BMLog *log)
{
return log->unused_ids;
}
/* Print the list of entries, marking the current one
*
* Keep around for debugging */
void BM_log_print(const BMLog *log, const char *description)
{
const BMLogEntry *entry;
const char *current = " <-- current";
int i;
printf("%s:\n", description);
printf(" % 2d: [ initial ]%s\n", 0, (!log->current_entry) ? current : "");
for (entry = static_cast<const BMLogEntry *>(log->entries.first), i = 1; entry;
entry = entry->next, i++)
{
printf(" % 2d: [%p]%s\n", i, entry, (entry == log->current_entry) ? current : "");
}
}
void BM_log_print_entry(BMesh *bm, BMLogEntry *entry)
{
if (bm) {
printf("BM { totvert=%d totedge=%d totloop=%d faces_num=%d\n",
bm->totvert,
bm->totedge,
bm->totloop,
bm->totface);
if (!bm->totvert) {
printf("%s: Warning: empty bmesh\n", __func__);
}
}
else {
printf("BM { totvert=unknown totedge=unknown totloop=unknown faces_num=unknown\n");
}
printf("v | added: %d, removed: %d, modified: %d\n",
int(entry->added_verts.size()),
int(entry->deleted_verts.size()),
int(entry->modified_verts.size()));
printf("f | added: %d, removed: %d, modified: %d\n",
int(entry->added_faces.size()),
int(entry->deleted_faces.size()),
int(entry->modified_faces.size()));
printf("}\n");
}
#endif
/** \} */
} // namespace blender

View File

@@ -0,0 +1,205 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
struct RangeTreeUInt;
namespace blender {
struct BMFace;
struct BMVert;
struct BMesh;
struct BMLog;
struct BMLogEntry;
/**
* Allocate, initialize, and assign a new BMLog.
*/
BMLog *BM_log_create(BMesh *bm);
/**
* Allocate and initialize a new #BMLog using existing #BMLogEntries
*
* The unused IDs field of the log will be initialized by taking all
* keys from all Maps in the log entry.
*
* \param entry: The last entry of the prior BMLog, its `prev` pointer will be followed back to
* reconstruct the log.
*/
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry);
/**
* Free all the data in a BMLog including the log itself.
*/
void BM_log_free(BMLog *log);
/**
* Start a new log entry and update the log entry list.
*
* If the log entry list is empty, or if the current log entry is the
* last entry, the new entry is simply appended to the end.
*
* Finally, the new entry is set as the current log entry.
*/
BMLogEntry *BM_log_entry_add(BMLog *log);
/** Mark all used ids as unused for this node */
void BM_log_cleanup_entry(BMLogEntry *entry);
/**
* Remove an entry from the log.
*
* Uses entry->log as the log. If the log is NULL, the entry will be
* freed but not removed from any list, nor shall its IDs be released.
*
* \warning This operation is only valid on the first and last entries in the log. Deleting from
* the middle will assert.
*/
void BM_log_entry_drop(BMLogEntry *entry);
/**
* Undo one #BMLogEntry.
*
* Has no effect if there's nothing left to undo.
*/
void BM_log_undo(BMesh *bm, BMLog *log);
/**
* Redo one #BMLogEntry.
*
* Has no effect if there's nothing left to redo.
*/
void BM_log_redo(BMesh *bm, BMLog *log);
/**
* Log a vertex before it is modified.
*
* Before modifying vertex coordinates, masks, or hflags, call this
* function to log its current values. This is better than logging
* after the coordinates have been modified, because only those
* vertices that are modified need to have their original values
* stored.
*
* Handles two separate cases:
*
* If the vertex was added in the current log entry, update the
* vertex in the map of added vertices.
*
* If the vertex already existed prior to the current log entry, a
* separate key/value map of modified vertices is used (using the
* vertex's ID as the key). The values stored in that case are
* the vertex's original state so that an undo can restore the
* previous state.
*
* On undo, the current vertex state will be swapped with the stored
* state so that a subsequent redo operation will restore the newer
* vertex state.
*/
void BM_log_vert_before_modified(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a new vertex as added to the #BMesh.
*
* The new vertex gets a unique ID assigned. It is then added to a map
* of added vertices, with the key being its ID and the value
* containing everything needed to reconstruct that vertex.
*/
void BM_log_vert_added(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a face before it is modified.
*
* This is intended to handle only header flags and we always
* assume face has been added before.
*/
void BM_log_face_modified(BMLog *log, BMFace *f);
/**
* Log a new face as added to the #BMesh.
*
* The new face gets a unique ID assigned. It is then added to a map
* of added faces, with the key being its ID and the value containing
* everything needed to reconstruct that face.
*/
void BM_log_face_added(BMLog *log, BMFace *f);
/**
* Log a vertex as removed from the #BMesh.
*
* A couple things can happen here:
*
* If the vertex was added as part of the current log entry, then it's
* deleted and forgotten about entirely. Its unique ID is returned to
* the unused pool.
*
* If the vertex was already part of the #BMesh before the current log
* entry, it is added to a map of deleted vertices, with the key being
* its ID and the value containing everything needed to reconstruct
* that vertex.
*
* If there's a move record for the vertex, that's used as the
* vertices original location, then the move record is deleted.
*/
void BM_log_vert_removed(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a face as removed from the #BMesh.
*
* A couple things can happen here:
*
* If the face was added as part of the current log entry, then it's
* deleted and forgotten about entirely. Its unique ID is returned to
* the unused pool.
*
* If the face was already part of the #BMesh before the current log
* entry, it is added to a map of deleted faces, with the key being
* its ID and the value containing everything needed to reconstruct
* that face.
*/
void BM_log_face_removed(BMLog *log, BMFace *f);
/**
* Log all vertices/faces in the #BMesh as added.
*/
void BM_log_all_added(BMesh *bm, BMLog *log);
/** Log all vertices/faces in the #BMesh as removed. */
void BM_log_before_all_removed(BMesh *bm, BMLog *log);
/**
* Search the log for the original vertex coordinates.
*
* Does not modify the log or the vertex.
*
* \return the pointer or nullptr if the vertex isn't found.
*/
const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v);
/**
* Search the log for the original vertex mask.
*
* Does not modify the log or the vertex.
*
* \return the pointer or nullptr if the vertex isn't found.
*/
const float *BM_log_find_original_vert_mask(BMLog *log, BMVert *v);
/** Get the logged data of a vertex (avoid multiple lookups). */
void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no);
#ifndef NDEBUG
/** For internal use only (unit testing). */
BMLogEntry *BM_log_current_entry(BMLog *log);
/** For internal use only (unit testing) */
struct RangeTreeUInt *BM_log_unused_ids(BMLog *log);
void BM_log_print(const BMLog *log, const char *description);
void BM_log_print_entry(BMesh *bm, BMLogEntry *entry);
#endif
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "bmesh_class.hh"
namespace blender {
struct BMEditSelection {
struct BMEditSelection *next, *prev;
BMElem *ele;
char htype;
};
enum class BMSelectFlushFlag : uint8_t {
None = 0,
RecalcLenVert = (1 << 0),
RecalcLenEdge = (1 << 1),
RecalcLenFace = (1 << 2),
/**
* Flush selection down, depending on the selection mode.
*
* Disabled by default as edge & face selection functions flush down:
* (functions #BM_edge_select_set & #BM_face_select_set).
* However selection logic needs to take care to perform de-selection *before* selection,
* otherwise flushing down *is* needed.
*/
Down = (1 << 3),
};
ENUM_OPERATORS(BMSelectFlushFlag)
#define BMSelectFlushFlag_All \
(BMSelectFlushFlag::RecalcLenVert | BMSelectFlushFlag::RecalcLenEdge | \
BMSelectFlushFlag::RecalcLenFace)
#define BMSelectFlushFlag_Default BMSelectFlushFlag_All
/* Geometry hiding code. */
#define BM_elem_hide_set(bm, ele, hide) _bm_elem_hide_set(bm, &(ele)->head, hide)
void _bm_elem_hide_set(BMesh *bm, BMHeader *head, bool hide);
void BM_vert_hide_set(BMVert *v, bool hide);
void BM_edge_hide_set(BMEdge *e, bool hide);
void BM_face_hide_set(BMFace *f, bool hide);
/* Selection code. */
/**
* \note use BM_elem_flag_test(ele, BM_ELEM_SELECT) to test selection
* \note by design, this will not touch the editselection history stuff
*/
void BM_elem_select_set(BMesh *bm, BMElem *ele, bool select);
void BM_mesh_elem_hflag_enable_test(
BMesh *bm, char htype, char hflag, bool respecthide, bool overwrite, char hflag_test);
void BM_mesh_elem_hflag_disable_test(
BMesh *bm, char htype, char hflag, bool respecthide, bool overwrite, char hflag_test);
void BM_mesh_elem_hflag_enable_all(BMesh *bm, char htype, char hflag, bool respecthide);
void BM_mesh_elem_hflag_disable_all(BMesh *bm, char htype, char hflag, bool respecthide);
/* Individual element select functions, #BM_elem_select_set is a shortcut for these
* that automatically detects which one to use. */
/**
* \brief Select Vert
*
* Changes selection state of a single vertex
* in a mesh
*/
void BM_vert_select_set(BMesh *bm, BMVert *v, bool select);
/**
* \brief Select Edge
*
* Changes selection state of a single edge in a mesh.
*/
void BM_edge_select_set(BMesh *bm, BMEdge *e, bool select);
/**
* \brief Select Face
*
* Changes selection state of a single
* face in a mesh.
*/
void BM_face_select_set(BMesh *bm, BMFace *f, bool select);
/* Lower level functions which don't do flushing. */
void BM_edge_select_set_noflush(BMesh *bm, BMEdge *e, bool select);
void BM_face_select_set_noflush(BMesh *bm, BMFace *f, bool select);
/**
* Return true when there are a mix of selected/unselected elements.
*/
bool BM_mesh_select_is_mixed(const BMesh *bm);
/**
* \brief Select Mode Clean
*
* Remove isolated selected elements when in a mode doesn't support them.
* eg: in edge-mode a selected vertex must be connected to a selected edge.
*
* \note this could be made a part of #BM_mesh_select_mode_flush_ex
*/
void BM_mesh_select_mode_clean_ex(BMesh *bm, short selectmode);
void BM_mesh_select_mode_clean(BMesh *bm);
/**
* Select Mode Set
*
* Sets the selection mode for the bmesh,
* updating the selection state.
*/
void BM_mesh_select_mode_set(BMesh *bm, int selectmode);
/**
* \brief Select Mode Flush
*
* Makes sure to flush selections 'upwards'
* (ie: all verts of an edge selects the edge and so on).
* This should only be called by system and not tool authors.
*
* \note Flushing down can be enabled for edge/face modes
* by enabling #BMSelectFlushFlag:Down for `flag`.
*/
void BM_mesh_select_mode_flush_ex(BMesh *bm, short selectmode, BMSelectFlushFlag flag);
void BM_mesh_select_mode_flush(BMesh *bm);
/**
* Mode independent selection/de-selection flush from vertices.
*
* \param select: When true, flush the selection state to de-selected elements,
* otherwise perform the opposite, flushing de-selection.
*/
void BM_mesh_select_flush_from_verts(BMesh *bm, bool select);
int BM_mesh_elem_hflag_count_enabled(BMesh *bm, char htype, char hflag, bool respecthide);
int BM_mesh_elem_hflag_count_disabled(BMesh *bm, char htype, char hflag, bool respecthide);
/* Edit selection stuff. */
void BM_mesh_active_face_set(BMesh *bm, BMFace *f);
int BM_mesh_active_face_index_get(BMesh *bm, bool is_sloppy, bool is_selected);
int BM_mesh_active_edge_index_get(BMesh *bm);
int BM_mesh_active_vert_index_get(BMesh *bm);
BMFace *BM_mesh_active_face_get(BMesh *bm, bool is_sloppy, bool is_selected);
BMEdge *BM_mesh_active_edge_get(BMesh *bm);
BMVert *BM_mesh_active_vert_get(BMesh *bm);
BMElem *BM_mesh_active_elem_get(BMesh *bm);
/**
* Generic way to get data from an #BMEditSelection type
* These functions were written to be used by the Modifier widget
* when in Rotate about active mode, but can be used anywhere.
*
* - #BM_editselection_center
* - #BM_editselection_normal
* - #BM_editselection_plane
*/
void BM_editselection_center(BMEditSelection *ese, float r_center[3]);
void BM_editselection_normal(BMEditSelection *ese, float r_normal[3]);
/**
* Calculate a plane that is right angles to the edge/vert/faces normal
* also make the plane run along an axis that is related to the geometry,
* because this is used for the gizmos Y axis.
*/
void BM_editselection_plane(BMEditSelection *ese, float r_plane[3]);
#define BM_select_history_check(bm, ele) _bm_select_history_check(bm, &(ele)->head)
#define BM_select_history_remove(bm, ele) _bm_select_history_remove(bm, &(ele)->head)
#define BM_select_history_store_notest(bm, ele) _bm_select_history_store_notest(bm, &(ele)->head)
#define BM_select_history_store(bm, ele) _bm_select_history_store(bm, &(ele)->head)
#define BM_select_history_store_head_notest(bm, ele) \
_bm_select_history_store_head_notest(bm, &(ele)->head)
#define BM_select_history_store_head(bm, ele) _bm_select_history_store_head(bm, &(ele)->head)
#define BM_select_history_store_after_notest(bm, ese_ref, ele) \
_bm_select_history_store_after_notest(bm, ese_ref, &(ele)->head)
#define BM_select_history_store_after(bm, ese, ese_ref) \
_bm_select_history_store_after(bm, ese_ref, &(ele)->head)
bool _bm_select_history_check(BMesh *bm, const BMHeader *ele);
bool _bm_select_history_remove(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_notest(BMesh *bm, BMHeader *ele);
void _bm_select_history_store(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_head_notest(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_head(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_after(BMesh *bm, BMEditSelection *ese_ref, BMHeader *ele);
void _bm_select_history_store_after_notest(BMesh *bm, BMEditSelection *ese_ref, BMHeader *ele);
void BM_select_history_validate(BMesh *bm);
void BM_select_history_clear(BMesh *bm);
/**
* Get all element types present in a selection history.
*/
[[nodiscard]] char BM_select_history_htype_all(const BMesh *bm);
/**
* Get the active mesh element (with active-face fallback).
*/
bool BM_select_history_active_get(BMesh *bm, struct BMEditSelection *ese);
/**
* Return a map from #BMVert/#BMEdge/#BMFace -> #BMEditSelection.
*/
struct GHash *BM_select_history_map_create(BMesh *bm);
/**
* Map arguments may all be the same pointer.
*/
void BM_select_history_merge_from_targetmap(BMesh *bm,
Map<void *, void *> *vert_map,
Map<void *, void *> *edge_map,
Map<void *, void *> *face_map,
bool use_chain);
#define BM_SELECT_HISTORY_BACKUP(bm) \
{ \
ListBaseT<BMEditSelection> _bm_prev_selected = (bm)->selected; \
BLI_listbase_clear(&(bm)->selected)
#define BM_SELECT_HISTORY_RESTORE(bm) \
(bm)->selected = _bm_prev_selected; \
} \
(void)0
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_array.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
namespace blender {
struct BMAllocTemplate;
void BM_mesh_elem_toolflags_ensure(BMesh *bm);
void BM_mesh_elem_toolflags_clear(BMesh *bm);
struct BMeshCreateParams {
bool use_toolflags : 1;
};
/**
* \brief BMesh Make Mesh
*
* Allocates a new BMesh structure.
*
* \return The New bmesh
*
* \note ob is needed by multires
*/
BMesh *BM_mesh_create(const BMAllocTemplate *allocsize, const BMeshCreateParams *params);
/**
* \brief BMesh Free Mesh
*
* Frees a BMesh data and its structure.
*/
void BM_mesh_free(BMesh *bm);
/**
* \brief BMesh Free Mesh Data
*
* Frees a BMesh structure.
*
* \note frees mesh, but not actual BMesh struct
*/
void BM_mesh_data_free(BMesh *bm);
/**
* \brief BMesh Clear Mesh
*
* Clear all data in bm
*/
void BM_mesh_clear(BMesh *bm);
/**
* \brief BMesh Begin Edit
*
* Functions for setting up a mesh for editing and cleaning up after
* the editing operations are done. These are called by the tools/operator
* API for each time a tool is executed.
*/
void bmesh_edit_begin(BMesh *bm, BMOpTypeFlag type_flag);
/**
* \brief BMesh End Edit
*/
void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag);
void BM_mesh_elem_index_ensure_ex(BMesh *bm, char htype, int elem_offset[4]);
void BM_mesh_elem_index_ensure(BMesh *bm, char htype);
/**
* Array checking/setting macros.
*
* Currently vert/edge/loop/face index data is being abused, in a few areas of the code.
*
* To avoid correcting them afterwards, set 'bm->elem_index_dirty' however its possible
* this flag is set incorrectly which could crash blender.
*
* Functions that calls this function may depend on dirty indices on being set.
*
* This is read-only, so it can be used for assertions that don't impact behavior.
*/
void BM_mesh_elem_index_validate(
BMesh *bm, const char *location, const char *func, const char *msg_a, const char *msg_b);
#ifndef NDEBUG
/**
* \see #BM_mesh_elem_index_validate the same rationale applies to this function.
*/
bool BM_mesh_elem_table_check(BMesh *bm);
#endif
/**
* Re-allocates mesh data with/without toolflags.
*/
void BM_mesh_toolflags_set(BMesh *bm, bool use_toolflags);
void BM_mesh_elem_table_ensure(BMesh *bm, char htype);
/* use BM_mesh_elem_table_ensure where possible to avoid full rebuild */
void BM_mesh_elem_table_init(BMesh *bm, char htype);
void BM_mesh_elem_table_free(BMesh *bm, char htype);
BLI_INLINE BMVert *BM_vert_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totvert));
BLI_assert((bm->elem_table_dirty & BM_VERT) == 0);
return bm->vtable[index];
}
BLI_INLINE BMEdge *BM_edge_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totedge));
BLI_assert((bm->elem_table_dirty & BM_EDGE) == 0);
return bm->etable[index];
}
BLI_INLINE BMFace *BM_face_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totface));
BLI_assert((bm->elem_table_dirty & BM_FACE) == 0);
return bm->ftable[index];
}
BMVert *BM_vert_at_index_find(BMesh *bm, int index);
BMEdge *BM_edge_at_index_find(BMesh *bm, int index);
BMFace *BM_face_at_index_find(BMesh *bm, int index);
BMLoop *BM_loop_at_index_find(BMesh *bm, int index);
/**
* Use lookup table when available, else use slower find functions.
*
* \note Try to use #BM_mesh_elem_table_ensure instead.
*/
BMVert *BM_vert_at_index_find_or_table(BMesh *bm, int index);
BMEdge *BM_edge_at_index_find_or_table(BMesh *bm, int index);
BMFace *BM_face_at_index_find_or_table(BMesh *bm, int index);
// XXX
/**
* Return the amount of element of type 'type' in a given bmesh.
*/
int BM_mesh_elem_count(BMesh *bm, char htype);
/**
* Remaps the vertices, edges and/or faces of the bmesh as indicated by vert/edge/face_idx arrays
* (xxx_idx[org_index] = new_index).
*
* A NULL array means no changes.
*
* \note
* - Does not mess with indices, just sets elem_index_dirty flag.
* - For verts/edges/faces only (as loops must remain "ordered" and "aligned"
* on a per-face basis...).
*
* \warning Be careful if you keep pointers to affected BM elements,
* or arrays, when using this func!
*/
void BM_mesh_remap(BMesh *bm, const uint *vert_idx, const uint *edge_idx, const uint *face_idx);
/**
* Use new memory pools for this mesh.
*
* \note needed for re-sizing elements (adding/removing tool flags)
* but could also be used for packing fragmented bmeshes.
*/
void BM_mesh_rebuild(BMesh *bm,
const BMeshCreateParams *params,
BLI_mempool *vpool,
BLI_mempool *epool,
BLI_mempool *lpool,
BLI_mempool *fpool);
struct BMAllocTemplate {
int totvert, totedge, totloop, totface;
};
/* used as an extern, defined in bmesh.h */
extern const BMAllocTemplate bm_mesh_allocsize_default;
extern const BMAllocTemplate bm_mesh_chunksize_default;
#define BMALLOC_TEMPLATE_FROM_BM(bm) \
{(CHECK_TYPE_INLINE(bm, BMesh *), (bm)->totvert), (bm)->totedge, (bm)->totloop, (bm)->totface}
#define _VA_BMALLOC_TEMPLATE_FROM_ME_1(me) \
{ \
(CHECK_TYPE_INLINE(me, Mesh *), (me)->verts_num), \
(me)->edges_num, \
(me)->corners_num, \
(me)->faces_num, \
}
#define _VA_BMALLOC_TEMPLATE_FROM_ME_2(me_a, me_b) \
{ \
(CHECK_TYPE_INLINE(me_a, Mesh *), \
CHECK_TYPE_INLINE(me_b, Mesh *), \
(me_a)->verts_num + (me_b)->verts_num), \
(me_a)->edges_num + (me_b)->edges_num, \
(me_a)->corners_num + (me_b)->corners_num, \
(me_a)->faces_num + (me_b)->faces_num, \
}
#define BMALLOC_TEMPLATE_FROM_ME(...) \
VA_NARGS_CALL_OVERLOAD(_VA_BMALLOC_TEMPLATE_FROM_ME_, __VA_ARGS__)
void BM_mesh_vert_normals_get(BMesh *bm, MutableSpan<float3> normals);
/* Vertex coords access. */
void BM_mesh_vert_coords_get(BMesh *bm, MutableSpan<float3> positions);
Array<float3> BM_mesh_vert_coords_alloc(BMesh *bm);
void BM_mesh_vert_coords_apply(BMesh *bm, Span<float3> vert_coords);
void BM_mesh_vert_coords_apply_with_mat4(BMesh *bm,
Span<float3> vert_coords,
const float4x4 &transform);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_string_ref.hh"
#include "bmesh.hh"
namespace blender {
/**
* \return Whether attributes with the given name are stored in special flags or fields in BMesh
* rather than in the regular custom data blocks.
*/
bool BM_attribute_stored_in_bmesh_builtin(StringRef name);
struct CustomData_MeshMasks;
struct Main;
struct Mesh;
struct BMeshFromMeshParams {
bool calc_face_normal;
bool calc_vert_normal;
/* add a vertex CD_SHAPE_KEYINDEX layer */
bool add_key_index;
/* set vertex coordinates from the shapekey */
bool use_shapekey;
/* define the active shape key (index + 1) */
int active_shapekey;
struct CustomData_MeshMasks cd_mask_extra;
};
/**
* \brief Mesh -> BMesh
* \param bm: The mesh to write into, while this is typically a newly created BMesh,
* merging into existing data is supported.
* Note the custom-data layout isn't used.
* If more comprehensive merging is needed we should move this into a separate function
* since this should be kept fast for edit-mode switching and storing undo steps.
*
* \warning This function doesn't calculate face normals.
*/
void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *params)
ATTR_NONNULL(1, 3);
struct BMeshToMeshParams {
/** Update object hook indices & vertex parents. */
bool calc_object_remap;
/**
* This re-assigns shape-key indices. Only do if the BMesh will have continued use
* to update the mesh & shape key in the future.
* In the case the BMesh is freed immediately, this can be left false.
*
* This is needed when flushing changes from edit-mode into object mode,
* so a second flush or edit-mode exit doesn't run with indices
* that have become invalid from updating the shape-key, see #71865.
*/
bool update_shapekey_indices;
/**
* Instead of copying the basis shape-key into the position array,
* copy the #BMVert.co directly to the #Mesh position (used for reading undo data).
*/
bool active_shapekey_to_mvert;
struct CustomData_MeshMasks cd_mask_extra;
};
/**
* \param bmain: May be NULL in case \a calc_object_remap parameter option is not set.
*/
void BM_mesh_bm_to_me(struct Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParams *params)
ATTR_NONNULL(2, 3, 4);
/**
* A version of #BM_mesh_bm_to_me intended for getting the mesh
* to pass to the modifier stack for evaluation,
* instead of mode switching (where we make sure all data is kept
* and do expensive lookups to maintain shape keys).
*
* Key differences:
*
* - Don't support merging with existing mesh.
* - Ignore shape-keys.
* - Ignore vertex-parents.
* - Ignore selection history.
* - Uses #CD_MASK_DERIVEDMESH instead of #CD_MASK_MESH.
*
* \note Was `cddm_from_bmesh_ex` in 2.7x, removed `MFace` support.
*/
void BM_mesh_bm_to_me_for_eval(BMesh &bm, Mesh &mesh, const CustomData_MeshMasks *cd_mask_extra);
/**
* A version of #BM_mesh_bm_to_me_for_eval but copying data layers and Mesh attributes is optional.
* It also allows shape-keys but don't re-assigns shape-key indices.
*
* \param mask: Custom data masks to control which layers are copied.
* If nullptr, no layer data is copied.
* \param add_mesh_attributes: If true, adds mesh attributes during the conversion.
*/
void BM_mesh_bm_to_me_compact(BMesh &bm,
Mesh &mesh,
const CustomData_MeshMasks *mask,
bool add_mesh_attributes);
} // namespace blender

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* Evaluated mesh info printing function, to help track down differences output.
*
* Output from these functions can be evaluated as Python literals.
* See `mesh_debug.cc` for the equivalent #Mesh functionality.
*/
#ifndef NDEBUG
# include <cstdio>
# include "MEM_guardedalloc.h"
# include "BKE_customdata.hh"
# include "bmesh.hh"
# include "bmesh_mesh_debug.hh"
# include "BLI_dynstr.h"
namespace blender {
char *BM_mesh_debug_info(BMesh *bm)
{
DynStr *dynstr = BLI_dynstr_new();
char *ret;
const char *indent8 = " ";
BLI_dynstr_append(dynstr, "{\n");
BLI_dynstr_appendf(dynstr, " 'ptr': '%p',\n", static_cast<void *>(bm));
BLI_dynstr_appendf(dynstr, " 'totvert': %d,\n", bm->totvert);
BLI_dynstr_appendf(dynstr, " 'totedge': %d,\n", bm->totedge);
BLI_dynstr_appendf(dynstr, " 'totface': %d,\n", bm->totface);
BLI_dynstr_append(dynstr, " 'vert_layers': (\n");
CustomData_debug_info_from_layers(&bm->vdata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'edge_layers': (\n");
CustomData_debug_info_from_layers(&bm->edata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'loop_layers': (\n");
CustomData_debug_info_from_layers(&bm->ldata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'poly_layers': (\n");
CustomData_debug_info_from_layers(&bm->pdata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, "}\n");
ret = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
return ret;
}
void BM_mesh_debug_print(BMesh *bm)
{
char *str = BM_mesh_debug_info(bm);
puts(str);
fflush(stdout);
MEM_delete(str);
}
} // namespace blender
#endif /* !NDEBUG */

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_compiler_attrs.h"
#include "bmesh.hh"
namespace blender {
#ifndef NDEBUG
char *BM_mesh_debug_info(BMesh *bm) ATTR_NONNULL(1) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
void BM_mesh_debug_print(BMesh *bm) ATTR_NONNULL(1);
#endif /* !NDEBUG */
} // namespace blender

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Duplicate geometry from one mesh from another.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "bmesh.hh"
namespace blender {
static BMVert *bm_vert_copy(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_vert_map,
BMVert *v_src)
{
BMVert *v_dst = BM_vert_create(bm_dst, v_src->co, nullptr, BM_CREATE_SKIP_CD);
if (cd_vert_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_vert_map.value(), v_src, v_dst);
}
else {
BM_elem_attrs_copy(bm_dst, v_src, v_dst);
}
return v_dst;
}
static BMEdge *bm_edge_copy_with_arrays(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_edge_map,
BMEdge *e_src,
BMVert **verts_dst)
{
BMVert *e_dst_v1 = verts_dst[BM_elem_index_get(e_src->v1)];
BMVert *e_dst_v2 = verts_dst[BM_elem_index_get(e_src->v2)];
BMEdge *e_dst = BM_edge_create(bm_dst, e_dst_v1, e_dst_v2, nullptr, BM_CREATE_SKIP_CD);
if (cd_edge_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_edge_map.value(), e_src, e_dst);
}
else {
BM_elem_attrs_copy(bm_dst, e_src, e_dst);
}
return e_dst;
}
static BMFace *bm_face_copy_with_arrays(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> cd_face_map,
const std::optional<BMCustomDataCopyMap> &cd_loop_map,
BMFace *f_src,
BMVert **verts_dst,
BMEdge **edges_dst)
{
BMFace *f_dst;
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vtar(f_src->len);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edar(f_src->len);
BMLoop *l_iter_src, *l_iter_dst, *l_first_src;
int i;
l_first_src = BM_FACE_FIRST_LOOP(f_src);
/* Lookup verts & edges. */
l_iter_src = l_first_src;
i = 0;
do {
vtar[i] = verts_dst[BM_elem_index_get(l_iter_src->v)];
edar[i] = edges_dst[BM_elem_index_get(l_iter_src->e)];
i++;
} while ((l_iter_src = l_iter_src->next) != l_first_src);
/* Create new face. */
f_dst = BM_face_create(bm_dst, vtar.data(), edar.data(), f_src->len, nullptr, BM_CREATE_SKIP_CD);
/* Copy attributes. */
if (cd_face_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_face_map.value(), f_src, f_dst);
}
else {
BM_elem_attrs_copy(bm_dst, f_src, f_dst);
}
/* Copy per-loop custom data. */
l_iter_src = l_first_src;
l_iter_dst = BM_FACE_FIRST_LOOP(f_dst);
do {
if (cd_loop_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_loop_map.value(), l_iter_src, l_iter_dst);
}
else {
BM_elem_attrs_copy(bm_dst, l_iter_src, l_iter_dst);
}
} while ((void)(l_iter_dst = l_iter_dst->next), (l_iter_src = l_iter_src->next) != l_first_src);
return f_dst;
}
void BM_mesh_copy_arrays(BMesh *bm_src,
BMesh *bm_dst,
BMVert **verts_src,
uint verts_src_len,
BMEdge **edges_src,
uint edges_src_len,
BMFace **faces_src,
uint faces_src_len)
{
const std::optional<BMCustomDataCopyMap> cd_vert_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->vdata, bm_dst->vdata)};
const std::optional<BMCustomDataCopyMap> cd_edge_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->edata, bm_dst->edata)};
const std::optional<BMCustomDataCopyMap> cd_face_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->pdata, bm_dst->pdata)};
const std::optional<BMCustomDataCopyMap> cd_loop_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->ldata, bm_dst->ldata)};
/* Vertices. */
BMVert **verts_dst = MEM_new_array_uninitialized<BMVert *>(verts_src_len, __func__);
for (uint i = 0; i < verts_src_len; i++) {
BMVert *v_src = verts_src[i];
BM_elem_index_set(v_src, i); /* set_dirty! */
BMVert *v_dst = bm_vert_copy(bm_dst, cd_vert_map, v_src);
BM_elem_index_set(v_dst, i); /* set_ok */
verts_dst[i] = v_dst;
}
bm_src->elem_index_dirty |= BM_VERT;
bm_dst->elem_index_dirty &= ~BM_VERT;
/* Edges. */
BMEdge **edges_dst = MEM_new_array_uninitialized<BMEdge *>(edges_src_len, __func__);
for (uint i = 0; i < edges_src_len; i++) {
BMEdge *e_src = edges_src[i];
BM_elem_index_set(e_src, i); /* set_dirty! */
BMEdge *e_dst = bm_edge_copy_with_arrays(bm_dst, cd_edge_map, e_src, verts_dst);
BM_elem_index_set(e_dst, i);
edges_dst[i] = e_dst;
}
bm_src->elem_index_dirty |= BM_EDGE;
bm_dst->elem_index_dirty &= ~BM_EDGE;
/* Faces. */
for (uint i = 0; i < faces_src_len; i++) {
BMFace *f_src = faces_src[i];
BMFace *f_dst = bm_face_copy_with_arrays(
bm_dst, cd_face_map, cd_loop_map, f_src, verts_dst, edges_dst);
BM_elem_index_set(f_dst, i);
}
bm_dst->elem_index_dirty &= ~BM_FACE;
/* Cleanup. */
MEM_delete(verts_dst);
MEM_delete(edges_dst);
}
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Geometry must be completely isolated.
*/
void BM_mesh_copy_arrays(BMesh *bm_src,
BMesh *bm_dst,
BMVert **verts_src,
uint verts_src_len,
BMEdge **edges_src,
uint edges_src_len,
BMFace **faces_src,
uint faces_src_len);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
namespace blender {
struct BMPartialUpdate;
struct BMeshNormalsUpdate_Params {
/**
* When calculating tessellation as well as normals, tessellate & calculate face normals
* for improved performance. See #BMeshCalcTessellation_Params
*/
bool face_normals;
};
/**
* \brief BMesh Compute Normals
*
* Updates the normals of a mesh.
*/
void BM_mesh_normals_update_ex(BMesh *bm, const BMeshNormalsUpdate_Params *param);
void BM_mesh_normals_update(BMesh *bm);
/**
* A version of #BM_mesh_normals_update that updates a subset of geometry,
* used to avoid the overhead of updating everything.
*/
void BM_mesh_normals_update_with_partial_ex(BMesh *bm,
const BMPartialUpdate *bmpinfo,
const BMeshNormalsUpdate_Params *param);
void BM_mesh_normals_update_with_partial(BMesh *bm, const BMPartialUpdate *bmpinfo);
/**
* \brief BMesh Compute Normals from/to external data.
*
* Computes the vertex normals of a mesh into vnos,
* using given vertex coordinates (vcos) and polygon normals (fnos).
*/
void BM_verts_calc_normal_vcos(BMesh *bm,
Span<float3> fnos,
Span<float3> vcos,
MutableSpan<float3> vnos);
/**
* \brief BMesh Compute Loop Normals from/to external data.
*
* Compute custom normals, i.e. vertex normals associated with each poly (hence 'loop normals').
* Useful to materialize sharp edges (or non-smooth faces) without actually modifying the geometry
* (splitting edges).
*/
void BM_loops_calc_normal_vcos(BMesh *bm,
Span<float3> vcos,
Span<float3> vnos,
Span<float3> fnos,
bool use_split_normals,
MutableSpan<float3> r_lnos,
MLoopNorSpaceArray *r_lnors_spacearr,
short (*clnors_data)[2],
int cd_loop_clnors_offset,
bool do_rebuild);
/**
* Check whether given loop is part of an unknown-so-far cyclic smooth fan, or not.
* Needed because cyclic smooth fans have no obvious 'entry point',
* and yet we need to walk them once, and only once.
*/
bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr);
void BM_lnorspacearr_store(BMesh *bm, MutableSpan<float3> r_lnors);
void BM_lnorspace_invalidate(BMesh *bm, bool do_invalidate_all);
void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor);
/**
* \warning This function sets #BM_ELEM_TAG on loops & edges via #bm_mesh_loops_calc_normals,
* take care to run this before setting up tags.
*/
void BM_lnorspace_update(BMesh *bm);
void BM_normals_loops_edges_tag(BMesh *bm, bool do_edges);
#ifndef NDEBUG
void BM_lnorspace_err(BMesh *bm);
#endif
/* Loop Generics */
/**
* Initialize loop data based on a type, overriding the #BMesh::selectmode of `bm`.
* This can be useful if a single types selection is preferred,
* instead of using mixed modes and the selection history.
*/
BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init_with_htype(BMesh *bm,
bool do_all_loops_of_vert,
char htype_override);
BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm, bool do_all_loops_of_vert);
void BM_loop_normal_editdata_array_free(BMLoopNorEditDataArray *lnors_ed_arr);
/**
* \warning This function sets #BM_ELEM_TAG on loops & edges via #bm_mesh_loops_calc_normals,
* take care to run this before setting up tags.
*/
bool BM_custom_loop_normals_to_vector_layer(BMesh *bm);
void BM_custom_loop_normals_from_vector_layer(BMesh *bm, bool add_sharp_edges);
/**
* Define sharp edges as needed to mimic auto-smooth from angle threshold.
*
* Used when defining an empty custom loop normals data layer,
* to keep same shading as with auto-smooth!
*/
void BM_edges_sharp_from_angle_set(BMesh *bm, float split_angle);
} // namespace blender

View File

@@ -0,0 +1,321 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Generate data needed for partially updating mesh information.
* Currently this is used for normals and tessellation.
*
* Transform is the obvious use case where there is no need to update normals or tessellation
* for geometry which has not been modified.
*
* In the future this could be integrated into GPU updates too.
*
* Kinds of Partial Geometry
* =========================
*
* All Tagged
* ----------
* Operate on everything that's tagged as well as connected geometry.
* see: #BM_mesh_partial_create_from_verts
*
* Grouped
* -------
* Operate on everything that is connected to both tagged and un-tagged.
* see: #BM_mesh_partial_create_from_verts_group_single
*
* Reduces computations when transforming isolated regions.
*
* Optionally support multiple groups since axis-mirror (for example)
* will transform vertices in different directions, as well as keeping centered vertices.
* see: #BM_mesh_partial_create_from_verts_group_multi
*
* \note Others can be added as needed.
*/
#include "MEM_guardedalloc.h"
#include "BLI_bit_vector.hh"
#include "BLI_math_base.h"
#include "bmesh.hh"
namespace blender {
BLI_INLINE bool partial_elem_vert_ensure(BMPartialUpdate *bmpinfo,
MutableBitSpan verts_tag,
BMVert *v)
{
const int i = BM_elem_index_get(v);
if (!verts_tag[i]) {
verts_tag[i].set();
bmpinfo->verts.append(v);
return true;
}
return false;
}
BLI_INLINE bool partial_elem_face_ensure(BMPartialUpdate *bmpinfo,
MutableBitSpan faces_tag,
BMFace *f)
{
const int i = BM_elem_index_get(f);
if (!faces_tag[i]) {
faces_tag[i].set();
bmpinfo->faces.append(f);
return true;
}
return false;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts(BMesh &bm,
const BMPartialUpdate_Params &params,
const BitSpan verts_mask,
const int verts_mask_count)
{
/* The caller is doing something wrong if this isn't the case. */
BLI_assert(verts_mask_count <= bm.totvert);
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
/* Reserve more edges than vertices since it's common for a grid topology
* to use around twice as many edges as vertices. */
const int default_verts_len_alloc = verts_mask_count;
const int default_faces_len_alloc = min_ii(bm.totface, verts_mask_count);
/* Allocate tags instead of using #BM_ELEM_TAG because the caller may already be using tags.
* Further, walking over all geometry to clear the tags isn't so efficient. */
BitVector<> verts_tag;
BitVector<> faces_tag;
/* Set vert inline. */
BM_mesh_elem_index_ensure(&bm, BM_FACE);
if (params.do_normals || params.do_tessellate) {
/* - Extend to all vertices connected faces:
* In the case of tessellation this is enough.
*
* In the case of vertex normal calculation,
* All the relevant connectivity data can be accessed from the faces
* (there is no advantage in storing connected edges or vertices in this pass).
*
* NOTE: In the future it may be useful to differentiate between vertices
* that are directly marked (by the filter function when looping over all vertices).
* And vertices marked from indirect connections.
* This would require an extra tag array, so avoid this unless it's needed.
*/
/* Faces. */
bmpinfo->faces.reserve(default_faces_len_alloc);
faces_tag.resize(bm.totface);
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
BM_elem_index_set(v, i); /* set_inline */
if (!verts_mask[i]) {
continue;
}
BMEdge *e_iter = v->e;
if (e_iter != nullptr) {
/* Loop over edges. */
BMEdge *e_first = v->e;
do {
BMLoop *l_iter = e_iter->l;
if (e_iter->l != nullptr) {
BMLoop *l_first = e_iter->l;
/* Loop over radial loops. */
do {
if (l_iter->v == v) {
partial_elem_face_ensure(bmpinfo, faces_tag, l_iter->f);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != e_first);
}
}
}
if (params.do_normals) {
/* - Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices.
*
* - Extend to all these vertices connected edges:
* These and needed to access those vertices edge vectors in normal calculation logic.
*/
/* Vertices. */
bmpinfo->verts.reserve(default_verts_len_alloc);
verts_tag.resize(bm.totvert);
for (const BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
}
bmpinfo->params = params;
return bmpinfo;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts_group_single(
BMesh &bm,
const BMPartialUpdate_Params &params,
const BitSpan verts_mask,
const int verts_mask_count)
{
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
BitVector<> verts_tag;
BitVector<> faces_tag;
int face_tag_loop_len = 0;
if (params.do_normals || params.do_tessellate) {
faces_tag.resize(bm.totface);
BMFace *f;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (f, &iter, &bm, BM_FACES_OF_MESH, i) {
enum Side { SIDE_A = (1 << 0), SIDE_B = (1 << 1) } side_flag = Side(0);
BM_elem_index_set(f, i); /* set_inline */
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const int j = BM_elem_index_get(l_iter->v);
side_flag = Side(side_flag | (verts_mask[j].test() ? SIDE_A : SIDE_B));
if (UNLIKELY(side_flag == (SIDE_A | SIDE_B))) {
partial_elem_face_ensure(bmpinfo, faces_tag, f);
face_tag_loop_len += f->len;
break;
}
} while ((l_iter = l_iter->next) != l_first);
}
}
if (params.do_normals) {
/* Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices. */
/* Over allocate using the total number of face loops. */
bmpinfo->verts.reserve(min_ii(bm.totvert, max_ii(1, face_tag_loop_len)));
verts_tag.resize(bm.totvert);
for (BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
/* Loose vertex support, these need special handling as loose normals depend on location. */
if (bmpinfo->verts.size() < verts_mask_count) {
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
if (verts_mask[i] && (BM_vert_find_first_loop(v) == nullptr)) {
partial_elem_vert_ensure(bmpinfo, verts_tag, v);
}
}
}
}
bmpinfo->params = params;
return bmpinfo;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts_group_multi(
BMesh &bm,
const BMPartialUpdate_Params &params,
const Span<int> verts_group,
const int verts_group_count)
{
/* Provide a quick way of visualizing which faces are being manipulated. */
// #define DEBUG_MATERIAL
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
BitVector<> verts_tag;
BitVector<> faces_tag;
int face_tag_loop_len = 0;
if (params.do_normals || params.do_tessellate) {
faces_tag.resize(bm.totface);
BMFace *f;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (f, &iter, &bm, BM_FACES_OF_MESH, i) {
BM_elem_index_set(f, i); /* set_inline */
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
const int group_test = verts_group[BM_elem_index_get(l_iter->prev->v)];
#ifdef DEBUG_MATERIAL
f->mat_nr = 0;
#endif
do {
const int group_iter = verts_group[BM_elem_index_get(l_iter->v)];
if (UNLIKELY((group_iter != group_test) || (group_iter == -1))) {
partial_elem_face_ensure(bmpinfo, faces_tag, f);
face_tag_loop_len += f->len;
#ifdef DEBUG_MATERIAL
f->mat_nr = 1;
#endif
break;
}
} while ((l_iter = l_iter->next) != l_first);
}
}
if (params.do_normals) {
/* Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices. */
/* Over allocate using the total number of face loops. */
bmpinfo->verts.reserve(min_ii(bm.totvert, max_ii(1, face_tag_loop_len)));
verts_tag.resize(bm.totvert);
for (BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
/* Loose vertex support, these need special handling as loose normals depend on location. */
if (bmpinfo->verts.size() < verts_group_count) {
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
if ((verts_group[i] != 0) && (BM_vert_find_first_loop(v) == nullptr)) {
partial_elem_vert_ensure(bmpinfo, verts_tag, v);
}
}
}
}
bmpinfo->params = params;
return bmpinfo;
}
void BM_mesh_partial_destroy(BMPartialUpdate *bmpinfo)
{
MEM_delete(bmpinfo);
}
} // namespace blender

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_bit_span.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_vector.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* Parameters used to determine which kinds of data needs to be generated.
*/
struct BMPartialUpdate_Params {
bool do_normals;
bool do_tessellate;
};
/**
* Cached data to speed up partial updates.
*
* Hints:
*
* - Avoid creating this data for single updates,
* it should be created and reused across multiple updates to gain a significant benefit
* (while transforming geometry for example).
*
* - Partial normal updates use face & loop indices,
* setting them to dirty values between updates will slow down normal recalculation.
*/
struct BMPartialUpdate {
Vector<BMVert *> verts;
Vector<BMFace *> faces;
/** Store the parameters used in creation so invalid use can be asserted. */
BMPartialUpdate_Params params = {};
};
/**
* All Tagged & Connected, see: #BM_mesh_partial_create_from_verts
* Operate on everything that's tagged as well as connected geometry.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts(
BMesh &bm, const BMPartialUpdate_Params &params, BitSpan verts_mask, int verts_mask_count);
/**
* All Connected, operate on all faces that have both tagged and un-tagged vertices.
*
* Reduces computations when transforming isolated regions.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts_group_single(
BMesh &bm, const BMPartialUpdate_Params &params, BitSpan verts_mask, int verts_mask_count);
/**
* All Connected, operate on all faces that have vertices in the same group.
*
* Reduces computations when transforming isolated regions.
*
* This is a version of #BM_mesh_partial_create_from_verts_group_single
* that handles multiple groups instead of a bitmap mask.
*
* This is needed for example when transform has mirror enabled,
* since one side needs to have a different group to the other since a face that has vertices
* attached to both won't have an affine transformation.
*
* \param verts_group: Vertex aligned array of groups.
* Values are used as follows:
* - >0: Each face is grouped with other faces of the same group.
* - 0: Not in a group (don't handle these).
* - -1: Don't use grouping logic (include any face that contains a vertex with this group).
* \param verts_group_count: The number of non-zero values in `verts_groups`.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts_group_multi(
BMesh &bm, const BMPartialUpdate_Params &params, Span<int> verts_group, int verts_group_count);
void BM_mesh_partial_destroy(BMPartialUpdate *bmpinfo) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,566 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains code for polygon tessellation
* (creating triangles from polygons).
*
* \see mesh_tessellate.cc for the #Mesh equivalent of this file.
*/
#include "BLI_heap.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_memarena.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_task.h"
#include "bmesh.hh"
namespace blender {
/**
* On systems with 32+ cores,
* only a very small number of faces has any advantage single threading (in the 100's).
* Note that between 500-2000 quads, the difference isn't so much
* (tessellation isn't a bottleneck in this case anyway).
* Avoid the slight overhead of using threads in this case.
*/
#define BM_FACE_TESSELLATE_THREADED_LIMIT 1024
/* -------------------------------------------------------------------- */
/** \name Default Mesh Tessellation
* \{ */
/**
* \param face_normal: This will be optimized out as a constant.
*/
BLI_INLINE void bmesh_calc_tessellation_for_face_impl(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p,
const bool face_normal)
{
#ifndef NDEBUG
/* The face normal is used for projecting faces into 2D space for tessellation.
* Invalid normals may result in invalid tessellation.
* Either `face_normal` should be true or normals should be updated first. */
BLI_assert(face_normal || BM_face_is_normal_valid(efa));
#endif
switch (efa->len) {
case 3: {
/* `0 1 2` -> `0 1 2` */
BMLoop *l;
BMLoop **l_ptr = looptris[0].data();
l_ptr[0] = l = BM_FACE_FIRST_LOOP(efa);
l_ptr[1] = l = l->next;
l_ptr[2] = l->next;
if (face_normal) {
normal_tri_v3(efa->no, l_ptr[0]->v->co, l_ptr[1]->v->co, l_ptr[2]->v->co);
}
break;
}
case 4: {
/* `0 1 2 3` -> (`0 1 2`, `0 2 3`) */
BMLoop *l;
BMLoop **l_ptr_a = looptris[0].data();
BMLoop **l_ptr_b = looptris[1].data();
(l_ptr_a[0] = l_ptr_b[0] = l = BM_FACE_FIRST_LOOP(efa));
(l_ptr_a[1] = l = l->next);
(l_ptr_a[2] = l_ptr_b[1] = l = l->next);
(l_ptr_b[2] = l->next);
if (face_normal) {
normal_quad_v3(
efa->no, l_ptr_a[0]->v->co, l_ptr_a[1]->v->co, l_ptr_a[2]->v->co, l_ptr_b[2]->v->co);
}
if (UNLIKELY(is_quad_flip_v3_first_third_fast(
l_ptr_a[0]->v->co, l_ptr_a[1]->v->co, l_ptr_a[2]->v->co, l_ptr_b[2]->v->co)))
{
/* Flip out of degenerate 0-2 state. */
l_ptr_a[2] = l_ptr_b[2];
l_ptr_b[0] = l_ptr_a[1];
}
break;
}
default: {
if (face_normal) {
BM_face_calc_normal(efa, efa->no);
}
BMLoop *l_iter, *l_first;
BMLoop **l_arr;
float axis_mat[3][3];
float (*projverts)[2];
uint(*tris)[3];
const int tris_len = efa->len - 2;
MemArena *pf_arena = *pf_arena_p;
if (UNLIKELY(pf_arena == nullptr)) {
pf_arena = *pf_arena_p = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
}
tris = static_cast<uint(*)[3]>(BLI_memarena_alloc(pf_arena, sizeof(*tris) * tris_len));
l_arr = static_cast<BMLoop **>(BLI_memarena_alloc(pf_arena, sizeof(*l_arr) * efa->len));
projverts = static_cast<float (*)[2]>(
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * efa->len));
axis_dominant_v3_to_m3_negate(axis_mat, efa->no);
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
l_arr[i] = l_iter;
mul_v2_m3v3(projverts[i], axis_mat, l_iter->v->co);
i++;
} while ((l_iter = l_iter->next) != l_first);
BLI_polyfill_calc_arena(projverts, efa->len, 1, tris, pf_arena);
for (i = 0; i < tris_len; i++) {
BMLoop **l_ptr = looptris[i].data();
uint *tri = tris[i];
l_ptr[0] = l_arr[tri[0]];
l_ptr[1] = l_arr[tri[1]];
l_ptr[2] = l_arr[tri[2]];
}
BLI_memarena_clear(pf_arena);
break;
}
}
}
static void bmesh_calc_tessellation_for_face(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p)
{
bmesh_calc_tessellation_for_face_impl(looptris, efa, pf_arena_p, false);
}
static void bmesh_calc_tessellation_for_face_with_normal(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p)
{
bmesh_calc_tessellation_for_face_impl(looptris, efa, pf_arena_p, true);
}
/**
* \brief BM_mesh_calc_tessellation get the looptris and its number from a certain bmesh
* \param looptris:
*
* \note \a looptris Must be pre-allocated to at least the size of given by: poly_to_tri_count
*/
static void bm_mesh_calc_tessellation__single_threaded(
BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris, const char face_normals)
{
#ifndef NDEBUG
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
#endif
BMIter iter;
BMFace *efa;
int i = 0;
MemArena *pf_arena = nullptr;
if (face_normals) {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
BM_face_calc_normal(efa, efa->no);
bmesh_calc_tessellation_for_face_with_normal(looptris.data() + i, efa, &pf_arena);
i += efa->len - 2;
}
}
else {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
bmesh_calc_tessellation_for_face(looptris.data() + i, efa, &pf_arena);
i += efa->len - 2;
}
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
pf_arena = nullptr;
}
BLI_assert(i <= looptris_tot);
}
struct TessellationUserTLS {
MemArena *pf_arena;
};
static void bmesh_calc_tessellation_for_face_fn(void *__restrict userdata,
MempoolIterData *mp_f,
const TaskParallelTLS *__restrict tls)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls->userdata_chunk);
std::array<BMLoop *, 3> *looptris = static_cast<std::array<BMLoop *, 3> *>(userdata);
BMFace *f = reinterpret_cast<BMFace *>(mp_f);
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(looptris + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_with_normals_fn(void *__restrict userdata,
MempoolIterData *mp_f,
const TaskParallelTLS *__restrict tls)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls->userdata_chunk);
std::array<BMLoop *, 3> *looptris = static_cast<std::array<BMLoop *, 3> *>(userdata);
BMFace *f = reinterpret_cast<BMFace *>(mp_f);
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(looptris + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_free_fn(const void *__restrict /*userdata*/,
void *__restrict tls_v)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls_v);
if (tls_data->pf_arena) {
BLI_memarena_free(tls_data->pf_arena);
}
}
static void bm_mesh_calc_tessellation__multi_threaded(
BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris, const char face_normals)
{
BM_mesh_elem_index_ensure(bm, BM_LOOP | BM_FACE);
TaskParallelSettings settings;
TessellationUserTLS tls_dummy = {nullptr};
BLI_parallel_mempool_settings_defaults(&settings);
settings.userdata_chunk = &tls_dummy;
settings.userdata_chunk_size = sizeof(tls_dummy);
settings.func_free = bmesh_calc_tessellation_for_face_free_fn;
BM_iter_parallel(bm,
BM_FACES_OF_MESH,
face_normals ? bmesh_calc_tessellation_for_face_with_normals_fn :
bmesh_calc_tessellation_for_face_fn,
looptris.data(),
&settings);
}
void BM_mesh_calc_tessellation_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMeshCalcTessellation_Params *params)
{
if (bm->totface < BM_FACE_TESSELLATE_THREADED_LIMIT) {
bm_mesh_calc_tessellation__single_threaded(bm, looptris, params->face_normals);
}
else {
bm_mesh_calc_tessellation__multi_threaded(bm, looptris, params->face_normals);
}
}
void BM_mesh_calc_tessellation(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris)
{
BMeshCalcTessellation_Params params{};
params.face_normals = false;
BM_mesh_calc_tessellation_ex(bm, looptris, &params);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Default Tessellation (Partial Updates)
* \{ */
struct PartialTessellationUserData {
BMFace *const *faces;
MutableSpan<std::array<BMLoop *, 3>> looptris;
};
struct PartialTessellationUserTLS {
MemArena *pf_arena;
};
static void bmesh_calc_tessellation_for_face_partial_fn(void *__restrict userdata,
const int index,
const TaskParallelTLS *__restrict tls)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(
tls->userdata_chunk);
PartialTessellationUserData *data = static_cast<PartialTessellationUserData *>(userdata);
BMFace *f = data->faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(data->looptris.data() + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_partial_with_normals_fn(
void *__restrict userdata, const int index, const TaskParallelTLS *__restrict tls)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(
tls->userdata_chunk);
PartialTessellationUserData *data = static_cast<PartialTessellationUserData *>(userdata);
BMFace *f = data->faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(
data->looptris.data() + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_partial_free_fn(const void *__restrict /*userdata*/,
void *__restrict tls_v)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(tls_v);
if (tls_data->pf_arena) {
BLI_memarena_free(tls_data->pf_arena);
}
}
static void bm_mesh_calc_tessellation_with_partial__multi_threaded(
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
const int faces_len = bmpinfo->faces.size();
BMFace *const *faces = bmpinfo->faces.data();
PartialTessellationUserData data{};
data.faces = faces;
data.looptris = looptris;
PartialTessellationUserTLS tls_dummy = {nullptr};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.use_threading = true;
settings.userdata_chunk = &tls_dummy;
settings.userdata_chunk_size = sizeof(tls_dummy);
settings.func_free = bmesh_calc_tessellation_for_face_partial_free_fn;
BLI_task_parallel_range(0,
faces_len,
&data,
params->face_normals ?
bmesh_calc_tessellation_for_face_partial_with_normals_fn :
bmesh_calc_tessellation_for_face_partial_fn,
&settings);
}
static void bm_mesh_calc_tessellation_with_partial__single_threaded(
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
const int faces_len = bmpinfo->faces.size();
BMFace *const *faces = bmpinfo->faces.data();
MemArena *pf_arena = nullptr;
if (params->face_normals) {
for (int index = 0; index < faces_len; index++) {
BMFace *f = faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(looptris.data() + offset, f, &pf_arena);
}
}
else {
for (int index = 0; index < faces_len; index++) {
BMFace *f = faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(looptris.data() + offset, f, &pf_arena);
}
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
}
}
void BM_mesh_calc_tessellation_with_partial_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
BLI_assert(bmpinfo->params.do_tessellate);
/* While harmless, exit early if there is nothing to do (avoids ensuring the index). */
if (UNLIKELY(bmpinfo->faces.is_empty())) {
return;
}
BM_mesh_elem_index_ensure(bm, BM_LOOP | BM_FACE);
if (bmpinfo->faces.size() < BM_FACE_TESSELLATE_THREADED_LIMIT) {
bm_mesh_calc_tessellation_with_partial__single_threaded(looptris, bmpinfo, params);
}
else {
bm_mesh_calc_tessellation_with_partial__multi_threaded(looptris, bmpinfo, params);
}
}
void BM_mesh_calc_tessellation_with_partial(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo)
{
BM_mesh_calc_tessellation_with_partial_ex(bm, looptris, bmpinfo, nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Beauty Mesh Tessellation
*
* Avoid degenerate triangles.
* \{ */
static int bmesh_calc_tessellation_for_face_beauty(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p,
Heap **pf_heap_p)
{
switch (efa->len) {
case 3: {
BMLoop *l;
BMLoop **l_ptr = looptris[0].data();
l_ptr[0] = l = BM_FACE_FIRST_LOOP(efa);
l_ptr[1] = l = l->next;
l_ptr[2] = l->next;
return 1;
}
case 4: {
BMLoop *l_v1 = BM_FACE_FIRST_LOOP(efa);
BMLoop *l_v2 = l_v1->next;
BMLoop *l_v3 = l_v2->next;
BMLoop *l_v4 = l_v1->prev;
/* #BM_verts_calc_rotate_beauty performs excessive checks we don't need!
* It's meant for rotating edges, it also calculates a new normal.
*
* Use #BLI_polyfill_beautify_quad_rotate_calc since we have the normal.
*/
#if 0
const bool split_13 = (BM_verts_calc_rotate_beauty(
l_v1->v, l_v2->v, l_v3->v, l_v4->v, 0, 0) < 0.0f);
#else
float axis_mat[3][3], v_quad[4][2];
axis_dominant_v3_to_m3(axis_mat, efa->no);
mul_v2_m3v3(v_quad[0], axis_mat, l_v1->v->co);
mul_v2_m3v3(v_quad[1], axis_mat, l_v2->v->co);
mul_v2_m3v3(v_quad[2], axis_mat, l_v3->v->co);
mul_v2_m3v3(v_quad[3], axis_mat, l_v4->v->co);
const bool split_13 = BLI_polyfill_beautify_quad_rotate_calc(
v_quad[0], v_quad[1], v_quad[2], v_quad[3]) < 0.0f;
#endif
BMLoop **l_ptr_a = looptris[0].data();
BMLoop **l_ptr_b = looptris[1].data();
if (split_13) {
l_ptr_a[0] = l_v1;
l_ptr_a[1] = l_v2;
l_ptr_a[2] = l_v3;
l_ptr_b[0] = l_v1;
l_ptr_b[1] = l_v3;
l_ptr_b[2] = l_v4;
}
else {
l_ptr_a[0] = l_v1;
l_ptr_a[1] = l_v2;
l_ptr_a[2] = l_v4;
l_ptr_b[0] = l_v2;
l_ptr_b[1] = l_v3;
l_ptr_b[2] = l_v4;
}
return 2;
}
default: {
MemArena *pf_arena = *pf_arena_p;
Heap *pf_heap = *pf_heap_p;
if (UNLIKELY(pf_arena == nullptr)) {
pf_arena = *pf_arena_p = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
pf_heap = *pf_heap_p = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
}
BMLoop *l_iter, *l_first;
BMLoop **l_arr;
float axis_mat[3][3];
float (*projverts)[2];
uint(*tris)[3];
const int tris_len = efa->len - 2;
tris = static_cast<uint(*)[3]>(BLI_memarena_alloc(pf_arena, sizeof(*tris) * tris_len));
l_arr = static_cast<BMLoop **>(BLI_memarena_alloc(pf_arena, sizeof(*l_arr) * efa->len));
projverts = static_cast<float (*)[2]>(
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * efa->len));
axis_dominant_v3_to_m3_negate(axis_mat, efa->no);
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
l_arr[i] = l_iter;
mul_v2_m3v3(projverts[i], axis_mat, l_iter->v->co);
i++;
} while ((l_iter = l_iter->next) != l_first);
BLI_polyfill_calc_arena(projverts, efa->len, 1, tris, pf_arena);
BLI_polyfill_beautify(projverts, efa->len, tris, pf_arena, pf_heap);
for (i = 0; i < tris_len; i++) {
BMLoop **l_ptr = looptris[i].data();
uint *tri = tris[i];
l_ptr[0] = l_arr[tri[0]];
l_ptr[1] = l_arr[tri[1]];
l_ptr[2] = l_arr[tri[2]];
}
BLI_memarena_clear(pf_arena);
return tris_len;
}
}
}
void BM_mesh_calc_tessellation_beauty(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris)
{
#ifndef NDEBUG
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
#endif
BMIter iter;
BMFace *efa;
int i = 0;
MemArena *pf_arena = nullptr;
/* use_beauty */
Heap *pf_heap = nullptr;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
i += bmesh_calc_tessellation_for_face_beauty(looptris.data() + i, efa, &pf_arena, &pf_heap);
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
BLI_heap_free(pf_heap, nullptr);
}
BLI_assert(i <= looptris_tot);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* 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 {
struct BMPartialUpdate;
struct BMeshCalcTessellation_Params {
/**
* When calculating normals as well as tessellation, calculate normals after tessellation
* for improved performance. See #BMeshCalcTessellation_Params
*/
bool face_normals;
};
void BM_mesh_calc_tessellation_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMeshCalcTessellation_Params *params);
void BM_mesh_calc_tessellation(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris);
/**
* A version of #BM_mesh_calc_tessellation that avoids degenerate triangles.
*/
void BM_mesh_calc_tessellation_beauty(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris);
void BM_mesh_calc_tessellation_with_partial_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params);
void BM_mesh_calc_tessellation_with_partial(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo);
} // namespace blender

View File

@@ -0,0 +1,251 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM mesh validation function.
*/
/* debug builds only */
#ifndef NDEBUG
# include "BLI_map.hh"
# include "BLI_ordered_edge.hh"
# include "BLI_set.hh"
# include "BLI_utildefines.h"
# include "bmesh.hh"
# include "bmesh_mesh_validate.hh"
namespace blender {
/* macro which inserts the function name */
# if defined __GNUC__
# define ERRMSG(format, args...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, ##args); \
errtot++; \
} \
(void)0
# elif defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL
# define ERRMSG(format, ...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, ##__VA_ARGS__); \
errtot++; \
} \
(void)0
# else
# define ERRMSG(format, ...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, __VA_ARGS__); \
errtot++; \
} \
(void)0
# endif
template<> struct DefaultHash<Set<Vector<int>>> {
uint64_t operator()(const Vector<int> &value) const
{
uint64_t hash = 0;
for (const int v : value) {
hash = get_default_hash(hash, v);
}
return hash;
}
};
bool BM_mesh_is_valid(BMesh *bm)
{
Map<OrderedEdge, BMEdge *> edge_hash;
edge_hash.reserve(bm->totedge);
int errtot;
BMIter iter;
BMVert *v;
BMEdge *e;
BMFace *f;
int i, j;
errtot = -1; /* 'ERRMSG' next line will set at zero */
fprintf(stderr, "\n");
ERRMSG("This is a debugging function and not intended for general use, running slow test!");
/* force recalc, even if tagged as valid, since this mesh is suspect! */
bm->elem_index_dirty |= BM_ALL;
BM_mesh_elem_index_ensure(bm, BM_ALL);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("vert %d: is hidden and selected", i);
}
if (v->e) {
if (!BM_vert_in_edge(v->e, v)) {
ERRMSG("vert %d: is not in its referenced edge: %d", i, BM_elem_index_get(v->e));
}
}
}
/* check edges */
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
if (e->v1 == e->v2) {
ERRMSG("edge %d: duplicate index: %d", i, BM_elem_index_get(e->v1));
}
/* Build edge-hash at the same time. */
edge_hash.add_or_modify(
{BM_elem_index_get(e->v1), BM_elem_index_get(e->v2)},
[&](BMEdge **value) { *value = e; },
[&](BMEdge **value) {
ERRMSG("edge %d, %d: are duplicates", i, BM_elem_index_get(*value));
});
}
/* edge radial structure */
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("edge %d: is hidden and selected", i);
}
if (e->l) {
BMLoop *l_iter;
BMLoop *l_first;
j = 0;
l_iter = l_first = e->l;
/* we could do more checks here, but save for face checks */
do {
if (l_iter->e != e) {
ERRMSG("edge %d: has invalid loop, loop is of face %d", i, BM_elem_index_get(l_iter->f));
}
else if (BM_vert_in_edge(e, l_iter->v) == false) {
ERRMSG("edge %d: has invalid loop with vert not in edge, loop is of face %d",
i,
BM_elem_index_get(l_iter->f));
}
else if (BM_vert_in_edge(e, l_iter->next->v) == false) {
ERRMSG("edge %d: has invalid loop with next vert not in edge, loop is of face %d",
i,
BM_elem_index_get(l_iter->f));
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
/* face structure */
Map<Vector<int>, int> face_map;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
if (BM_elem_flag_test(f, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("face %d: is hidden and selected", i);
}
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_elem_flag_disable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->e, BM_ELEM_INTERNAL_TAG);
j++;
} while ((l_iter = l_iter->next) != l_first);
const int f_len = j;
if (f_len != f->len) {
ERRMSG("face %d: has length of %d but should be %d", i, f->len, f_len);
}
/* Store the loop with the minimum index to create a list of vertex indices. */
BMLoop *l_vert_min = l_first;
j = 0;
l_iter = l_first;
do {
if (BM_elem_flag_test(l_iter, BM_ELEM_INTERNAL_TAG)) {
ERRMSG("face %d: has duplicate loop at corner: %d", i, j);
}
if (BM_elem_flag_test(l_iter->v, BM_ELEM_INTERNAL_TAG)) {
ERRMSG(
"face %d: has duplicate vert: %d, at corner: %d", i, BM_elem_index_get(l_iter->v), j);
}
if (BM_elem_flag_test(l_iter->e, BM_ELEM_INTERNAL_TAG)) {
ERRMSG(
"face %d: has duplicate edge: %d, at corner: %d", i, BM_elem_index_get(l_iter->e), j);
}
/* adjacent data checks */
if (l_iter->f != f) {
ERRMSG("face %d: has loop that points to face: %d at corner: %d",
i,
BM_elem_index_get(l_iter->f),
j);
}
if (l_iter != l_iter->prev->next) {
ERRMSG("face %d: has invalid 'prev/next' at corner: %d", i, j);
}
if (l_iter != l_iter->next->prev) {
ERRMSG("face %d: has invalid 'next/prev' at corner: %d", i, j);
}
if (l_iter != l_iter->radial_prev->radial_next) {
ERRMSG("face %d: has invalid 'radial_prev/radial_next' at corner: %d", i, j);
}
if (l_iter != l_iter->radial_next->radial_prev) {
ERRMSG("face %d: has invalid 'radial_next/radial_prev' at corner: %d", i, j);
}
BM_elem_flag_enable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(l_iter->e, BM_ELEM_INTERNAL_TAG);
if (BM_elem_index_get(l_iter->v) < BM_elem_index_get(l_vert_min->v)) {
l_vert_min = l_iter;
}
j++;
} while ((l_iter = l_iter->next) != l_first);
/* Store ordered face verts, walking over the lowest index first
* so faces with flipped winding still match. */
Vector<int> face_verts;
face_verts.reserve(f_len);
if (BM_elem_index_get(l_vert_min->next->v) < BM_elem_index_get(l_vert_min->prev->v)) {
l_iter = l_vert_min;
do {
face_verts.append_unchecked(BM_elem_index_get(l_iter->v));
} while ((l_iter = l_iter->next) != l_vert_min);
}
else {
l_iter = l_vert_min;
do {
face_verts.append_unchecked(BM_elem_index_get(l_iter->v));
} while ((l_iter = l_iter->prev) != l_vert_min);
}
face_map.add_or_modify(
std::move(face_verts),
[&](int *value) { *value = i; },
[&](const int *value) { ERRMSG("face %d: duplicate of %d", i, *value); });
/* leave elements un-tagged, not essential but nice to avoid unintended dirty tag use later. */
do {
BM_elem_flag_disable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->e, BM_ELEM_INTERNAL_TAG);
} while ((l_iter = l_iter->next) != l_first);
}
const bool is_valid = (errtot == 0);
ERRMSG("Finished - errors %d", errtot);
return is_valid;
}
} // namespace blender
#endif

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Check of this #BMesh is valid,
* this function can be slow since its intended to help with debugging.
*
* \return true when the mesh is valid.
*/
bool BM_mesh_is_valid(BMesh *bm);
} // namespace blender

View File

@@ -0,0 +1,903 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains functions for locally modifying
* the topology of existing mesh data. (split, join, flip etc).
*/
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
bool BM_vert_dissolve(BMesh *bm, BMVert *v)
{
/* logic for 3 or more is identical */
const int len = BM_vert_edge_count_at_most(v, 3);
if (len == 1) {
BM_vert_kill(bm, v); /* will kill edges too */
return true;
}
if (!BM_vert_is_manifold(v)) {
if (!v->e) {
BM_vert_kill(bm, v);
return true;
}
if (!v->e->l) {
if (len == 2) {
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
/* used to kill the vertex here, but it may be connected to faces.
* so better do nothing */
return false;
}
return false;
}
if (len == 2 && BM_vert_face_count_is_equal(v, 1)) {
/* boundary vertex on a face */
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
return BM_disk_dissolve(bm, v);
}
bool BM_disk_dissolve(BMesh *bm, BMVert *v)
{
BMEdge *e, *keepedge = nullptr, *baseedge = nullptr;
int len = 0;
if (!BM_vert_is_manifold(v)) {
return false;
}
if (v->e) {
/* v->e we keep, what else */
e = v->e;
do {
e = bmesh_disk_edge_next(e, v);
if (!BM_edge_share_face_check(e, v->e)) {
keepedge = e;
baseedge = v->e;
break;
}
len++;
} while (e != v->e);
}
/* this code for handling 2 and 3-valence verts
* may be totally bad */
if (keepedge == nullptr && len == 3) {
#if 0
/* handle specific case for three-valence. solve it by
* increasing valence to four. this may be hackish. */
BMLoop *l_a = BM_face_vert_share_loop(e->l->f, v);
BMLoop *l_b = (e->l->v == v) ? e->l->next : e->l;
if (!BM_face_split(bm, e->l->f, l_a, l_b, nullptr, nullptr, false)) {
return false;
}
if (!BM_disk_dissolve(bm, v)) {
return false;
}
#else
BMFace *f_double;
if (UNLIKELY(!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double))) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
if (UNLIKELY(!BM_vert_collapse_faces(bm, v->e, v, 1.0, true, false, true, true))) {
return false;
}
#endif
return true;
}
if (keepedge == nullptr && len == 2) {
/* collapse the vertex */
e = BM_vert_collapse_faces(bm, v->e, v, 1.0, true, true, true, true);
if (!e) {
return false;
}
/* handle two-valence */
if (e->l != e->l->radial_next) {
BMFace *f_double;
if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double)) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
return true;
}
if (keepedge) {
bool done = false;
while (!done) {
done = true;
e = v->e;
do {
BMFace *f = nullptr;
if (BM_edge_is_manifold(e) && (e != baseedge) && (e != keepedge)) {
BMFace *f_double;
f = BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double);
/* return if couldn't join faces in manifold
* conditions */
/* !disabled for testing why bad things happen */
if (!f) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
if (f) {
done = false;
break;
}
} while ((e = bmesh_disk_edge_next(e, v)) != v->e);
}
/* collapse the vertex */
/* NOTE: the baseedge can be a boundary of manifold, use this as join_faces arg. */
e = BM_vert_collapse_faces(
bm, baseedge, v, 1.0, true, !BM_edge_is_boundary(baseedge), true, true);
if (!e) {
return false;
}
if (e->l) {
/* get remaining two faces */
if (e->l != e->l->radial_next) {
BMFace *f_double;
/* join two remaining faces */
if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double)) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
}
}
return true;
}
BMFace *BM_faces_join_pair(
BMesh *bm, BMLoop *l_a, BMLoop *l_b, const bool do_del, BMFace **r_double)
{
BLI_assert((l_a != l_b) && (l_a->e == l_b->e));
if (l_a->v == l_b->v) {
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
bmesh_kernel_loop_reverse(bm, l_b->f, cd_loop_mdisp_offset, true);
}
BMFace *faces[2] = {l_a->f, l_b->f};
return BM_faces_join(bm, faces, 2, do_del, r_double);
}
BMFace *BM_face_split(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
BMLoop **r_l,
BMEdge *example,
const bool no_double)
{
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
BMFace *f_new, *f_tmp;
BLI_assert(l_a != l_b);
BLI_assert(f == l_a->f && f == l_b->f);
BLI_assert(!BM_loop_is_adjacent(l_a, l_b));
/* could be an assert */
if (UNLIKELY(BM_loop_is_adjacent(l_a, l_b)) || UNLIKELY(f != l_a->f || f != l_b->f)) {
if (r_l) {
*r_l = nullptr;
}
return nullptr;
}
/* do we have a multires layer? */
if (cd_loop_mdisp_offset != -1) {
f_tmp = BM_face_copy(bm, f, false, false);
}
#ifdef USE_BMESH_HOLES
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, r_l, nullptr, example, no_double);
#else
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, r_l, example, no_double);
#endif
if (f_new) {
/* handle multires update */
if (cd_loop_mdisp_offset != -1) {
float f_dst_center[3];
float f_src_center[3];
BM_face_calc_center_median(f_tmp, f_src_center);
BM_face_calc_center_median(f, f_dst_center);
BM_face_interp_multires_ex(bm, f, f_tmp, f_dst_center, f_src_center, cd_loop_mdisp_offset);
BM_face_calc_center_median(f_new, f_dst_center);
BM_face_interp_multires_ex(
bm, f_new, f_tmp, f_dst_center, f_src_center, cd_loop_mdisp_offset);
#if 0
/* BM_face_multires_bounds_smooth doesn't flip displacement correct */
BM_face_multires_bounds_smooth(bm, f);
BM_face_multires_bounds_smooth(bm, f_new);
#endif
}
}
if (cd_loop_mdisp_offset != -1) {
BM_face_kill(bm, f_tmp);
}
return f_new;
}
BMFace *BM_face_split_n(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
float cos[][3],
int n,
BMLoop **r_l,
BMEdge *example)
{
BMFace *f_new, *f_tmp;
BMLoop *l_new;
BMEdge *e, *e_new;
BMVert *v_new;
// BMVert *v_a = l_a->v; /* UNUSED */
BMVert *v_b = l_b->v;
int i, j;
BLI_assert(l_a != l_b);
BLI_assert(f == l_a->f && f == l_b->f);
BLI_assert(!((n == 0) && BM_loop_is_adjacent(l_a, l_b)));
/* could be an assert */
if (UNLIKELY((n == 0) && BM_loop_is_adjacent(l_a, l_b)) || UNLIKELY(l_a->f != l_b->f)) {
if (r_l) {
*r_l = nullptr;
}
return nullptr;
}
f_tmp = BM_face_copy(bm, f, true, true);
#ifdef USE_BMESH_HOLES
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, &l_new, nullptr, example, false);
#else
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, &l_new, example, false);
#endif
/* bmesh_kernel_split_face_make_edge returns in 'l_new'
* a Loop for f_new going from 'v_a' to 'v_b'.
* The radial_next is for 'f' and goes from 'v_b' to 'v_a'. */
if (f_new) {
e = l_new->e;
for (i = 0; i < n; i++) {
v_new = bmesh_kernel_split_edge_make_vert(bm, v_b, e, &e_new);
BLI_assert(v_new != nullptr);
/* bmesh_kernel_split_edge_make_vert returns in 'e_new'
* the edge going from 'v_new' to 'v_b'. */
copy_v3_v3(v_new->co, cos[i]);
/* interpolate the loop data for the loops with (v == v_new), using orig face */
for (j = 0; j < 2; j++) {
BMEdge *e_iter = (j == 0) ? e : e_new;
BMLoop *l_iter = e_iter->l;
do {
if (l_iter->v == v_new) {
/* this interpolates both loop and vertex data */
BM_loop_interp_from_face(bm, l_iter, f_tmp, true, true);
}
} while ((l_iter = l_iter->radial_next) != e_iter->l);
}
e = e_new;
}
}
BM_face_verts_kill(bm, f_tmp);
if (r_l) {
*r_l = l_new;
}
return f_new;
}
BMEdge *BM_vert_collapse_faces(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
float fac,
const bool do_del,
const bool join_faces,
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
BMEdge *e_new = nullptr;
BMVert *tv = BM_edge_other_vert(e_kill, v_kill);
BMEdge *e2;
BMVert *tv2;
/* Only intended to be called for 2-valence vertices */
BLI_assert(bmesh_disk_count(v_kill) <= 2);
/* first modify the face loop data */
if (e_kill->l) {
BMLoop *l_iter;
const float w[2] = {1.0f - fac, fac};
l_iter = e_kill->l;
do {
if (l_iter->v == tv && l_iter->next->v == v_kill) {
const void *src[2];
BMLoop *tvloop = l_iter;
BMLoop *kvloop = l_iter->next;
src[0] = kvloop->head.data;
src[1] = tvloop->head.data;
CustomData_bmesh_interp(&bm->ldata, src, w, 2, kvloop->head.data);
}
} while ((l_iter = l_iter->radial_next) != e_kill->l);
}
/* now interpolate the vertex data */
BM_data_interp_from_verts(bm, v_kill, tv, v_kill, fac);
e2 = bmesh_disk_edge_next(e_kill, v_kill);
tv2 = BM_edge_other_vert(e2, v_kill);
if (join_faces) {
BMIter fiter;
BMFace *f;
Vector<BMFace *, BM_DEFAULT_ITER_STACK_SIZE> faces;
BM_ITER_ELEM (f, &fiter, v_kill, BM_FACES_OF_VERT) {
faces.append(f);
}
if (faces.size() >= 2) {
BMFace *f_double;
if (BMFace *f2 = BM_faces_join(bm, faces.data(), faces.size(), true, &f_double)) {
if (kill_duplicate_faces && (f_double != nullptr)) {
BM_face_kill(bm, f_double);
}
BMLoop *l_a, *l_b;
if ((l_a = BM_face_vert_share_loop(f2, tv)) && (l_b = BM_face_vert_share_loop(f2, tv2))) {
BMLoop *l_new;
if (BM_face_split(bm, f2, l_a, l_b, &l_new, nullptr, false)) {
e_new = l_new->e;
}
}
}
}
}
else {
/* single face or no faces */
/* same as BM_vert_collapse_edge() however we already
* have vars to perform this operation so don't call. */
e_new = bmesh_kernel_join_edge_kill_vert(
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces, kill_duplicate_faces);
// e_new = BM_edge_exists(tv, tv2); /* Same as return above. */
}
return e_new;
}
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
const bool do_del,
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
/* nice example implementation but we want loops to have their customdata
* accounted for */
#if 0
BMEdge *e_new = nullptr;
/* Collapse between 2 edges */
/* in this case we want to keep all faces and not join them,
* rather just get rid of the vertex - see bug #28645. */
BMVert *tv = BM_edge_other_vert(e_kill, v_kill);
if (tv) {
BMEdge *e2 = bmesh_disk_edge_next(e_kill, v_kill);
if (e2) {
BMVert *tv2 = BM_edge_other_vert(e2, v_kill);
if (tv2) {
/* only action, other calls here only get the edge to return */
e_new = bmesh_kernel_join_edge_kill_vert(
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces);
}
}
}
return e_new;
#else
/* with these args faces are never joined, same as above
* but account for loop customdata */
return BM_vert_collapse_faces(
bm, e_kill, v_kill, 1.0f, do_del, false, kill_degenerate_faces, kill_duplicate_faces);
#endif
}
#undef DO_V_INTERP
BMVert *BM_edge_collapse(
BMesh *bm, BMEdge *e_kill, BMVert *v_kill, const bool do_del, const bool kill_degenerate_faces)
{
return bmesh_kernel_join_vert_kill_edge(bm, e_kill, v_kill, do_del, true, kill_degenerate_faces);
}
BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
{
BMVert *v_new, *v_other;
BMEdge *e_new;
Vector<BMFace *, 32> oldfaces;
const int cd_loop_mdisp_offset = BM_edge_is_wire(e) ?
-1 :
CustomData_get_offset(&bm->ldata, CD_MDISPS);
BLI_assert(BM_vert_in_edge(e, v) == true);
/* do we have a multi-res layer? */
if (cd_loop_mdisp_offset != -1) {
BMLoop *l = e->l;
do {
oldfaces.append(l->f);
l = l->radial_next;
} while (l != e->l);
/* flag existing faces so we can differentiate oldfaces from new faces */
for (int64_t i = 0; i < oldfaces.size(); i++) {
BM_ELEM_API_FLAG_ENABLE(oldfaces[i], _FLAG_OVERLAP);
oldfaces[i] = BM_face_copy(bm, oldfaces[i], true, true);
BM_ELEM_API_FLAG_DISABLE(oldfaces[i], _FLAG_OVERLAP);
}
}
v_other = BM_edge_other_vert(e, v);
v_new = bmesh_kernel_split_edge_make_vert(bm, v, e, &e_new);
if (r_e != nullptr) {
*r_e = e_new;
}
BLI_assert(v_new != nullptr);
BLI_assert(BM_vert_in_edge(e_new, v) && BM_vert_in_edge(e_new, v_new));
BLI_assert(BM_vert_in_edge(e, v_new) && BM_vert_in_edge(e, v_other));
sub_v3_v3v3(v_new->co, v_other->co, v->co);
madd_v3_v3v3fl(v_new->co, v->co, v_new->co, fac);
e_new->head.hflag = e->head.hflag;
BM_elem_attrs_copy(bm, e, e_new);
/* v->v_new->v2 */
BM_data_interp_face_vert_edge(bm, v_other, v, v_new, e, fac);
BM_data_interp_from_verts(bm, v, v_other, v_new, fac);
if (cd_loop_mdisp_offset != -1) {
/* interpolate new/changed loop data from copied old faces */
for (BMFace *oldface : oldfaces) {
float f_center_old[3];
BM_face_calc_center_median(oldface, f_center_old);
for (int j = 0; j < 2; j++) {
BMEdge *e1 = j ? e_new : e;
BMLoop *l = e1->l;
if (UNLIKELY(!l)) {
BMESH_ASSERT(0);
break;
}
do {
/* check this is an old face */
if (BM_ELEM_API_FLAG_TEST(l->f, _FLAG_OVERLAP)) {
float f_center[3];
BM_face_calc_center_median(l->f, f_center);
BM_face_interp_multires_ex(
bm, l->f, oldface, f_center, f_center_old, cd_loop_mdisp_offset);
}
l = l->radial_next;
} while (l != e1->l);
}
}
/* destroy the old faces */
for (BMFace *oldface : oldfaces) {
BM_face_verts_kill(bm, oldface);
}
/* fix boundaries a bit, doesn't work too well quite yet */
#if 0
for (int j = 0; j < 2; j++) {
BMEdge *e1 = j ? e_new : e;
BMLoop *l, *l2;
l = e1->l;
if (UNLIKELY(!l)) {
BMESH_ASSERT(0);
break;
}
do {
BM_face_multires_bounds_smooth(bm, l->f);
l = l->radial_next;
} while (l != e1->l);
}
#endif
}
return v_new;
}
BMVert *BM_edge_split_n(BMesh *bm, BMEdge *e, int numcuts, BMVert **r_varr)
{
int i;
float percent;
BMVert *v_new = nullptr;
for (i = 0; i < numcuts; i++) {
percent = 1.0f / float(numcuts + 1 - i);
v_new = BM_edge_split(bm, e, e->v2, nullptr, percent);
if (r_varr) {
/* fill in reverse order (v1 -> v2) */
r_varr[numcuts - i - 1] = v_new;
}
}
return v_new;
}
void BM_edge_verts_swap(BMEdge *e)
{
std::swap(e->v1, e->v2);
std::swap(e->v1_disk_link, e->v2_disk_link);
}
bool BM_edge_calc_rotate(BMEdge *e, const bool ccw, BMLoop **r_l1, BMLoop **r_l2)
{
BMVert *v1, *v2;
BMFace *fa, *fb;
/* this should have already run */
BLI_assert(BM_edge_rotate_check(e) == true);
/* we know this will work */
BM_edge_face_pair(e, &fa, &fb);
/* so we can use `ccw` variable correctly,
* otherwise we could use the edges verts direct */
BM_edge_ordered_verts(e, &v1, &v2);
/* we could swap the verts _or_ the faces, swapping faces
* gives more predictable results since that way the next vert
* just stitches from face fa / fb */
if (!ccw) {
std::swap(fa, fb);
}
BMLoop *l1 = BM_face_other_vert_loop(fb, v2, v1);
BMLoop *l2 = BM_face_other_vert_loop(fa, v1, v2);
/* This occurs when faces share multiple edges next to `e`.
* While rare it's not an error, this rotation must be skipped. */
if (l1->v == l2->v) [[unlikely]] {
return false;
}
*r_l1 = l1;
*r_l2 = l2;
return true;
}
bool BM_edge_rotate_check(BMEdge *e)
{
BMFace *fa, *fb;
if (BM_edge_face_pair(e, &fa, &fb)) {
BMLoop *la, *lb;
la = BM_face_other_vert_loop(fa, e->v2, e->v1);
lb = BM_face_other_vert_loop(fb, e->v2, e->v1);
/* check that the next vert in both faces isn't the same
* (ie - the next edge doesn't share the same faces).
* since we can't rotate usefully in this case. */
if (la->v == lb->v) {
return false;
}
/* mirror of the check above but in the opposite direction */
la = BM_face_other_vert_loop(fa, e->v1, e->v2);
lb = BM_face_other_vert_loop(fb, e->v1, e->v2);
if (la->v == lb->v) {
return false;
}
return true;
}
return false;
}
bool BM_edge_rotate_check_degenerate(BMEdge *e, BMLoop *l1, BMLoop *l2)
{
/* NOTE: for these vars 'old' just means initial edge state. */
float ed_dir_old[3]; /* edge vector */
float ed_dir_new[3]; /* edge vector */
float ed_dir_new_flip[3]; /* edge vector */
float ed_dir_v1_old[3];
float ed_dir_v2_old[3];
float ed_dir_v1_new[3];
float ed_dir_v2_new[3];
float cross_old[3];
float cross_new[3];
/* original verts - these will be in the edge 'e' */
BMVert *v1_old, *v2_old;
/* verts from the loops passed */
BMVert *v1, *v2;
/* These are the opposite verts - the verts that _would_ be used if `ccw` was inverted. */
BMVert *v1_alt, *v2_alt;
/* this should have already run */
BLI_assert(BM_edge_rotate_check(e) == true);
BM_edge_ordered_verts(e, &v1_old, &v2_old);
v1 = l1->v;
v2 = l2->v;
/* get the next vert along */
v1_alt = BM_face_other_vert_loop(l1->f, v1_old, v1)->v;
v2_alt = BM_face_other_vert_loop(l2->f, v2_old, v2)->v;
/* normalize all so comparisons are scale independent */
BLI_assert(BM_edge_exists(v1_old, v1));
BLI_assert(BM_edge_exists(v1, v1_alt));
BLI_assert(BM_edge_exists(v2_old, v2));
BLI_assert(BM_edge_exists(v2, v2_alt));
/* old and new edge vecs */
sub_v3_v3v3(ed_dir_old, v1_old->co, v2_old->co);
sub_v3_v3v3(ed_dir_new, v1->co, v2->co);
normalize_v3(ed_dir_old);
normalize_v3(ed_dir_new);
/* old edge corner vecs */
sub_v3_v3v3(ed_dir_v1_old, v1_old->co, v1->co);
sub_v3_v3v3(ed_dir_v2_old, v2_old->co, v2->co);
normalize_v3(ed_dir_v1_old);
normalize_v3(ed_dir_v2_old);
/* old edge corner vecs */
sub_v3_v3v3(ed_dir_v1_new, v1->co, v1_alt->co);
sub_v3_v3v3(ed_dir_v2_new, v2->co, v2_alt->co);
normalize_v3(ed_dir_v1_new);
normalize_v3(ed_dir_v2_new);
/* compare */
cross_v3_v3v3(cross_old, ed_dir_old, ed_dir_v1_old);
cross_v3_v3v3(cross_new, ed_dir_new, ed_dir_v1_new);
if (dot_v3v3(cross_old, cross_new) < 0.0f) { /* does this flip? */
return false;
}
cross_v3_v3v3(cross_old, ed_dir_old, ed_dir_v2_old);
cross_v3_v3v3(cross_new, ed_dir_new, ed_dir_v2_new);
if (dot_v3v3(cross_old, cross_new) < 0.0f) { /* does this flip? */
return false;
}
negate_v3_v3(ed_dir_new_flip, ed_dir_new);
/* result is zero area corner */
if ((dot_v3v3(ed_dir_new, ed_dir_v1_new) > 0.999f) ||
(dot_v3v3(ed_dir_new_flip, ed_dir_v2_new) > 0.999f))
{
return false;
}
return true;
}
bool BM_edge_rotate_check_beauty(BMEdge *e, BMLoop *l1, BMLoop *l2)
{
/* Stupid check for now:
* Could compare angles of surrounding edges
* before & after, but this is OK. */
return (len_squared_v3v3(e->v1->co, e->v2->co) > len_squared_v3v3(l1->v->co, l2->v->co));
}
BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, const bool ccw, const short check_flag)
{
BMVert *v1, *v2;
BMLoop *l1, *l2;
BMFace *f;
BMEdge *e_new = nullptr;
char f_active_prev = 0;
char f_hflag_prev_1;
char f_hflag_prev_2;
if (!BM_edge_rotate_check(e)) {
return nullptr;
}
if (!BM_edge_calc_rotate(e, ccw, &l1, &l2)) {
return nullptr;
}
/* the loops will be freed so assign verts */
v1 = l1->v;
v2 = l2->v;
/* --------------------------------------- */
/* Checking Code - make sure we can rotate */
if (check_flag & BM_EDGEROT_CHECK_BEAUTY) {
if (!BM_edge_rotate_check_beauty(e, l1, l2)) {
return nullptr;
}
}
/* check before applying */
if (check_flag & BM_EDGEROT_CHECK_EXISTS) {
if (BM_edge_exists(v1, v2)) {
return nullptr;
}
}
/* slowest, check last */
if (check_flag & BM_EDGEROT_CHECK_DEGENERATE) {
if (!BM_edge_rotate_check_degenerate(e, l1, l2)) {
return nullptr;
}
}
/* Done Checking */
/* ------------- */
/* --------------- */
/* Rotate The Edge */
/* first create the new edge, this is so we can copy the customdata from the old one
* if splice if disabled, always add in a new edge even if there's one there. */
e_new = BM_edge_create(
bm, v1, v2, e, (check_flag & BM_EDGEROT_CHECK_SPLICE) ? BM_CREATE_NO_DOUBLE : BM_CREATE_NOP);
f_hflag_prev_1 = l1->f->head.hflag;
f_hflag_prev_2 = l2->f->head.hflag;
/* maintain active face */
if (bm->act_face == l1->f) {
f_active_prev = 1;
}
else if (bm->act_face == l2->f) {
f_active_prev = 2;
}
const bool is_flipped = !BM_edge_is_contiguous(e);
BMFace *f_double;
/* don't delete the edge, manually remove the edge after so we can copy its attributes */
f = BM_faces_join_pair(
bm, BM_face_edge_share_loop(l1->f, e), BM_face_edge_share_loop(l2->f, e), true, &f_double);
if (f == nullptr) {
return nullptr;
}
/* NOTE: this assumes joining the faces _didnt_ also remove the verts.
* the #BM_edge_rotate_check will ensure this, but its possibly corrupt state or future edits
* break this */
if ((l1 = BM_face_vert_share_loop(f, v1)) && (l2 = BM_face_vert_share_loop(f, v2)) &&
BM_face_split(bm, f, l1, l2, nullptr, nullptr, true))
{
/* we should really be able to know the faces some other way,
* rather than fetching them back from the edge, but this is predictable
* where using the return values from face split isn't. - campbell */
BMFace *fa, *fb;
if (BM_edge_face_pair(e_new, &fa, &fb)) {
fa->head.hflag = f_hflag_prev_1;
fb->head.hflag = f_hflag_prev_2;
if (f_active_prev == 1) {
bm->act_face = fa;
}
else if (f_active_prev == 2) {
bm->act_face = fb;
}
if (is_flipped) {
BM_face_normal_flip(bm, fb);
if (ccw) {
/* Needed otherwise `ccw` toggles direction */
e_new->l = e_new->l->radial_next;
}
}
}
}
else {
/* See #BM_faces_join note on callers asserting when `r_double` is non-null.
* Checked here because a double is acceptable as long as its temporary.
*
* TODO(@ideasman42): To properly solve we'd need to create the 2x faces with edge rotation
* then only delete the original faces once the new faces have been successfully created.
* - Worth looking into. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
return nullptr;
}
return e_new;
}
BMVert *BM_face_loop_separate(BMesh *bm, BMLoop *l_sep)
{
return bmesh_kernel_unglue_region_make_vert(bm, l_sep);
}
BMVert *BM_face_loop_separate_multi_isolated(BMesh *bm, BMLoop *l_sep)
{
return bmesh_kernel_unglue_region_make_vert_multi_isolated(bm, l_sep);
}
BMVert *BM_face_loop_separate_multi(BMesh *bm, BMLoop **larr, int larr_len)
{
return bmesh_kernel_unglue_region_make_vert_multi(bm, larr, larr_len);
}
} // namespace blender

View File

@@ -0,0 +1,285 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "bmesh_class.hh"
namespace blender {
/** \file
* \ingroup bmesh
*/
/**
* \brief Dissolve Vert
*
* Turns the face region surrounding a manifold vertex into a single polygon.
*
* \par Example:
* <pre>
* +---------+ +---------+
* | \ / | | |
* Before: | v | After: | |
* | / \ | | |
* +---------+ +---------+
* </pre>
*
* This function can also collapse edges too
* in cases when it can't merge into faces.
*
* \par Example:
* <pre>
* Before: +----v----+ After: +---------+
* </pre>
*
* \note dissolves vert, in more situations than BM_disk_dissolve
* (e.g. if the vert is part of a wire edge, etc).
*/
bool BM_vert_dissolve(BMesh *bm, BMVert *v);
/**
* dissolves all faces around a vert, and removes it.
*/
bool BM_disk_dissolve(BMesh *bm, BMVert *v);
/**
* \brief Faces Join Pair
*
* Joins two adjacent faces together.
*
* \note This method calls to #BM_faces_join to do its work.
* This means connected edges which also share the two faces will be joined.
*
* If the windings do not match the winding of the new face will follow
* \a l_a's winding (i.e. \a l_b will be reversed before the join).
*
* \param bm: The bmesh.
* \param l_a: First loop of an adjacent face pair that will be joined.
* \param l_b: Second loop of an adjacent face pair that will be joined.
* \param do_del: If true, remove the original faces, internal edges,
* and internal verts such that they are replaced by the new face.
* \param r_double: A pointer to a face that controls processing of doubled faces.
* See #BM_faces_join `r_double` argument for details.
*
* \return The combined face or NULL on failure.
*/
BMFace *BM_faces_join_pair(BMesh *bm, BMLoop *l_a, BMLoop *l_b, bool do_del, BMFace **r_double);
/** see: bmesh_polygon_edgenet.hh for #BM_face_split_edgenet */
/**
* \brief Face Split
*
* Split a face along two vertices. returns the newly made face, and sets
* the \a r_l member to a loop in the newly created edge.
*
* \param bm: The bmesh
* \param f: the original face
* \param l_a, l_b: Loops of this face, their vertices define
* the split edge to be created (must be differ and not can't be adjacent in the face).
* \param r_l: pointer which will receive the BMLoop for the split edge in the new face
* \param example: Edge used for attributes of splitting edge, if non-NULL
* \param no_double: Use an existing edge if found
*
* \return Pointer to the newly created face representing one side of the split
* if the split is successful (and the original face will be the other side).
* NULL if the split fails.
*/
BMFace *BM_face_split(
BMesh *bm, BMFace *f, BMLoop *l_a, BMLoop *l_b, BMLoop **r_l, BMEdge *example, bool no_double);
/**
* \brief Face Split with intermediate points
*
* Like BM_face_split, but with an edge split by \a n intermediate points with given coordinates.
*
* \param bm: The bmesh.
* \param f: the original face.
* \param l_a, l_b: Vertices which define the split edge, must be different.
* \param cos: Array of coordinates for intermediate points.
* \param n: Length of \a cos (must be > 0).
* \param r_l: pointer which will receive the BMLoop.
* for the first split edge (from \a l_a) in the new face.
* \param example: Edge used for attributes of splitting edge, if non-NULL.
*
* \return Pointer to the newly created face representing one side of the split
* if the split is successful (and the original face will be the other side).
* NULL if the split fails.
*/
BMFace *BM_face_split_n(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
float cos[][3],
int n,
BMLoop **r_l,
BMEdge *example);
/**
* \brief Vert Collapse Faces
*
* Collapses vertex \a v_kill that has only two manifold edges
* onto a vertex it shares an edge with.
* \a fac defines the amount of interpolation for Custom Data.
*
* \note that this is not a general edge collapse function.
*
* \note this function is very close to #BM_vert_collapse_edge,
* both collapse a vertex and return a new edge.
* Except this takes a factor and merges custom data.
*
* \param bm: The bmesh
* \param e_kill: The edge to collapse
* \param v_kill: The vertex to collapse into the edge
* \param fac: The factor along the edge
* \param join_faces: When true the faces around the vertex will be joined
* otherwise collapse the vertex by merging the 2 edges this vert touches into one.
* \param kill_degenerate_faces: Removes faces with less than 3 verts after collapsing.
*
* \returns The New Edge
*/
BMEdge *BM_vert_collapse_faces(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
float fac,
bool do_del,
bool join_faces,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* \brief Vert Collapse Faces
*
* Collapses a vertex onto another vertex it shares an edge with.
*
* \return The New Edge
*
* \note To check if collapsing would create duplicate geometry,
* see: #BM_vert_collapse_check_double_face.
*/
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* Collapse and edge into a single vertex.
*/
BMVert *BM_edge_collapse(
BMesh *bm, BMEdge *e_kill, BMVert *v_kill, bool do_del, bool kill_degenerate_faces);
/**
* \brief Edge Split
*
* <pre>
* Before: v
* +-----------------------------------+
* e
*
* After: v v_new (returned)
* +-----------------+-----------------+
* r_e e
* </pre>
*
* \param e: The edge to split.
* \param v: One of the vertices in \a e and defines the "from" end of the splitting operation,
* the new vertex will be \a fac of the way from \a v to the other end.
* \param r_e: The newly created edge.
* \return The new vertex.
*/
BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac);
/**
* \brief Split an edge multiple times evenly
*
* \param r_varr: Optional array, verts in between (v1 -> v2)
*/
BMVert *BM_edge_split_n(BMesh *bm, BMEdge *e, int numcuts, BMVert **r_varr);
/**
* Swap v1 & v2
*
* \note Typically we shouldn't care about this, however it's used when extruding wire edges.
*/
void BM_edge_verts_swap(BMEdge *e);
/**
* Calculate the 2 loops which _would_ make up the newly rotated Edge
* but don't actually change anything.
*
* Use this to further inspect if the loops to be connected have issues:
*
* Examples:
* - the newly formed edge already exists
* - the new face would be degenerate (zero area / concave / bow-tie)
* - may want to measure if the new edge gives improved results topology.
* over the old one, as with beauty fill.
*
* \note #BM_edge_rotate_check must have already run.
*/
[[nodiscard]] bool BM_edge_calc_rotate(BMEdge *e, bool ccw, BMLoop **r_l1, BMLoop **r_l2);
/**
* \brief Check if Rotate Edge is OK
*
* Quick check to see if we could rotate the edge,
* use this to avoid calling exceptions on common cases.
*
* Take care, depending on the rotation direction its possible
* the adjacent faces share multiple edges on either side.
*
* Before executing the rotation it's important to check the rotated loops
* on both faces don't reference the same vertex.
*/
bool BM_edge_rotate_check(BMEdge *e);
/**
* \brief Check if Edge Rotate Gives Degenerate Faces
*
* Check 2 cases
* 1) does the newly forms edge form a flipped face (compare with previous cross product)
* 2) does the newly formed edge cause a zero area corner (or close enough to be almost zero)
*
* \param e: The edge to test rotation.
* \param l1, l2: are the loops of the proposed verts to rotate too and should
* be the result of calling #BM_edge_calc_rotate
*/
bool BM_edge_rotate_check_degenerate(BMEdge *e, BMLoop *l1, BMLoop *l2);
bool BM_edge_rotate_check_beauty(BMEdge *e, BMLoop *l1, BMLoop *l2);
/**
* \brief Rotate Edge
*
* Spins an edge topologically,
* either counter-clockwise or clockwise depending on \a ccw.
*
* \return The spun edge, NULL on error
* (e.g., if the edge isn't surrounded by exactly two faces).
*
* \note This works by dissolving the edge then re-creating it,
* so the returned edge won't have the same pointer address as the original one.
*
* \see header definition for \a check_flag enum.
*/
BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, bool ccw, short check_flag);
/** Flags for #BM_edge_rotate */
enum {
/** Disallow rotating when the new edge matches an existing one. */
BM_EDGEROT_CHECK_EXISTS = (1 << 0),
/** Overrides existing check, if the edge already, rotate and merge them. */
BM_EDGEROT_CHECK_SPLICE = (1 << 1),
/** Disallow creating bow-tie, concave or zero area faces */
BM_EDGEROT_CHECK_DEGENERATE = (1 << 2),
/** Disallow rotating into ugly topology. */
BM_EDGEROT_CHECK_BEAUTY = (1 << 3),
};
/**
* \brief Rip a single face from a vertex fan
*/
BMVert *BM_face_loop_separate(BMesh *bm, BMLoop *l_sep);
BMVert *BM_face_loop_separate_multi_isolated(BMesh *bm, BMLoop *l_sep);
BMVert *BM_face_loop_separate_multi(BMesh *bm, BMLoop **larr, int larr_len);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,847 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_enum_flags.hh"
#include "BLI_ghash.h"
#include <cstdarg>
#include "bmesh_class.hh"
namespace blender {
/**
* operators represent logical, executable mesh modules. all topological
* operations involving a bmesh has to go through them.
*
* operators are nested, as are tool flags, which are private to an operator
* when it's executed. tool flags are allocated in layers, one per operator
* execution, and are used for all internal flagging a tool needs to do.
*
* each operator has a series of "slots" which can be of the following types:
* - simple numerical types
* - arrays of elements (e.g. arrays of faces).
* - hash mappings.
*
* each slot is identified by a slot code, as are each operator.
* operators, and their slots, are defined in bmesh_opdefines.cc (with their
* execution functions prototyped in bmesh_operators_private.hh), with all their
* operator code and slot codes defined in bmesh_operators.hh. see
* bmesh_opdefines.cc and the BMOpDefine struct for how to define new operators.
*
* in general, operators are fed arrays of elements, created using either
* #BMO_slot_buffer_from_hflag or #BMO_slot_buffer_from_flag
* (or through one of the format specifiers in #BMO_op_callf or #BMO_op_initf).
*
* \note multiple element types (e.g. faces and edges)
* can be fed to the same slot array. Operators act on this data,
* and possibly spit out data into output slots.
*
* \note operators should never read from header flags (e.g. element->head.flag).
* For example, if you want an operator to only operate on selected faces, you
* should use #BMO_slot_buffer_from_hflag to put the selected elements into a slot.
*
* \note when you read from an element slot array or mapping, you can either tool-flag
* all the elements in it, or read them using an iterator API (which is semantically
* similar to the iterator API in bmesh_iterators.hh).
*
* \note only #BMLoop items can't be put into slots as with verts, edges & faces.
*/
struct GHashIterator;
BLI_INLINE BMFlagLayer *BMO_elem_flag_from_header(BMHeader *ele_head)
{
switch (ele_head->htype) {
case BM_VERT:
return (reinterpret_cast<BMVert_OFlag *>(ele_head))->oflags;
case BM_EDGE:
return (reinterpret_cast<BMEdge_OFlag *>(ele_head))->oflags;
default:
return (reinterpret_cast<BMFace_OFlag *>(ele_head))->oflags;
}
}
#define BMO_elem_flag_test(bm, ele, oflag) \
_bmo_elem_flag_test(bm, BMO_elem_flag_from_header(&(ele)->head), oflag)
#define BMO_elem_flag_test_bool(bm, ele, oflag) \
_bmo_elem_flag_test_bool(bm, BMO_elem_flag_from_header(&(ele)->head), oflag)
#define BMO_elem_flag_enable(bm, ele, oflag) \
_bmo_elem_flag_enable( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
#define BMO_elem_flag_disable(bm, ele, oflag) \
_bmo_elem_flag_disable( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
#define BMO_elem_flag_set(bm, ele, oflag, val) \
_bmo_elem_flag_set(bm, \
(BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), \
oflag, \
val)
#define BMO_elem_flag_toggle(bm, ele, oflag) \
_bmo_elem_flag_toggle( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
/* take care not to instantiate args multiple times */
#ifdef __GNUC___
# define _BMO_CAST_V_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_VERT(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_VERT), \
(const BMVert_OFlag *)_e); \
})
# define _BMO_CAST_E_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_EDGE(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_EDGE), \
(const BMEdge_OFlag *)_e); \
})
# define _BMO_CAST_F_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_FACE(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_FACE), \
(const BMFace_OFlag *)_e); \
})
# define _BMO_CAST_V(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_VERT_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_VERT), \
(BMVert_OFlag *)_e); \
})
# define _BMO_CAST_E(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_EDGE_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_EDGE), \
(BMEdge_OFlag *)_e); \
})
# define _BMO_CAST_F(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_FACE_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_FACE), \
(BMFace_OFlag *)_e); \
})
#else
# define _BMO_CAST_V_CONST(e) (BM_CHECK_TYPE_VERT(e), (const BMVert_OFlag *)e)
# define _BMO_CAST_E_CONST(e) (BM_CHECK_TYPE_EDGE(e), (const BMEdge_OFlag *)e)
# define _BMO_CAST_F_CONST(e) (BM_CHECK_TYPE_FACE(e), (const BMFace_OFlag *)e)
# define _BMO_CAST_V(e) (BM_CHECK_TYPE_VERT_NONCONST(e), (BMVert_OFlag *)e)
# define _BMO_CAST_E(e) (BM_CHECK_TYPE_EDGE_NONCONST(e), (BMEdge_OFlag *)e)
# define _BMO_CAST_F(e) (BM_CHECK_TYPE_FACE_NONCONST(e), (BMFace_OFlag *)e)
#endif
#define BMO_vert_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_V_CONST(e)->oflags, oflag)
#define BMO_vert_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_V_CONST(e)->oflags, oflag)
#define BMO_vert_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_vert_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_vert_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_V(e)->oflags, oflag, val)
#define BMO_vert_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_edge_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_E_CONST(e)->oflags, oflag)
#define BMO_edge_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_E_CONST(e)->oflags, oflag)
#define BMO_edge_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_edge_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_edge_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_E(e)->oflags, oflag, val)
#define BMO_edge_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_face_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_F_CONST(e)->oflags, oflag)
#define BMO_face_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_F_CONST(e)->oflags, oflag)
#define BMO_face_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_F(e)->oflags, oflag)
#define BMO_face_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_F(e)->oflags, oflag)
#define BMO_face_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_F(e)->oflags, oflag, val)
#define BMO_face_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_F(e)->oflags, oflag)
BLI_INLINE short _bmo_elem_flag_test(BMesh *bm, const BMFlagLayer *oflags, short oflag);
BLI_INLINE bool _bmo_elem_flag_test_bool(BMesh *bm, const BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_enable(BMesh *bm, BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_disable(BMesh *bm, BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_set(BMesh *bm, BMFlagLayer *oflags, short oflag, int val);
BLI_INLINE void _bmo_elem_flag_toggle(BMesh *bm, BMFlagLayer *oflags, short oflag);
/* slot type arrays are terminated by the last member
* having a slot type of 0 */
enum eBMOpSlotType {
/* BMO_OP_SLOT_SENTINEL = 0, */
BMO_OP_SLOT_BOOL = 1,
BMO_OP_SLOT_INT = 2,
BMO_OP_SLOT_FLT = 3,
/* normally store pointers to object, scene,
* _never_ store arrays corresponding to mesh elements with this */
BMO_OP_SLOT_PTR = 4, /* requires subtype BMO_OP_SLOT_SUBTYPE_PTR_xxx */
BMO_OP_SLOT_MAT = 5,
BMO_OP_SLOT_VEC = 8,
/* after BMO_OP_SLOT_VEC, everything is dynamically allocated arrays.
* We leave a space in the identifiers for future growth.
*
* it's very important this remain a power of two */
BMO_OP_SLOT_ELEMENT_BUF = 9, /* list of verts/edges/faces */
BMO_OP_SLOT_MAPPING = 10 /* simple hash map, requires subtype BMO_OP_SLOT_SUBTYPE_MAP_xxx */
};
#define BMO_OP_SLOT_TOTAL_TYPES 11
/* don't overlap values to avoid confusion */
enum eBMOpSlotSubType_Elem {
/* use as flags */
BMO_OP_SLOT_SUBTYPE_ELEM_VERT = BM_VERT,
BMO_OP_SLOT_SUBTYPE_ELEM_EDGE = BM_EDGE,
BMO_OP_SLOT_SUBTYPE_ELEM_FACE = BM_FACE,
BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE = (BM_FACE << 1),
};
ENUM_OPERATORS(eBMOpSlotSubType_Elem)
enum eBMOpSlotSubType_Map {
BMO_OP_SLOT_SUBTYPE_MAP_EMPTY = 64, /* use as a set(), unused value */
BMO_OP_SLOT_SUBTYPE_MAP_ELEM = 65,
BMO_OP_SLOT_SUBTYPE_MAP_FLT = 66,
BMO_OP_SLOT_SUBTYPE_MAP_INT = 67,
BMO_OP_SLOT_SUBTYPE_MAP_BOOL = 68,
BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL = 69, /* python can't convert these */
};
enum eBMOpSlotSubType_Ptr {
BMO_OP_SLOT_SUBTYPE_PTR_BMESH = 100,
BMO_OP_SLOT_SUBTYPE_PTR_SCENE = 101,
BMO_OP_SLOT_SUBTYPE_PTR_OBJECT = 102,
BMO_OP_SLOT_SUBTYPE_PTR_MESH = 103,
BMO_OP_SLOT_SUBTYPE_PTR_STRUCT = 104,
};
enum eBMOpSlotSubType_Int {
BMO_OP_SLOT_SUBTYPE_INT_ENUM = 200,
BMO_OP_SLOT_SUBTYPE_INT_FLAG = 201,
};
union eBMOpSlotSubType_Union {
eBMOpSlotSubType_Elem elem;
eBMOpSlotSubType_Ptr ptr;
eBMOpSlotSubType_Map map;
eBMOpSlotSubType_Int intg;
};
struct BMO_FlagSet {
int value;
const char *identifier;
};
/* please ignore all these structures, don't touch them in tool code, except
* for when your defining an operator with BMOpDefine. */
struct BMOpSlot {
const char *slot_name; /* pointer to BMOpDefine.slot_args */
eBMOpSlotType slot_type;
eBMOpSlotSubType_Union slot_subtype;
int len;
// int flag; /* UNUSED */
// int index; /* index within slot array */ /* UNUSED */
union {
int i;
float f;
void *p;
float vec[3];
void **buf;
GHash *ghash;
struct {
/** Don't clobber (i) when assigning flags, see #eBMOpSlotSubType_Int. */
int _i;
BMO_FlagSet *flags;
} enum_data;
} data;
};
/* mainly for use outside bmesh internal code */
#define BMO_SLOT_AS_BOOL(slot) ((slot)->data.i)
#define BMO_SLOT_AS_INT(slot) ((slot)->data.i)
#define BMO_SLOT_AS_FLOAT(slot) ((slot)->data.f)
#define BMO_SLOT_AS_VECTOR(slot) ((slot)->data.vec)
#define BMO_SLOT_AS_MATRIX(slot) ((float (*)[4])((slot)->data.p))
#define BMO_SLOT_AS_BUFFER(slot) ((slot)->data.buf)
#define BMO_SLOT_AS_GHASH(slot) ((slot)->data.ghash)
#define BMO_ASSERT_SLOT_IN_OP(slot, op) \
BLI_assert(((slot >= (op)->slots_in) && (slot < &(op)->slots_in[BMO_OP_MAX_SLOTS])) || \
((slot >= (op)->slots_out) && (slot < &(op)->slots_out[BMO_OP_MAX_SLOTS])))
/* Limit hit, so expanded for bevel operator. Compiler complains if limit is hit. */
#define BMO_OP_MAX_SLOTS 21
/* BMOpDefine->type_flag */
enum BMOpTypeFlag {
BMO_OPTYPE_FLAG_NOP = 0,
/** Switch from multires tangent space to absolute coordinates. */
BMO_OPTYPE_FLAG_UNTAN_MULTIRES = (1 << 0),
BMO_OPTYPE_FLAG_NORMALS_CALC = (1 << 1),
BMO_OPTYPE_FLAG_SELECT_FLUSH = (1 << 2),
BMO_OPTYPE_FLAG_SELECT_VALIDATE = (1 << 3),
BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL = (1 << 4),
};
ENUM_OPERATORS(BMOpTypeFlag)
struct BMOperator {
struct BMOpSlot slots_in[BMO_OP_MAX_SLOTS];
struct BMOpSlot slots_out[BMO_OP_MAX_SLOTS];
void (*exec)(BMesh *bm, struct BMOperator *op);
struct MemArena *arena;
int type;
BMOpTypeFlag type_flag;
int flag; /* runtime options */
};
enum {
BMO_FLAG_RESPECT_HIDE = 1,
};
#define BMO_FLAG_DEFAULTS BMO_FLAG_RESPECT_HIDE
#define MAX_SLOTNAME 32
struct BMOSlotType {
char name[MAX_SLOTNAME];
eBMOpSlotType type;
eBMOpSlotSubType_Union subtype;
BMO_FlagSet *enum_flags;
};
struct BMOpDefine {
const char *opname;
BMOSlotType slot_types_in[BMO_OP_MAX_SLOTS];
BMOSlotType slot_types_out[BMO_OP_MAX_SLOTS];
/**
* Optional initialize function.
* Can be used for setting defaults.
*/
void (*init)(BMOperator *op);
void (*exec)(BMesh *bm, BMOperator *op);
BMOpTypeFlag type_flag;
};
/* -------------------------------------------------------------------- */
/** \name BMesh Operator API
*
* \note data types that use pointers (arrays, etc) must _never_ have it set directly.
* Don't #BMO_slot_ptr_set to pass in a list of edges or any arrays.
* \{ */
/**
* \brief BMESH OPSTACK INIT OP
*
* Initializes an operator structure to a certain type
*/
void BMO_op_init(BMesh *bm, BMOperator *op, int flag, const char *opname);
/**
* \brief BMESH OPSTACK EXEC OP
*
* Executes a passed in operator.
*
* This handles the allocation and freeing of temporary tool flag
* layers and starting/stopping the modeling loop.
* Can be called from other operators exec callbacks as well.
*/
void BMO_op_exec(BMesh *bm, BMOperator *op);
/**
* \brief BMESH OPSTACK FINISH OP
*
* Does housekeeping chores related to finishing up an operator.
*
* \note the operator's tool flag is removed after it finishes executing in #BMO_op_exec.
*/
void BMO_op_finish(BMesh *bm, BMOperator *op);
/**
* Count the number of elements with the specified flag enabled.
* type can be a bit-mask of #BM_FACE, #BM_EDGE, or #BM_FACE.
*/
int BMO_mesh_enabled_flag_count(BMesh *bm, char htype, short oflag);
/**
* Count the number of elements with the specified flag disabled.
* type can be a bit-mask of #BM_FACE, #BM_EDGE, or #BM_FACE.
*/
int BMO_mesh_disabled_flag_count(BMesh *bm, char htype, short oflag);
/**
* \brief BMESH OPSTACK PUSH
*
* Pushes the operator-stack down one level and allocates a new flag layer if appropriate.
*/
void BMO_push(BMesh *bm, BMOperator *op);
/**
* \brief BMESH OPSTACK POP
*
* Pops the operator-stack one level and frees a flag layer if appropriate
*
* BMESH_TODO: investigate NOT freeing flag layers.
*/
void BMO_pop(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Formatted Operator Initialization/Execution
*
* Format Strings for #BMOperator Initialization.
*
* This system is used to execute or initialize an operator,
* using a formatted-string system.
*
* The basic format for the format string is:
* `[operatorname] [slot_name]=%[code] [slot_name]=%[code]`
*
* Example:
*
* \code{.c}
* BMO_op_callf(bm, BMO_FLAG_DEFAULTS,
* "delete context=%i geom=%hv",
* DEL_ONLYFACES, BM_ELEM_SELECT);
* \endcode
* **Primitive Types**
* - `b` - boolean (same as int but 1/0 only). #BMO_OP_SLOT_BOOL
* - `i` - int. #BMO_OP_SLOT_INT
* - `f` - float. #BMO_OP_SLOT_FLT
* - `p` - pointer (normally to a Scene/Mesh/Object/BMesh). #BMO_OP_SLOT_PTR
* - `m3` - 3x3 matrix of floats. #BMO_OP_SLOT_MAT
* - `m4` - 4x4 matrix of floats. #BMO_OP_SLOT_MAT
* - `v` - 3D vector of floats. #BMO_OP_SLOT_VEC
* **Utility**
*
* Pass an existing slot which is copied to either an input or output slot.
* Taking the operator and slot-name pair of args (BMOperator *, const char *).
* - `s` - slot_in (lower case)
* - `S` - slot_out (upper case)
* **Element Buffer** (#BMO_OP_SLOT_ELEMENT_BUF)
* - `e` - single element vert/edge/face (use with #BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE).
* - `eb` - elem buffer, take an array and a length.
* - `av` - all verts
* - `ae` - all edges
* - `af` - all faces
* - `hv` - header flagged verts (hflag)
* - `he` - header flagged edges (hflag)
* - `hf` - header flagged faces (hflag)
* - `Hv` - header flagged verts (hflag off)
* - `He` - header flagged edges (hflag off)
* - `Hf` - header flagged faces (hflag off)
* - `fv` - flagged verts (oflag)
* - `fe` - flagged edges (oflag)
* - `ff` - flagged faces (oflag)
* - `Fv` - flagged verts (oflag off)
* - `Fe` - flagged edges (oflag off)
* - `Ff` - flagged faces (oflag off)
*
* \note The common v/e/f suffix can be mixed,
* so `avef` is can be used for all verts, edges and faces.
* Order is not important so `Hfev` is also valid (all un-flagged verts, edges and faces).
*
* \{ */
/** Executes an operator. */
bool BMO_op_callf(BMesh *bm, int flag, const char *fmt, ...);
/** A `va_list` version of #BMO_op_callf. */
bool BMO_op_vcallf(BMesh *bm, int flag, const char *fmt, va_list list);
/**
* Initializes, but doesn't execute an operator. this is so you can
* gain access to the outputs of the operator. note that you have
* to execute/finish (BMO_op_exec and BMO_op_finish) yourself.
*/
bool BMO_op_initf(BMesh *bm, BMOperator *op, int flag, const char *fmt, ...);
/**
* A `va_list` version, used to implement the above two functions,
* plus #EDBM_op_callf in editmesh_utils.cc.
*/
bool BMO_op_vinitf(BMesh *bm, BMOperator *op, int flag, const char *fmt, va_list vlist);
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Slot Access
* \{ */
/**
* \brief BMESH OPSTACK HAS SLOT
*
* \return Success if the slot if found.
*/
bool BMO_slot_exists(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier);
/* get a pointer to a slot. this may be removed layer on from the public API. */
/**
* \brief BMESH OPSTACK GET SLOT
*
* Returns a pointer to the slot of type 'slot_code'
*/
BMOpSlot *BMO_slot_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier);
/* copies the data of a slot from one operator to another. src and dst are the
* source/destination slot codes, respectively. */
#define BMO_slot_copy(op_src, slots_src, slot_name_src, op_dst, slots_dst, slot_name_dst) \
_bmo_slot_copy( \
(op_src)->slots_src, slot_name_src, (op_dst)->slots_dst, slot_name_dst, (op_dst)->arena)
/**
* \brief BMESH OPSTACK COPY SLOT
*
* define used.
* Copies data from one slot to another.
*/
void _bmo_slot_copy(BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
const char *slot_name_src,
BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
const char *slot_name_dst,
struct MemArena *arena_dst);
/** \} */
/** Delete "context" slot values, used for operator too. */
enum {
DEL_VERTS = 1,
DEL_EDGES,
DEL_ONLYFACES,
DEL_EDGESFACES,
DEL_FACES,
/* A version of 'DEL_FACES' that keeps edges on face boundaries,
* allowing the surrounding edge-loop to be kept from removed face regions. */
DEL_FACES_KEEP_BOUNDARY,
DEL_ONLYTAGGED,
};
enum BMO_SymmDirection {
BMO_SYMMETRIZE_NEGATIVE_X,
BMO_SYMMETRIZE_NEGATIVE_Y,
BMO_SYMMETRIZE_NEGATIVE_Z,
BMO_SYMMETRIZE_POSITIVE_X,
BMO_SYMMETRIZE_POSITIVE_Y,
BMO_SYMMETRIZE_POSITIVE_Z,
};
enum BMO_Delimit {
BMO_DELIM_NORMAL = 1 << 0,
BMO_DELIM_MATERIAL = 1 << 1,
BMO_DELIM_SEAM = 1 << 2,
BMO_DELIM_SHARP = 1 << 3,
BMO_DELIM_UV = 1 << 4,
};
ENUM_OPERATORS(BMO_Delimit)
void BMO_op_flag_enable(BMesh *bm, BMOperator *op, int op_flag);
void BMO_op_flag_disable(BMesh *bm, BMOperator *op, int op_flag);
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Slot Get/Set
* \{ */
void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float f);
float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int i);
int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, bool i);
bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* Return a copy of the element buffer.
*/
void *BMO_slot_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int *len);
/**
* Don't pass in arrays that are supposed to map to elements this way.
*
* so, e.g. passing in list of floats per element in another slot is bad.
* passing in, e.g. pointer to an edit-mesh for the conversion operator is fine though.
*/
void BMO_slot_ptr_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, void *p);
void *BMO_slot_ptr_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_vec_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const float vec[3]);
void BMO_slot_vec_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float r_vec[3]);
/**
* Only supports square matrices.
* size must be 3 or 4; this API is meant only for transformation matrices.
*
* \note the matrix is stored in 4x4 form, and it's safe to call whichever function you want.
*/
void BMO_slot_mat_set(BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const float *mat,
int size);
void BMO_slot_mat4_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
float r_mat[4][4]);
void BMO_slot_mat3_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
float r_mat[3][3]);
/** \} */
void BMO_mesh_flag_disable_all(BMesh *bm, BMOperator *op, char htype, short oflag);
void BMO_mesh_selected_remap(BMesh *bm,
BMOpSlot *slot_vert_map,
BMOpSlot *slot_edge_map,
BMOpSlot *slot_face_map,
bool check_select);
/**
* Copies the values from another slot to the end of the output slot.
*/
#define BMO_slot_buffer_append( \
op_src, slots_src, slot_name_src, op_dst, slots_dst, slot_name_dst) \
_bmo_slot_buffer_append( \
(op_src)->slots_src, slot_name_src, (op_dst)->slots_dst, slot_name_dst, (op_dst)->arena)
/**
* Copies the values from another slot to the end of the output slot.
*/
void _bmo_slot_buffer_append(BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
const char *slot_name_dst,
BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
const char *slot_name_src,
struct MemArena *arena_dst);
/**
* Puts every element of type 'type' (which is a bit-mask) with tool flag 'flag', into a slot.
*/
void BMO_slot_buffer_from_enabled_flag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* Puts every element of type 'type' (which is a bit-mask) without tool flag 'flag', into a slot.
*/
void BMO_slot_buffer_from_disabled_flag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Flags elements in a slots buffer
*/
void BMO_slot_buffer_flag_enable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Removes flags from elements in a slots buffer
*/
void BMO_slot_buffer_flag_disable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Header Flags elements in a slots buffer, automatically
* using the selection API where appropriate.
*/
void BMO_slot_buffer_hflag_enable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag,
bool do_flush);
/**
* \brief BMO_FLAG_BUFFER
*
* Removes flags from elements in a slots buffer, automatically
* using the selection API where appropriate.
*/
void BMO_slot_buffer_hflag_disable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag,
bool do_flush);
/**
* Puts every element of type 'type' (which is a bit-mask) with header flag 'flag', into a slot.
* \note ignores hidden elements (e.g. elements with header flag BM_ELEM_HIDDEN set).
*/
void BMO_slot_buffer_from_enabled_hflag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag);
/**
* Puts every element of type 'type' (which is a bit-mask) without header flag 'flag', into a slot.
* \note ignores hidden elements (e.g. elements with header flag BM_ELEM_HIDDEN set).
*/
void BMO_slot_buffer_from_disabled_hflag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag);
void BMO_slot_buffer_from_array(BMOperator *op,
BMOpSlot *slot,
BMHeader **ele_buffer,
int ele_buffer_len);
void BMO_slot_buffer_from_single(BMOperator *op, BMOpSlot *slot, BMHeader *ele);
void *BMO_slot_buffer_get_single(BMOpSlot *slot);
/** Return the number of elements inside a slot array. */
int BMO_slot_buffer_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/** Return the number of elements inside a slot map. */
int BMO_slot_map_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* Inserts a key/value mapping into a mapping slot. note that it copies the
* value, it doesn't store a reference to it.
*/
void BMO_slot_map_insert(BMOperator *op, BMOpSlot *slot, const void *element, const void *data);
/**
* Flags all elements in a mapping.
* \note that the mapping must only have #BMesh elements in it.
*/
void BMO_slot_map_to_flag(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
void *BMO_slot_buffer_alloc(BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
int len);
/**
* \brief BMO_ALL_TO_SLOT
*
* Copies all elements of a certain type into an operator slot.
*/
void BMO_slot_buffer_from_all(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype);
/**
* This part of the API is used to iterate over element buffer or
* mapping slots.
*
* for example, iterating over the faces in a slot is:
*
* \code{.c}
*
* BMOIter oiter;
* BMFace *f;
*
* f = BMO_iter_new(&oiter, some_operator, "slot_name", BM_FACE);
* for (; f; f = BMO_iter_step(&oiter)) {
* // do something with the face
* }
*
* another example, iterating over a mapping:
* BMOIter oiter;
* void *key;
* void *val;
*
* key = BMO_iter_new(&oiter, bm, some_operator, "slot_name", 0);
* for (; key; key = BMO_iter_step(&oiter)) {
* val = BMO_iter_map_value(&oiter);
* //do something with the key/val pair
* //note that val is a pointer to the val data,
* //whether it's a float, pointer, whatever.
* //
* // so to get a pointer, for example, use:
* // *((void **)BMO_iter_map_value(&oiter));
* //or something like that.
* }
* \endcode
*/
/* contents of this structure are private,
* don't directly access. */
struct BMOIter {
BMOpSlot *slot;
int cur; // for arrays
GHashIterator giter;
void **val;
/** Bit-wise '&' with #BMHeader.htype */
char restrictmask;
};
void *BMO_slot_buffer_get_first(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* \brief New Iterator
*
* \param restrictmask: restricts the iteration to certain element types
* (e.g. combination of BM_VERT, BM_EDGE, BM_FACE), if iterating
* over an element buffer (not a mapping). */
void *BMO_iter_new(BMOIter *iter,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask);
void *BMO_iter_step(BMOIter *iter);
/**
* Returns a pointer to the key-value when iterating over mappings.
* remember for pointer maps this will be a pointer to a pointer.
*/
void **BMO_iter_map_value_p(BMOIter *iter);
void *BMO_iter_map_value_ptr(BMOIter *iter);
float BMO_iter_map_value_float(BMOIter *iter);
int BMO_iter_map_value_int(BMOIter *iter);
bool BMO_iter_map_value_bool(BMOIter *iter);
#define BMO_ITER(ele, iter, slot_args, slot_name, restrict_flag) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_new(iter, slot_args, slot_name, restrict_flag); \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_step(iter))
#define BMO_ITER_INDEX(ele, iter, slot_args, slot_name, restrict_flag, i_) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_new(iter, slot_args, slot_name, restrict_flag), \
i_ = 0; \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_step(iter), i_++)
/* operator slot type information - size of one element of the type given. */
extern const int BMO_OPSLOT_TYPEINFO[BMO_OP_SLOT_TOTAL_TYPES];
int BMO_opcode_from_opname(const char *opname);
} // namespace blender

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline operator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
namespace blender {
struct BMOperator;
/* Tool Flag API: Tool code must never put junk in header flags (#BMHeader.hflag)
* instead, use this API to set flags.
* If you need to store a value per element, use a #GHash or a mapping slot to do it. */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
short _bmo_elem_flag_test(BMesh *bm, const BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
return oflags[bm->toolflag_index].f & oflag;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool _bmo_elem_flag_test_bool(BMesh *bm, const BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
return (oflags[bm->toolflag_index].f & oflag) != 0;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_enable(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f |= oflag;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_disable(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f &= short(~oflag);
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_set(BMesh *bm, BMFlagLayer *oflags, const short oflag, int val)
{
BLI_assert(bm->use_toolflags);
if (val) {
oflags[bm->toolflag_index].f |= oflag;
}
else {
oflags[bm->toolflag_index].f &= short(~oflag);
}
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_toggle(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f ^= oflag;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_int_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const int val)
{
union {
void *ptr;
int val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INT);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_bool_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const bool val)
{
union {
void *ptr;
bool val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_BOOL);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_float_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const float val)
{
union {
void *ptr;
float val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_FLT);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
/* pointer versions of BMO_slot_map_float_get and BMO_slot_map_float_insert.
*
* do NOT use these for non-operator-api-allocated memory! instead
* use BMO_slot_map_data_get and BMO_slot_map_insert, which copies the data. */
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_ptr_insert(BMOperator *op,
BMOpSlot *slot,
const void *element,
void *val)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL);
BMO_slot_map_insert(op, slot, element, val);
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_elem_insert(BMOperator *op,
BMOpSlot *slot,
const void *element,
void *val)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_ELEM);
BMO_slot_map_insert(op, slot, element, val);
}
/* no values */
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_empty_insert(BMOperator *op, BMOpSlot *slot, const void *element)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_EMPTY);
BMO_slot_map_insert(op, slot, element, nullptr);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BMO_slot_map_contains(BMOpSlot *slot, const void *element)
{
BLI_assert(slot->slot_type == BMO_OP_SLOT_MAPPING);
return BLI_ghash_haskey(slot->data.ghash, element);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void **BMO_slot_map_data_get(BMOpSlot *slot, const void *element)
{
return BLI_ghash_lookup_p(slot->data.ghash, element);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
float BMO_slot_map_float_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_FLT);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<float *>(data);
}
return 0.0f;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
int BMO_slot_map_int_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INT);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<int *>(data);
}
return 0;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BMO_slot_map_bool_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_BOOL);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<bool *>(data);
}
return false;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BMO_slot_map_ptr_get(BMOpSlot *slot, const void *element)
{
void **val = BMO_slot_map_data_get(slot, element);
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL);
if (val) {
return *val;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BMO_slot_map_elem_get(BMOpSlot *slot, const void *element)
{
void **val = static_cast<void **>(BMO_slot_map_data_get(slot, element));
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_ELEM);
if (val) {
return *val;
}
return nullptr;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,241 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "intern/bmesh_operator_api.hh"
namespace blender {
/* See comments in `intern/bmesh_opdefines.cc` for documentation of specific operators. */
/*--------defines/enumerations for specific operators-------*/
/* Quad `innervert` values. */
enum {
SUBD_CORNER_INNERVERT,
SUBD_CORNER_PATH,
SUBD_CORNER_FAN,
SUBD_CORNER_STRAIGHT_CUT,
};
/* aligned with PROP_SMOOTH and friends */
enum {
SUBD_FALLOFF_SMOOTH = 0,
SUBD_FALLOFF_SPHERE,
SUBD_FALLOFF_ROOT,
SUBD_FALLOFF_SHARP,
SUBD_FALLOFF_LIN,
SUBD_FALLOFF_INVSQUARE = 7, /* matching PROP_INVSQUARE */
};
enum {
SUBDIV_SELECT_NONE,
SUBDIV_SELECT_ORIG,
SUBDIV_SELECT_INNER,
SUBDIV_SELECT_LOOPCUT,
};
/* subdivide_edgering */
enum {
/* just subdiv */
SUBD_RING_INTERP_LINEAR,
/* single bezier spline - curve follows bezier rotation */
SUBD_RING_INTERP_PATH,
/* beziers based on adjacent faces (fallback to tangent) */
SUBD_RING_INTERP_SURF,
};
/* similar face selection slot values */
enum {
SIMFACE_MATERIAL = 201,
SIMFACE_AREA,
SIMFACE_SIDES,
SIMFACE_PERIMETER,
SIMFACE_NORMAL,
SIMFACE_COPLANAR,
SIMFACE_SMOOTH,
SIMFACE_FREESTYLE,
};
/* similar edge selection slot values */
enum {
SIMEDGE_LENGTH = 101,
SIMEDGE_DIR,
SIMEDGE_FACE,
SIMEDGE_FACE_ANGLE,
SIMEDGE_CREASE,
SIMEDGE_BEVEL,
SIMEDGE_SEAM,
SIMEDGE_SHARP,
SIMEDGE_FREESTYLE,
};
/* similar vertex selection slot values */
enum {
SIMVERT_NORMAL = 0,
SIMVERT_FACE,
SIMVERT_VGROUP,
SIMVERT_EDGE,
SIMVERT_CREASE,
};
/* Poke face center calculation */
enum {
BMOP_POKE_MEDIAN_WEIGHTED = 0,
BMOP_POKE_MEDIAN,
BMOP_POKE_BOUNDS,
};
/* Bevel offset_type slot values */
enum {
BEVEL_AMT_OFFSET,
BEVEL_AMT_WIDTH,
BEVEL_AMT_DEPTH,
BEVEL_AMT_PERCENT,
BEVEL_AMT_ABSOLUTE,
};
/* Bevel profile type */
enum {
BEVEL_PROFILE_SUPERELLIPSE,
BEVEL_PROFILE_CUSTOM,
};
/* Bevel face_strength_mode values: should match face_str mode enum in DNA_modifier_types.h */
enum {
BEVEL_FACE_STRENGTH_NONE,
BEVEL_FACE_STRENGTH_NEW,
BEVEL_FACE_STRENGTH_AFFECTED,
BEVEL_FACE_STRENGTH_ALL,
};
/* Bevel miter slot values */
enum {
BEVEL_MITER_SHARP,
BEVEL_MITER_PATCH,
BEVEL_MITER_ARC,
};
/* Bevel vertex mesh creation methods */
enum {
BEVEL_VMESH_ADJ,
BEVEL_VMESH_CUTOFF,
};
/* Bevel affect option. */
enum {
BEVEL_AFFECT_VERTICES = 0,
BEVEL_AFFECT_EDGES = 1,
};
/* Normal Face Strength values */
enum {
FACE_STRENGTH_WEAK = -16384,
FACE_STRENGTH_MEDIUM = 0,
FACE_STRENGTH_STRONG = 16384,
};
/** Interpolation method used for spacing vertices. */
enum SpaceInterpolationMethod {
SPACE_EDGE_LOOPS_EVENLY_INTERP_CUBIC = 0,
SPACE_EDGE_LOOPS_EVENLY_INTERP_LINEAR = 1,
};
/**
* Methods for determining the orientation of flattening the plane.
*/
enum FlattenMethod {
FLATTEN_BEST_FIT = 0,
FLATTEN_NORMAL = 1,
FLATTEN_VIEW = 2,
};
extern const BMOpDefine *bmo_opdefines[];
extern const int bmo_opdefines_total;
/*------specific operator helper functions-------*/
void BM_mesh_esubdivide(BMesh *bm,
char edge_hflag,
float smooth,
short smooth_falloff,
bool use_smooth_even,
float fractal,
float along_normal,
int numcuts,
int seltype,
int cornertype,
short use_single_edge,
short use_grid_fill,
short use_only_quads,
int seed);
/**
* Fills first available UV-map with grid-like UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on
* \param x_segments: The x-resolution of the grid
* \param y_segments: The y-resolution of the grid
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_grid(
BMesh *bm, uint x_segments, uint y_segments, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with spherical projected UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_sphere(BMesh *bm, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with 2D projected UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on.
* \param mat: The transform matrix applied to the created circle.
* \param radius: The size of the circle.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_circle(
BMesh *bm, float mat[4][4], float radius, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with cylinder/cone-like UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on.
* \param mat: The transform matrix applied to the created cone/cylinder.
* \param radius_top: The size of the top end of the cone/cylinder.
* \param radius_bottom: The size of the bottom end of the cone/cylinder.
* \param segments: The number of subdivisions in the sides of the cone/cylinder.
* \param cap_ends: Whether the ends of the cone/cylinder are filled or not.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_cone(BMesh *bm,
float mat[4][4],
float radius_top,
float radius_bottom,
int segments,
bool cap_ends,
short oflag,
int cd_loop_uv_offset);
/**
* Fills first available UV-map with cube-like UVs for all faces with `oflag` set.
*
* \note Expects tagged faces to be six quads.
* \note Caller must order faces for correct alignment.
*
* \param bm: The BMesh to operate on.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_cube(BMesh *bm, short oflag);
} // namespace blender
#include "intern/bmesh_operator_api_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct BMOperator;
struct BMesh;
void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op);
void bmo_beautify_fill_exec(BMesh *bm, BMOperator *op);
void bmo_bevel_exec(BMesh *bm, BMOperator *op);
void bmo_bisect_edges_exec(BMesh *bm, BMOperator *op);
void bmo_bisect_plane_exec(BMesh *bm, BMOperator *op);
void bmo_bmesh_to_mesh_exec(BMesh *bm, BMOperator *op);
void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op);
void bmo_collapse_exec(BMesh *bm, BMOperator *op);
void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_concave_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_nonplanar_exec(BMesh *bm, BMOperator *op);
void bmo_connect_vert_pair_exec(BMesh *bm, BMOperator *op);
void bmo_contextual_create_exec(BMesh *bm, BMOperator *op);
void bmo_convex_hull_exec(BMesh *bm, BMOperator *op);
void bmo_circularize_exec(BMesh *bm, BMOperator *op);
void bmo_create_circle_exec(BMesh *bm, BMOperator *op);
void bmo_create_cone_exec(BMesh *bm, BMOperator *op);
void bmo_create_cube_exec(BMesh *bm, BMOperator *op);
void bmo_create_grid_exec(BMesh *bm, BMOperator *op);
void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_monkey_exec(BMesh *bm, BMOperator *op);
void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_vert_exec(BMesh *bm, BMOperator *op);
void bmo_delete_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_edges_init(BMOperator *op);
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op);
void bmo_duplicate_exec(BMesh *bm, BMOperator *op);
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op);
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op);
void bmo_holes_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_discrete_faces_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op);
void bmo_find_doubles_exec(BMesh *bm, BMOperator *op);
void bmo_flatten_exec(BMesh *bm, BMOperator *op);
void bmo_grid_fill_exec(BMesh *bm, BMOperator *op);
void bmo_inset_individual_exec(BMesh *bm, BMOperator *op);
void bmo_inset_region_exec(BMesh *bm, BMOperator *op);
void bmo_join_triangles_exec(BMesh *bm, BMOperator *op);
void bmo_mesh_to_bmesh_exec(BMesh *bm, BMOperator *op);
void bmo_mirror_exec(BMesh *bm, BMOperator *op);
void bmo_object_load_bmesh_exec(BMesh *bm, BMOperator *op);
void bmo_pointmerge_exec(BMesh *bm, BMOperator *op);
void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op);
void bmo_recalc_face_normals_exec(BMesh *bm, BMOperator *op);
void bmo_poke_exec(BMesh *bm, BMOperator *op);
void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op);
void bmo_planar_faces_exec(BMesh *bm, BMOperator *op);
void bmo_region_extend_exec(BMesh *bm, BMOperator *op);
void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_colors_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_faces_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_colors_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_edges_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_scale_exec(BMesh *bm, BMOperator *op);
void bmo_smooth_vert_exec(BMesh *bm, BMOperator *op);
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op);
void bmo_solidify_face_region_exec(BMesh *bm, BMOperator *op);
void bmo_space_edge_loops_evenly_exec(BMesh *bm, BMOperator *op);
void bmo_spin_exec(BMesh *bm, BMOperator *op);
void bmo_split_edges_exec(BMesh *bm, BMOperator *op);
void bmo_split_exec(BMesh *bm, BMOperator *op);
void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op);
void bmo_subdivide_edgering_exec(BMesh *bm, BMOperator *op);
void bmo_symmetrize_exec(BMesh *bm, BMOperator *op);
void bmo_transform_exec(BMesh *bm, BMOperator *op);
void bmo_translate_exec(BMesh *bm, BMOperator *op);
void bmo_triangle_fill_exec(BMesh *bm, BMOperator *op);
void bmo_triangulate_exec(BMesh *bm, BMOperator *op);
void bmo_unsubdivide_exec(BMesh *bm, BMOperator *op);
void bmo_weld_verts_exec(BMesh *bm, BMOperator *op);
void bmo_wireframe_exec(BMesh *bm, BMOperator *op);
void bmo_flip_quad_tessellation_exec(BMesh *bm, BMOperator *op);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct Heap;
#include "BLI_compiler_attrs.h"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
/**
* For tools that insist on using triangles, ideally we would cache this data.
*
* \param use_fixed_quad: When true,
* always split quad along (0 -> 2) regardless of concave corners,
* (as done in #BM_mesh_calc_tessellation).
* \param r_loops: Store face loop pointers, (f->len)
* \param r_index: Store triangle triples, indices into \a r_loops, `((f->len - 2) * 3)`
*/
void BM_face_calc_tessellation(const BMFace *f,
bool use_fixed_quad,
BMLoop **r_loops,
uint (*r_index)[3]);
/**
* Return a point inside the face.
*/
void BM_face_calc_point_in_face(const BMFace *f, float r_co[3]);
/**
* \brief BMESH UPDATE FACE NORMAL
*
* Updates the stored normal for the
* given face. Requires that a buffer
* of sufficient length to store projected
* coordinates for all of the face's vertices
* is passed in as well.
*/
float BM_face_calc_normal(const BMFace *f, float r_no[3]) ATTR_NONNULL();
/* exact same as 'BM_face_calc_normal' but accepts vertex coords */
float BM_face_calc_normal_vcos(const BMesh *bm,
const BMFace *f,
float r_no[3],
Span<float3> vertexCos) ATTR_NONNULL();
/**
* Calculate a normal from a vertex cloud.
*/
void BM_verts_calc_normal_from_cloud_ex(
BMVert **varr, int varr_len, float r_normal[3], float r_center[3], int *r_index_tangent);
void BM_verts_calc_normal_from_cloud(BMVert **varr, int varr_len, float r_normal[3]);
/**
* Calculates the face subset normal.
*/
float BM_face_calc_normal_subset(const BMLoop *l_first, const BMLoop *l_last, float r_no[3])
ATTR_NONNULL();
/**
* get the area of the face
*/
float BM_face_calc_area(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get the area of the face in world space.
*/
float BM_face_calc_area_with_mat3(const BMFace *f, const float mat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Calculate the signed area of UV face.
*/
float BM_face_calc_area_uv_signed(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Calculate the area of UV face.
*/
float BM_face_calc_area_uv(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* compute the perimeter of an ngon
*/
float BM_face_calc_perimeter(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Calculate the perimeter of a ngon in world space.
*/
float BM_face_calc_perimeter_with_mat3(const BMFace *f,
const float mat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Compute the tangent of the face, using the longest edge.
*/
void BM_face_calc_tangent_from_edge(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
void BM_face_calc_tangent_pair_from_edge(const BMFace *f,
float r_tangent_a[3],
float r_tangent_b[3]);
/**
* Compute the tangent of the face, using the two longest disconnected edges.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_face_calc_tangent_from_edge_pair(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute the tangent of the face, using the edge farthest away from any vertex in the face.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_face_calc_tangent_from_edge_diagonal(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute the tangent of the face, using longest distance between vertices on the face.
*
* \note The logic is almost identical to #BM_face_calc_tangent_edge_diagonal
*/
void BM_face_calc_tangent_from_vert_diagonal(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute a meaningful direction along the face (use for gizmo axis).
*
* \note Callers shouldn't depend on the *exact* method used here.
*/
void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* A version of BM_face_calc_tangent_auto that calculates two tangents.
* Useful when one may not be usable.
*/
void BM_face_calc_tangent_pair_auto(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3])
ATTR_NONNULL();
/**
* computes center of face in 3d. uses center of bounding box.
*/
void BM_face_calc_center_bounds(const BMFace *f, float r_cent[3]) ATTR_NONNULL();
/**
* computes center of face in 3d. uses center of bounding box.
*/
void BM_face_calc_center_bounds_vcos(const BMesh *bm,
const BMFace *f,
float r_center[3],
Span<float3> vert_positions) ATTR_NONNULL();
/**
* computes the center of a face, using the mean average
*/
void BM_face_calc_center_median(const BMFace *f, float r_center[3]) ATTR_NONNULL();
/* exact same as 'BM_face_calc_normal' but accepts vertex coords */
void BM_face_calc_center_median_vcos(const BMesh *bm,
const BMFace *f,
float r_center[3],
const Span<float3> vert_positions) ATTR_NONNULL();
/**
* computes the center of a face, using the mean average
* weighted by edge length
*/
void BM_face_calc_center_median_weighted(const BMFace *f, float r_cent[3]) ATTR_NONNULL();
/**
* expands bounds (min/max must be initialized).
*/
void BM_face_calc_bounds_expand(const BMFace *f, float min[3], float max[3]);
void BM_face_normal_update(BMFace *f) ATTR_NONNULL();
/**
* updates face and vertex normals incident on an edge
*/
void BM_edge_normals_update(BMEdge *e) ATTR_NONNULL();
bool BM_vert_calc_normal_ex(const BMVert *v, char hflag, float r_no[3]);
bool BM_vert_calc_normal(const BMVert *v, float r_no[3]);
/**
* update a vert normal (but not the faces incident on it)
*/
void BM_vert_normal_update(BMVert *v) ATTR_NONNULL();
void BM_vert_normal_update_all(BMVert *v) ATTR_NONNULL();
/**
* \brief Face Flip Normal
*
* Reverses the winding of a face.
* \note This updates the calculated normal.
*/
void BM_face_normal_flip_ex(BMesh *bm,
BMFace *f,
int cd_loop_mdisp_offset,
bool use_loop_mdisp_flip) ATTR_NONNULL();
void BM_face_normal_flip(BMesh *bm, BMFace *f) ATTR_NONNULL();
/**
* BM POINT IN FACE
*
* Projects co onto face f, and returns true if it is inside
* the face bounds.
*
* \note this uses a best-axis projection test,
* instead of projecting co directly into f's orientation space,
* so there might be accuracy issues.
*/
bool BM_face_point_inside_test(const BMFace *f, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BMESH TRIANGULATE FACE
*
* Breaks all quads and ngons down to triangles.
* It uses poly-fill for the ngons splitting, and
* the beautify operator when use_beauty is true.
*
* \param r_faces_new: if non-null, must be an array of BMFace pointers,
* with a length equal to (f->len - 3). It will be filled with the new
* triangles (not including the original triangle).
*
* \param r_faces_double: When newly created faces are duplicates of existing faces,
* they're added to this list. Caller must handle de-duplication.
* This is done because its possible _all_ faces exist already,
* and in that case we would have to remove all faces including the one passed,
* which causes complications adding/removing faces while looking over them.
*
* \note The number of faces is _almost_ always (f->len - 3),
* However there may be faces that already occupying the
* triangles we would make, so the caller must check \a r_faces_new_tot.
*
* \note use_tag tags new flags and edges.
*/
void BM_face_triangulate(BMesh *bm,
BMFace *f,
BMFace **r_faces_new,
int *r_faces_new_tot,
BMEdge **r_edges_new,
int *r_edges_new_tot,
struct LinkNode **r_faces_double,
int quad_method,
int ngon_method,
bool use_tag,
struct MemArena *pf_arena,
struct Heap *pf_heap) ATTR_NONNULL(1, 2);
/**
* each pair of loops defines a new edge, a split. this function goes
* through and sets pairs that are geometrically invalid to null. a
* split is invalid, if it forms a concave angle or it intersects other
* edges in the face, or it intersects another split. in the case of
* intersecting splits, only the first of the set of intersecting
* splits survives
*/
void BM_face_splits_check_legal(BMesh *bm, BMFace *f, BMLoop *(*loops)[2], int len) ATTR_NONNULL();
/**
* This simply checks that the verts don't connect faces which would have more optimal splits.
* but _not_ check for correctness.
*/
void BM_face_splits_check_optimal(BMFace *f, BMLoop *(*loops)[2], int len) ATTR_NONNULL();
/**
* Small utility functions for fast access
*
* faster alternative to:
* BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 3);
*/
void BM_face_as_array_vert_tri(BMFace *f, BMVert *r_verts[3]) ATTR_NONNULL();
/**
* faster alternative to:
* BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 4);
*/
void BM_face_as_array_vert_quad(BMFace *f, BMVert *r_verts[4]) ATTR_NONNULL();
/**
* Small utility functions for fast access
*
* faster alternative to:
* BM_iter_as_array(bm, BM_LOOPS_OF_FACE, f, (void **)l, 3);
*/
void BM_face_as_array_loop_tri(BMFace *f, BMLoop *r_loops[3]) ATTR_NONNULL();
/**
* faster alternative to:
* BM_iter_as_array(bm, BM_LOOPS_OF_FACE, f, (void **)l, 4);
*/
void BM_face_as_array_loop_quad(BMFace *f, BMLoop *r_loops[4]) ATTR_NONNULL();
/**
* Calculate a tangent from any 3 vertices.
*
* The tangent aligns to the most *unique* edge
* (the edge most unlike the other two).
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_vert_tri_calc_tangent_from_edge(BMVert *verts[3], float r_tangent[3]);
void BM_vert_tri_calc_tangent_pair_from_edge(BMVert *verts[3],
float r_tangent_a[3],
float r_tangent_b[3]);
/**
* Calculate a tangent from any 3 vertices,
*
* The tangent follows the center-line formed by the most unique edges center
* and the opposite vertex.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_vert_tri_calc_tangent_edge_pair(BMVert *verts[3], float r_tangent[3]);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_vector.hh"
#include "bmesh_class.hh"
namespace blender {
/** \file
* \ingroup bmesh
*/
/**
* Splits a face into many smaller faces defined by an edge-net.
* handle customdata and degenerate cases.
*
* - Isolated holes or unsupported face configurations, will be ignored.
* - Customdata calculations aren't efficient
* (need to calculate weights for each vert).
*/
bool BM_face_split_edgenet(
BMesh *bm, BMFace *f, BMEdge **edge_net, int edge_net_len, Vector<BMFace *> *r_face_arr);
/**
* For when the edge-net has holes in it-this connects them.
*
* \param use_partial_connect: Support for handling islands connected by only a single edge,
* \note that this is quite slow so avoid using where possible.
* \param mem_arena: Avoids many small allocations & should be cleared after each use.
* take care since \a edge_net_new is stored in \a r_edge_net_new.
*/
bool BM_face_split_edgenet_connect_islands(BMesh *bm,
BMFace *f,
BMEdge **edge_net_init,
uint edge_net_init_len,
bool use_partial_connect,
struct MemArena *mem_arena,
BMEdge ***r_edge_net_new,
uint *r_edge_net_new_len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 6, 7, 8);
} // namespace blender

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* Private function prototypes for bmesh public API.
* This file is a grab-bag of functions from various
* parts of the bmesh internals.
*/
#include "bmesh_class.hh"
namespace blender {
/* returns positive nonzero on error */
#ifdef NDEBUG
/* No error checking for release,
* it can take most of the CPU time when running some tools. */
# define BM_CHECK_ELEMENT(el) (void)(el)
#else
/**
* Check the element is valid.
*
* BMESH_TODO, when this raises an error the output is incredibly confusing.
* need to have some nice way to print/debug what the heck's going on.
*/
int bmesh_elem_check(void *element, char htype);
# define BM_CHECK_ELEMENT(el) \
{ \
if (bmesh_elem_check(el, ((BMHeader *)el)->htype)) { \
printf( \
"check_element failure, with code %i on line %i in file\n" \
" \"%s\"\n\n", \
bmesh_elem_check(el, ((BMHeader *)el)->htype), \
__LINE__, \
__FILE__); \
} \
} \
((void)0)
#endif
int bmesh_radial_length(const BMLoop *l);
int bmesh_disk_count_at_most(const BMVert *v, int count_max);
int bmesh_disk_count(const BMVert *v);
/**
* Internal BMHeader.api_flag
* \note Ensure different parts of the API do not conflict
* on using these internal flags!
*/
enum {
_FLAG_JF = (1 << 0), /* Join faces. */
_FLAG_MF = (1 << 1), /* Make face. */
_FLAG_MV = (1 << 1), /* Make face, vertex. */
_FLAG_OVERLAP = (1 << 2), /* General overlap flag. */
_FLAG_WALK = (1 << 3), /* General walk flag (keep clean). */
_FLAG_WALK_ALT = (1 << 4), /* Same as #_FLAG_WALK, for when a second tag is needed. */
_FLAG_ELEM_CHECK = (1 << 7), /* Reserved for bmesh_elem_check. */
};
#define BM_ELEM_API_FLAG_ENABLE(element, f) \
{ \
((element)->head.api_flag |= (f)); \
} \
(void)0
#define BM_ELEM_API_FLAG_DISABLE(element, f) \
{ \
((element)->head.api_flag &= (uchar) ~(f)); \
} \
(void)0
#define BM_ELEM_API_FLAG_TEST(element, f) ((element)->head.api_flag & (f))
#define BM_ELEM_API_FLAG_CLEAR(element) \
{ \
((element)->head.api_flag = 0); \
} \
(void)0
/**
* \brief POLY ROTATE PLANE
*
* Rotates a polygon so that its
* normal is pointing towards the mesh Z axis
*/
void poly_rotate_plane(const float normal[3], float (*verts)[3], uint nverts);
} // namespace blender
/* include the rest of our private declarations */
#include "bmesh_structure.hh" /* IWYU pragma: export */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,799 @@
/* 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_compiler_compat.h"
#include "bmesh_class.hh"
/**
* Returns true if the vertex is used in a given face.
*/
namespace blender {
bool BM_vert_in_face(BMVert *v, BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Compares the number of vertices in an array
* that appear in a given face
*/
int BM_verts_in_face_count(BMVert **varr, int len, BMFace *f) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Return true if all verts are in the face.
*/
bool BM_verts_in_face(BMVert **varr, int len, BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns whether or not a given edge is part of a given face.
*/
bool BM_edge_in_face(const BMEdge *e, const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_in_loop(const BMEdge *e, const BMLoop *l) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE bool BM_vert_in_edge(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE bool BM_verts_in_edge(const BMVert *v1,
const BMVert *v2,
const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns edge length
*/
float BM_edge_calc_length(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns edge length squared (for comparisons)
*/
float BM_edge_calc_length_squared(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Utility function, since enough times we have an edge
* and want to access 2 connected faces.
*
* \return true when only 2 faces are found.
*/
bool BM_edge_face_pair(BMEdge *e, BMFace **r_fa, BMFace **r_fb) ATTR_NONNULL();
/**
* Utility function, since enough times we have an edge
* and want to access 2 connected loops.
*
* \return true when only 2 faces are found.
*/
bool BM_edge_loop_pair(BMEdge *e, BMLoop **r_la, BMLoop **r_lb) ATTR_NONNULL();
BLI_INLINE BMVert *BM_edge_other_vert(BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Given a edge and a loop (assumes the edge is manifold). returns
* the other faces loop, sharing the same vertex.
*
* <pre>
* +-------------------+
* | |
* | |
* |l_other <-- return |
* +-------------------+ <-- A manifold edge between 2 faces
* |l e <-- edge |
* |^ <-------- loop |
* | |
* +-------------------+
* </pre>
*/
BMLoop *BM_edge_other_loop(BMEdge *e, BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing an Edge
*
* Finds the other loop that shares \a v with \a e loop in \a f.
* <pre>
* +----------+
* | |
* | f |
* | |
* +----------+ <-- return the face loop of this vertex.
* v --> e
* ^ ^ <------- These vert args define direction
* in the face to check.
* The faces loop direction is ignored.
* </pre>
*
* \note caller must ensure \a e is used in \a f
*/
BMLoop *BM_face_other_edge_loop(BMFace *f, BMEdge *e, BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* See #BM_face_other_edge_loop This is the same functionality
* to be used when the edges loop is already known.
*/
BMLoop *BM_loop_other_edge_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing a Vertex
*
* Finds the other loop in a face.
*
* This function returns a loop in \a f that shares an edge with \a v
* The direction is defined by \a v_prev, where the return value is
* the loop of what would be 'v_next'
* <pre>
* +----------+ <-- return the face loop of this vertex.
* | |
* | f |
* | |
* +----------+
* v_prev --> v
* ^^^^^^ ^ <-- These vert args define direction
* in the face to check.
* The faces loop direction is ignored.
* </pre>
*
* \note \a v_prev and \a v _implicitly_ define an edge.
*/
BMLoop *BM_face_other_vert_loop(BMFace *f, BMVert *v_prev, BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Return the other loop that uses this edge.
*
* In this case the loop defines the vertex,
* the edge passed in defines the direction to step.
*
* <pre>
* +----------+ <-- Return the face-loop of this vertex.
* | |
* | e | <-- This edge defines the direction.
* | |
* +----------+ <-- This loop defines the face and vertex..
* l
* </pre>
*/
BMLoop *BM_loop_other_vert_loop_by_edge(BMLoop *l, BMEdge *e) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing a Vert
*
* Finds the other loop that shares \a v with \a e loop in \a f.
* <pre>
* +----------+ <-- return the face loop of this vertex.
* | |
* | |
* | |
* +----------+ <-- This vertex defines the direction.
* l v
* ^ <------- This loop defines both the face to search
* and the edge, in combination with 'v'
* The faces loop direction is ignored.
* </pre>
*/
BMLoop *BM_loop_other_vert_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Utility function to step around a fan of loops,
* using an edge to mark the previous side.
*
* \note all edges must be manifold,
* once a non manifold edge is hit, return NULL.
*
* \code{.unparsed}
* ,.,-->|
* _,-' |
* ,' | (notice how 'e_step'
* / | and 'l' define the
* / | direction the arrow
* | return | points).
* | loop --> |
* ---------------------+---------------------
* ^ l --> |
* | |
* assign e_step |
* |
* begin e_step ----> |
* |
* \endcode
*/
BMLoop *BM_vert_step_fan_loop(BMLoop *l, BMEdge **e_step) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get the first loop of a vert. Uses the same initialization code for the first loop of the
* iterator API
*/
BMLoop *BM_vert_find_first_loop(BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of #BM_vert_find_first_loop that ignores hidden loops.
*/
BMLoop *BM_vert_find_first_loop_visible(BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Only #BMEdge.l access us needed, however when we want the first visible loop,
* a utility function is needed.
*/
BMLoop *BM_edge_find_first_loop_visible(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if verts share a face.
*/
bool BM_vert_pair_share_face_check(BMVert *v_a, BMVert *v_b) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_vert_pair_share_face_check_cb(BMVert *v_a,
BMVert *v_b,
bool (*test_fn)(BMFace *f, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
BMFace *BM_vert_pair_shared_face_cb(BMVert *v_a,
BMVert *v_b,
bool allow_adjacent,
bool (*callback)(BMFace *, BMLoop *, BMLoop *, void *userdata),
void *user_data,
BMLoop **r_l_a,
BMLoop **r_l_b) ATTR_NONNULL(1, 2, 4, 6, 7);
/**
* Given 2 verts, find the smallest face they share and give back both loops.
*/
BMFace *BM_vert_pair_share_face_by_len(
BMVert *v_a, BMVert *v_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
/**
* Given 2 verts,
* find a face they share that has the lowest angle across these verts and give back both loops.
*
* This can be better than #BM_vert_pair_share_face_by_len
* because concave splits are ranked lowest.
*/
BMFace *BM_vert_pair_share_face_by_angle(
BMVert *v_a, BMVert *v_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
BMFace *BM_edge_pair_share_face_by_len(
BMEdge *e_a, BMEdge *e_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
int BM_vert_edge_count_nonwire(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_vert_edge_count_is_equal(v, n) (BM_vert_edge_count_at_most(v, (n) + 1) == n)
#define BM_vert_edge_count_is_over(v, n) (BM_vert_edge_count_at_most(v, (n) + 1) == (n) + 1)
int BM_vert_edge_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of edges around this vertex.
*/
int BM_vert_edge_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_edge_face_count_is_equal(e, n) (BM_edge_face_count_at_most(e, (n) + 1) == n)
#define BM_edge_face_count_is_over(e, n) (BM_edge_face_count_at_most(e, (n) + 1) == (n) + 1)
int BM_edge_face_count_at_most(const BMEdge *e, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of faces around this edge
*/
int BM_edge_face_count(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_vert_face_count_is_equal(v, n) (BM_vert_face_count_at_most(v, (n) + 1) == n)
#define BM_vert_face_count_is_over(v, n) (BM_vert_face_count_at_most(v, (n) + 1) == (n) + 1)
int BM_vert_face_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of faces around this vert
* length matches #BM_LOOPS_OF_VERT iterator
*/
int BM_vert_face_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* The function takes a vertex at the center of a fan and returns the opposite edge in the fan.
* All edges in the fan must be manifold, otherwise return NULL.
*
* \note This could (probably) be done more efficiently.
*/
BMEdge *BM_vert_other_disk_edge(BMVert *v, BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Fast alternative to `(BM_vert_edge_count(v) == 2)`.
*/
bool BM_vert_is_edge_pair(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Fast alternative to `(BM_vert_edge_count(v) == 2)`
* that checks both edges connect to the same faces.
*/
bool BM_vert_is_edge_pair_manifold(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Access a verts 2 connected edges.
*
* \return true when only 2 verts are found.
*/
bool BM_vert_edge_pair(const BMVert *v, BMEdge **r_e_a, BMEdge **r_e_b);
/**
* Return true if the vertex is connected to _any_ faces.
*
* same as `BM_vert_face_count(v) != 0` or `BM_vert_find_first_loop(v) == NULL`.
*/
bool BM_vert_face_check(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Tests whether or not the vertex is part of a wire edge.
* (ie: has no faces attached to it)
*/
bool BM_vert_is_wire(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_wire(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A vertex is non-manifold if it meets the following conditions:
* 1: Loose - (has no edges/faces incident upon it).
* 2: Joins two distinct regions - (two pyramids joined at the tip).
* 3: Is part of an edge with more than 2 faces.
* 4: Is part of a wire edge.
*/
bool BM_vert_is_manifold(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of #BM_vert_is_manifold
* which only checks if we're connected to multiple isolated regions.
*/
bool BM_vert_is_manifold_region(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_manifold(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_vert_is_boundary(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_contiguous(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the edge is convex or concave
* (depends on face winding)
*/
bool BM_edge_is_convex(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \return true when loop customdata is contiguous.
*/
bool BM_edge_is_contiguous_loop_cd(const BMEdge *e,
int cd_loop_type,
int cd_loop_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* The number of loops connected to this loop (not including disconnected regions).
*/
int BM_loop_region_loops_count_at_most(BMLoop *l, int *r_loop_total) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
int BM_loop_region_loops_count(BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Check if the loop is convex or concave
* (depends on face normal)
*/
bool BM_loop_is_convex(const BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_loop_is_adjacent(const BMLoop *l_a, const BMLoop *l_b) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if a point is inside the corner defined by a loop
* (within the 2 planes defined by the loops corner & face normal).
*
* \return signed, squared distance to the loops planes, less than 0.0 when outside.
*/
float BM_loop_point_side_of_loop_test(const BMLoop *l, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if a point is inside the edge defined by a loop
* (within the plane defined by the loops edge & face normal).
*
* \return signed, squared distance to the edge plane, less than 0.0 when outside.
*/
float BM_loop_point_side_of_edge_test(const BMLoop *l, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \return The previous loop, over \a eps_sq distance from \a l (or \a NULL if l_stop is reached).
*/
BMLoop *BM_loop_find_prev_nodouble(BMLoop *l, BMLoop *l_stop, float eps_sq);
/**
* \return The next loop, over \a eps_sq distance from \a l (or \a NULL if l_stop is reached).
*/
BMLoop *BM_loop_find_next_nodouble(BMLoop *l, BMLoop *l_stop, float eps_sq);
/**
* Calculates the angle between the previous and next loops
* (angle at this loops face corner).
*
* \return angle in radians
*/
float BM_loop_calc_face_angle(const BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_normal
*
* Calculate the normal at this loop corner or fall back to the face normal on straight lines.
*
* \param l: The loop to calculate the normal at
* \param r_normal: Resulting normal
* \return The length of the cross product (double the area).
*/
float BM_loop_calc_face_normal(const BMLoop *l, float r_normal[3]) ATTR_NONNULL();
/**
* #BM_loop_calc_face_normal_safe_ex with predefined sane epsilon.
*
* Since this doesn't scale based on triangle size, fixed value works well.
*/
float BM_loop_calc_face_normal_safe(const BMLoop *l, float r_normal[3]) ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_normal
*
* Calculate the normal at this loop corner or fall back to the face normal on straight lines.
*
* \param l: The loop to calculate the normal at.
* \param epsilon_sq: Value to avoid numeric errors (1e-5f works well).
* \param r_normal: Resulting normal.
*/
float BM_loop_calc_face_normal_safe_ex(const BMLoop *l, float epsilon_sq, float r_normal[3])
ATTR_NONNULL();
/**
* A version of BM_loop_calc_face_normal_safe_ex which takes vertex coordinates.
*/
float BM_loop_calc_face_normal_safe_vcos_ex(const BMLoop *l,
const float normal_fallback[3],
float const (*vertexCos)[3],
float epsilon_sq,
float r_normal[3]) ATTR_NONNULL();
float BM_loop_calc_face_normal_safe_vcos(const BMLoop *l,
const float normal_fallback[3],
float const (*vertexCos)[3],
float r_normal[3]) ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_direction
*
* Calculate the direction a loop is pointing.
*
* \param l: The loop to calculate the direction at
* \param r_dir: Resulting direction
*/
void BM_loop_calc_face_direction(const BMLoop *l, float r_dir[3]);
/**
* \brief BM_loop_calc_face_tangent
*
* Calculate the tangent at this loop corner or fall back to the face normal on straight lines.
* This vector always points inward into the face.
*
* \param l: The loop to calculate the tangent at
* \param r_tangent: Resulting tangent
*/
void BM_loop_calc_face_tangent(const BMLoop *l, float r_tangent[3]);
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_ex(const BMEdge *e, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_edge_calc_face_angle(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_signed_ex(const BMEdge *e, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces in world space.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_with_imat3_ex(const BMEdge *e,
const float imat3[3][3],
float fallback) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
float BM_edge_calc_face_angle_with_imat3(const BMEdge *e,
const float imat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_edge_calc_face_angle_signed(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE TANGENT
*
* Calculate the tangent at this loop corner or fallback to the face normal on straight lines.
* This vector always points inward into the face.
*
* \brief BM_edge_calc_face_tangent
* \param e:
* \param e_loop: The loop to calculate the tangent at,
* used to get the face and winding direction.
* \param r_tangent: The loop corner tangent to set
*/
void BM_edge_calc_face_tangent(const BMEdge *e, const BMLoop *e_loop, float r_tangent[3])
ATTR_NONNULL();
float BM_vert_calc_edge_angle(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH VERT/EDGE ANGLE
*
* Calculates the angle a verts 2 edges.
*
* \returns the angle in radians
*/
float BM_vert_calc_edge_angle_ex(const BMVert *v, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \note this isn't optimal to run on an array of verts,
* see 'solidify_add_thickness' for a function which runs on an array.
*/
float BM_vert_calc_shell_factor(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/* alternate version of #BM_vert_calc_shell_factor which only
* uses 'hflag' faces, but falls back to all if none found. */
float BM_vert_calc_shell_factor_ex(const BMVert *v,
const float no[3],
char hflag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \note quite an obscure function.
* used in bmesh operators that have a relative scale options,
*/
float BM_vert_calc_median_tagged_edge_length(const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the loop of the shortest edge in f.
*/
BMLoop *BM_face_find_shortest_loop(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns the loop of the longest edge in f.
*/
BMLoop *BM_face_find_longest_loop(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BMEdge *BM_edge_exists(BMVert *v_a, BMVert *v_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns an edge sharing the same vertices as this one.
* This isn't an invalid state but tools should clean up these cases before
* returning the mesh to the user.
*/
BMEdge *BM_edge_find_double(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices (varr), find out if
* there is a face with exactly those vertices
* (and only those vertices).
*
* \note there used to be a BM_face_exists_overlap function that checks for partial overlap.
*/
BMFace *BM_face_exists(BMVert *const *varr, int len) ATTR_NONNULL(1);
/**
* Check if a face exists, using a subset of an existing face's loops.
* Edges defined by loops from `l_a` to `l_b` (inclusive) are used to check if they make a face,
* with an implied edge between `l_a->v` & `l_b->v` which must exist for there to be a face.
*
* \param l_a, l_b: First and last loop of the subset.
* \param f_len: Number of loops (vertices) between `l_a` & `l_b` (inclusive).
* \return The matching face if it exists, otherwise null.
*/
BMFace *BM_face_exists_subset_from_face(BMLoop *l_a,
BMLoop *l_b,
int f_len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the face has an exact duplicate (both winding directions).
*/
BMFace *BM_face_find_double(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices and edges (\a varr, \a earr), find out if
* all those vertices are filled in by existing faces that _only_ use those vertices.
*
* This is for use in cases where creating a face is possible but would result in
* many overlapping faces.
*
* An example of how this is used: when 2 triangles are selected that share an edge,
* pressing F-key would make a new overlapping quad (without a check like this)
*
* \a earr and \a varr can be in any order, however they _must_ form a closed loop.
*/
bool BM_face_exists_multi(BMVert **varr, BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Same as #BM_face_exists_multi but builds the vert array from edges.
*/
bool BM_face_exists_multi_edge(BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices (varr), find out if
* all those vertices overlap an existing face.
*
* \note The face may contain other verts \b not in \a varr.
*
* \note Its possible there are more than one overlapping faces,
* in this case the first one found will be returned.
*
* \param varr: Array of unordered verts.
* \param len: \a varr array length.
* \return The face or NULL.
*/
BMFace *BM_face_exists_overlap(BMVert **varr, int len) ATTR_WARN_UNUSED_RESULT;
/**
* Given a set of vertices (varr), find out if
* there is a face that uses vertices only from this list
* (that the face is a subset or made from the vertices given).
*
* \param varr: Array of unordered verts.
* \param len: varr array length.
*/
bool BM_face_exists_overlap_subset(BMVert **varr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns the number of faces that are adjacent to both f1 and f2,
* \note Could be sped up a bit by not using iterators and by tagging
* faces on either side, then count the tags rather then searching.
*/
int BM_face_share_face_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Counts the number of edges two faces share (if any)
*/
int BM_face_share_edge_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Counts the number of verts two faces share (if any).
*/
int BM_face_share_vert_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* same as #BM_face_share_face_count but returns a bool
*/
bool BM_face_share_face_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true if the faces share an edge
*/
bool BM_face_share_edge_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true if the faces share a vert.
*/
bool BM_face_share_vert_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true when 2 loops share an edge (are adjacent in the face-fan)
*/
bool BM_loop_share_edge_check(BMLoop *l_a, BMLoop *l_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Test if e1 shares any faces with e2
*/
bool BM_edge_share_face_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Test if e1 shares any quad faces with e2
*/
bool BM_edge_share_quad_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Tests to see if e1 shares a vertex with e2
*/
bool BM_edge_share_vert_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Return the shared vertex between the two edges or NULL
*/
BMVert *BM_edge_share_vert(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Edge and Vert
*
* Finds the loop used which uses \a v in face loop \a l
*
* \note this function takes a loop rather than an edge
* so we can select the face that the loop should be from.
*/
BMLoop *BM_edge_vert_share_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Face and Vertex
*
* Finds the loop used which uses \a v in face loop \a l
*
* \note currently this just uses simple loop in future may be sped up
* using radial vars
*/
BMLoop *BM_face_vert_share_loop(BMFace *f, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Face and Edge
*
* Finds the loop used which uses \a e in face loop \a l
*
* \note currently this just uses simple loop in future may be sped up
* using radial vars
*/
BMLoop *BM_face_edge_share_loop(BMFace *f, BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
void BM_edge_ordered_verts(const BMEdge *edge, BMVert **r_v1, BMVert **r_v2) ATTR_NONNULL();
/**
* Returns the verts of an edge as used in a face
* if used in a face at all, otherwise just assign as used in the edge.
*
* Useful to get a deterministic winding order when calling
* BM_face_create_ngon() on an arbitrary array of verts,
* though be sure to pick an edge which has a face.
*
* \note This is in fact quite a simple check,
* mainly include this function so the intent is more obvious.
* We know these 2 verts will _always_ make up the loops edge
*/
void BM_edge_ordered_verts_ex(const BMEdge *edge,
BMVert **r_v1,
BMVert **r_v2,
const BMLoop *edge_loop) ATTR_NONNULL();
bool BM_vert_is_all_edge_flag_test(const BMVert *v,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_vert_is_all_face_flag_test(const BMVert *v,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_edge_is_all_face_flag_test(const BMEdge *e,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/* convenience functions for checking flags */
bool BM_edge_is_any_vert_flag_test(const BMEdge *e, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_edge_is_any_face_flag_test(const BMEdge *e, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_face_is_any_vert_flag_test(const BMFace *f, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_face_is_any_edge_flag_test(const BMFace *f, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_edge_is_any_face_len_test(const BMEdge *e, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Use within asserts to check normals are valid.
*/
bool BM_face_is_normal_valid(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
double BM_mesh_calc_volume(BMesh *bm, bool is_signed) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Calculate isolated groups of faces with optional filtering.
*
* \param bm: the BMesh.
* \param r_groups_array: Array of integers to fill in, length of `bm->totface`
* (or when hflag_test is set, the number of flagged faces).
* \param r_group_index: index, length pairs into \a r_groups_array, size of return value
* int pairs: (array_start, array_length).
* \param filter_fn: Filter the edge-loops or vert-loops we step over (depends on \a htype_step).
* \param user_data: Optional user data for \a filter_fn, can be NULL.
* \param hflag_test: Optional flag to test faces,
* use to exclude faces from the calculation, 0 for all faces.
* \param htype_step: BM_VERT to walk over face-verts, BM_EDGE to walk over faces edges
* (having both set is supported too).
* \return The number of groups found.
*/
int BM_mesh_calc_face_groups(BMesh *bm,
int *r_groups_array,
int (**r_group_index)[2],
BMLoopFilterFunc filter_fn,
BMLoopPairFilterFunc filter_pair_fn,
void *user_data,
char hflag_test,
char htype_step) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3);
/**
* Calculate isolated groups of edges with optional filtering.
*
* \param bm: the BMesh.
* \param r_groups_array: Array of ints to fill in, length of `bm->totedge`
* (or when hflag_test is set, the number of flagged edges).
* \param r_group_index: index, length pairs into \a r_groups_array, size of return value
* int pairs: (array_start, array_length).
* \param filter_fn: Filter the edges or verts we step over (depends on \a htype_step)
* as to which types we deal with.
* \param user_data: Optional user data for \a filter_fn, can be NULL.
* \param hflag_test: Optional flag to test edges,
* use to exclude edges from the calculation, 0 for all edges.
* \return The number of groups found.
*
* \note Unlike #BM_mesh_calc_face_groups there is no 'htype_step' argument,
* since we always walk over verts.
*/
int BM_mesh_calc_edge_groups(BMesh *bm,
int *r_groups_array,
int (**r_group_index)[2],
BMVertFilterFunc filter_fn,
void *user_data,
char hflag_test) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3);
/**
* This is an alternative to #BM_mesh_calc_edge_groups.
*
* While we could call this, then create vertex & face arrays,
* it requires looping over geometry connectivity twice,
* this slows down edit-mesh separate by loose parts, see: #70864.
*/
int BM_mesh_calc_edge_groups_as_arrays(BMesh *bm,
BMVert **verts,
BMEdge **edges,
BMFace **faces,
int (**r_groups)[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 4, 5);
/* Not really any good place to put this. */
float bmesh_subd_falloff_calc(int falloff, float val) ATTR_WARN_UNUSED_RESULT;
} // namespace blender
#include "bmesh_query_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "BLI_utildefines.h"
#include "bmesh_class.hh"
namespace blender {
/**
* Returns whether or not a given vertex is
* is part of a given edge.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BM_vert_in_edge(const BMEdge *e, const BMVert *v)
{
return (ELEM(v, e->v1, e->v2));
}
/**
* Returns whether or not a given edge is part of a given loop.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool BM_edge_in_loop(const BMEdge *e, const BMLoop *l)
{
return (l->e == e || l->prev->e == e);
}
/**
* Returns whether or not two vertices are in
* a given edge
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3) BLI_INLINE
bool BM_verts_in_edge(const BMVert *v1, const BMVert *v2, const BMEdge *e)
{
return ((e->v1 == v1 && e->v2 == v2) || (e->v1 == v2 && e->v2 == v1));
}
/**
* Given a edge and one of its vertices, returns
* the other vertex.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMVert *BM_edge_other_vert(BMEdge *e,
const BMVert *v)
{
if (e->v1 == v) {
return e->v2;
}
if (e->v2 == v) {
return e->v1;
}
return nullptr;
}
/**
* Tests whether or not the edge is part of a wire.
* (ie: has no faces attached to it)
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_wire(const BMEdge *e)
{
return (e->l == nullptr);
}
/**
* Tests whether or not this edge is manifold.
* A manifold edge has exactly 2 faces attached to it.
*/
#if 1 /* fast path for checking manifold */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_manifold(const BMEdge *e)
{
const BMLoop *l = e->l;
return (l && (l->radial_next != l) && /* not 0 or 1 face users */
(l->radial_next->radial_next == l)); /* 2 face users */
}
#else
BLI_INLINE int BM_edge_is_manifold(BMEdge *e)
{
return (BM_edge_face_count(e) == 2);
}
#endif
/**
* Tests that the edge is manifold and
* that both its faces point the same way.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_contiguous(const BMEdge *e)
{
const BMLoop *l = e->l;
const BMLoop *l_other;
return (l && ((l_other = l->radial_next) != l) && /* not 0 or 1 face users */
(l_other->radial_next == l) && /* 2 face users */
(l_other->v != l->v));
}
/**
* Tests whether or not an edge is on the boundary
* of a shell (has one face associated with it)
*/
#if 1 /* fast path for checking boundary */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e)
{
const BMLoop *l = e->l;
return (l && (l->radial_next == l));
}
#else
BLI_INLINE int BM_edge_is_boundary(BMEdge *e)
{
return (BM_edge_face_count(e) == 1);
}
#endif
/**
* Tests whether one loop is next to another within the same face.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool BM_loop_is_adjacent(const BMLoop *l_a, const BMLoop *l_b)
{
BLI_assert(l_a->f == l_b->f);
BLI_assert(l_a != l_b);
return (ELEM(l_b, l_a->next, l_a->prev));
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_loop_is_manifold(const BMLoop *l)
{
return ((l != l->radial_next) && (l == l->radial_next->radial_next));
}
/**
* Check if we have a single wire edge user.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_vert_is_wire_endpoint(const BMVert *v)
{
const BMEdge *e = v->e;
if (e && e->l == nullptr) {
return (BM_DISK_EDGE_NEXT(e, v) == e);
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,210 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#include "BLI_array.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BKE_attribute.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
namespace blender {
BMUVOffsets BM_uv_map_offsets_from_layer(const BMesh *bm, const int layer)
{
using namespace bke;
const int layer_index = CustomData_get_layer_index_n(&bm->ldata, CD_PROP_FLOAT2, layer);
if (layer_index == -1) {
return BMUVOFFSETS_NONE;
}
const StringRef name = bm->ldata.layers[layer_index].name;
char buffer[MAX_CUSTOMDATA_LAYER_NAME];
BMUVOffsets offsets;
offsets.uv = bm->ldata.layers[layer_index].offset;
offsets.pin = CustomData_get_offset_named(
&bm->ldata, CD_PROP_BOOL, BKE_uv_map_pin_name_get(name, buffer));
return offsets;
}
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
{
const int layer = CustomData_get_active_layer(&bm->ldata, CD_PROP_FLOAT2);
if (layer == -1) {
return BMUVOFFSETS_NONE;
}
return BM_uv_map_offsets_from_layer(bm, layer);
}
static void uv_aspect(const BMLoop *l,
const float aspect[2],
const int cd_loop_uv_offset,
float r_uv[2])
{
const float *uv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
r_uv[0] = uv[0] * aspect[0];
r_uv[1] = uv[1] * aspect[1];
}
/**
* Typically we avoid hiding arguments,
* make this an exception since it reads poorly with so many repeated arguments.
*/
#define UV_ASPECT(l, r_uv) uv_aspect(l, aspect, cd_loop_uv_offset, r_uv)
void BM_face_uv_calc_center_median_weighted(const BMFace *f,
const float aspect[2],
const int cd_loop_uv_offset,
float r_cent[2])
{
const BMLoop *l_iter;
const BMLoop *l_first;
float totw = 0.0f;
float w_prev;
zero_v2(r_cent);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
float uv_prev[2], uv_curr[2];
UV_ASPECT(l_iter->prev, uv_prev);
UV_ASPECT(l_iter, uv_curr);
w_prev = len_v2v2(uv_prev, uv_curr);
do {
float uv_next[2];
UV_ASPECT(l_iter->next, uv_next);
const float w_curr = len_v2v2(uv_curr, uv_next);
const float w = (w_curr + w_prev);
madd_v2_v2fl(r_cent, uv_curr, w);
totw += w;
w_prev = w_curr;
copy_v2_v2(uv_curr, uv_next);
} while ((l_iter = l_iter->next) != l_first);
if (totw != 0.0f) {
mul_v2_fl(r_cent, 1.0f / totw);
}
/* Reverse aspect. */
r_cent[0] /= aspect[0];
r_cent[1] /= aspect[1];
}
#undef UV_ASPECT
void BM_face_uv_calc_center_median(const BMFace *f, const int cd_loop_uv_offset, float r_cent[2])
{
const BMLoop *l_iter;
const BMLoop *l_first;
zero_v2(r_cent);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
add_v2_v2(r_cent, luv);
} while ((l_iter = l_iter->next) != l_first);
mul_v2_fl(r_cent, 1.0f / float(f->len));
}
float BM_face_uv_calc_cross(const BMFace *f, const int cd_loop_uv_offset)
{
Array<float2, BM_DEFAULT_NGON_STACK_SIZE> uvs(f->len);
const BMLoop *l_iter;
const BMLoop *l_first;
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
uvs[i++] = BM_ELEM_CD_GET_FLOAT2_P(l_iter, cd_loop_uv_offset);
} while ((l_iter = l_iter->next) != l_first);
return cross_poly_v2(reinterpret_cast<const float (*)[2]>(uvs.data()), f->len);
}
void BM_face_uv_minmax(const BMFace *f, float min[2], float max[2], const int cd_loop_uv_offset)
{
const BMLoop *l_iter;
const BMLoop *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
minmax_v2v2_v2(min, max, luv);
} while ((l_iter = l_iter->next) != l_first);
}
bool BM_loop_uv_share_edge_check(const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
{
BLI_assert(l_a->e == l_b->e);
const float *luv_a_curr = BM_ELEM_CD_GET_FLOAT_P(l_a, cd_loop_uv_offset);
const float *luv_a_next = BM_ELEM_CD_GET_FLOAT_P(l_a->next, cd_loop_uv_offset);
const float *luv_b_curr = BM_ELEM_CD_GET_FLOAT_P(l_b, cd_loop_uv_offset);
const float *luv_b_next = BM_ELEM_CD_GET_FLOAT_P(l_b->next, cd_loop_uv_offset);
if (l_a->v != l_b->v) {
std::swap(luv_b_curr, luv_b_next);
}
return (equals_v2v2(luv_a_curr, luv_b_curr) && equals_v2v2(luv_a_next, luv_b_next));
}
bool BM_loop_uv_share_vert_check(const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
{
BLI_assert(l_a->v == l_b->v);
const float *luv_a = BM_ELEM_CD_GET_FLOAT_P(l_a, cd_loop_uv_offset);
const float *luv_b = BM_ELEM_CD_GET_FLOAT_P(l_b, cd_loop_uv_offset);
if (!equals_v2v2(luv_a, luv_b)) {
return false;
}
return true;
}
bool BM_edge_uv_share_vert_check(const BMEdge *e,
const BMLoop *l_a,
const BMLoop *l_b,
const int cd_loop_uv_offset)
{
BLI_assert(l_a->v == l_b->v);
if (!BM_loop_uv_share_vert_check(l_a, l_b, cd_loop_uv_offset)) {
return false;
}
/* No need for null checks, these will always succeed. */
const BMLoop *l_other_a = BM_loop_other_vert_loop_by_edge(const_cast<BMLoop *>(l_a),
const_cast<BMEdge *>(e));
const BMLoop *l_other_b = BM_loop_other_vert_loop_by_edge(const_cast<BMLoop *>(l_b),
const_cast<BMEdge *>(e));
{
const float *luv_other_a = BM_ELEM_CD_GET_FLOAT_P(l_other_a, cd_loop_uv_offset);
const float *luv_other_b = BM_ELEM_CD_GET_FLOAT_P(l_other_b, cd_loop_uv_offset);
if (!equals_v2v2(luv_other_a, luv_other_b)) {
return false;
}
}
return true;
}
bool BM_face_uv_point_inside_test(const BMFace *f, const float co[2], const int cd_loop_uv_offset)
{
Array<float2, BM_DEFAULT_NGON_STACK_SIZE> projverts(f->len);
BMLoop *l_iter;
int i;
BLI_assert(BM_face_is_normal_valid(f));
for (i = 0, l_iter = BM_FACE_FIRST_LOOP(f); i < f->len; i++, l_iter = l_iter->next) {
projverts[i] = BM_ELEM_CD_GET_FLOAT2_P(l_iter, cd_loop_uv_offset);
}
return isect_point_poly_v2(co, reinterpret_cast<const float (*)[2]>(projverts.data()), f->len);
}
} // namespace blender

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BKE_customdata.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* Retrieve the custom data offsets for the UV map.
* \param layer: The layer index (where 0 is the first UV map).
* \return The layer offsets or -1 when not found.
*/
BMUVOffsets BM_uv_map_offsets_from_layer(const BMesh *bm, int layer);
/**
* Retrieve the custom data offsets for layers used for user interaction with the active UV map.
* \return The layer offsets or -1 when not found.
*/
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm);
float BM_loop_uv_calc_edge_length_squared(const BMLoop *l,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_loop_uv_calc_edge_length(const BMLoop *l, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Computes the UV center of a face, using the mean average weighted by edge length.
*
* See #BM_face_calc_center_median_weighted for matching spatial functionality.
*
* \param aspect: Calculate the center scaling by these values, and finally dividing.
* Since correct weighting depends on having the correct aspect.
*/
void BM_face_uv_calc_center_median_weighted(const BMFace *f,
const float aspect[2],
int cd_loop_uv_offset,
float r_cent[2]) ATTR_NONNULL();
void BM_face_uv_calc_center_median(const BMFace *f, int cd_loop_uv_offset, float r_cent[2])
ATTR_NONNULL();
/**
* Calculate the UV cross product (use the sign to check the winding).
*/
float BM_face_uv_calc_cross(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
void BM_face_uv_minmax(const BMFace *f, float min[2], float max[2], int cd_loop_uv_offset);
bool BM_loop_uv_share_edge_check_with_limit(const BMLoop *l_a,
const BMLoop *l_b,
const float limit[2],
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if two loops that share an edge also have the same UV coordinates.
*/
bool BM_loop_uv_share_edge_check(const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if two loops that share a vertex also have the same UV coordinates.
*/
bool BM_edge_uv_share_vert_check(const BMEdge *e,
const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if two loops that share a vertex also have the same UV coordinates.
*/
bool BM_loop_uv_share_vert_check(const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the point is inside the UV face.
*/
bool BM_face_uv_point_inside_test(const BMFace *f,
const float co[2],
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
} // namespace blender

View File

@@ -0,0 +1,573 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Low level routines for manipulating the BM structure.
*/
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
/**
* MISC utility functions.
*/
void bmesh_disk_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
if (e->v1 == v_src) {
e->v1 = v_dst;
e->v1_disk_link.next = e->v1_disk_link.prev = nullptr;
}
else if (e->v2 == v_src) {
e->v2 = v_dst;
e->v2_disk_link.next = e->v2_disk_link.prev = nullptr;
}
else {
BLI_assert(0);
}
}
void bmesh_edge_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
/* swap out loops */
if (e->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
if (l_iter->v == v_src) {
l_iter->v = v_dst;
}
else if (l_iter->next->v == v_src) {
l_iter->next->v = v_dst;
}
else {
BLI_assert(l_iter->prev->v != v_src);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
/* swap out edges */
bmesh_disk_vert_replace(e, v_dst, v_src);
}
void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
BLI_assert(e->v1 == v_src || e->v2 == v_src);
bmesh_disk_edge_remove(e, v_src); /* Remove `e` from `v_src` disk cycle. */
bmesh_disk_vert_swap(e, v_dst, v_src); /* Swap out `v_src` for `v_dst` in `e`. */
bmesh_disk_edge_append(e, v_dst); /* Add `e` to `v_dst` disk cycle. */
BLI_assert(e->v1 != e->v2);
}
/**
* \section bm_cycles BMesh Cycles
*
* NOTE(@joeedh): this is somewhat outdated, though bits of its API are still used.
*
* Cycles are circular doubly linked lists that form the basis of adjacency
* information in the BME modeler. Full adjacency relations can be derived
* from examining these cycles very quickly. Although each cycle is a double
* circular linked list, each one is considered to have a 'base' or 'head',
* and care must be taken by Euler code when modifying the contents of a cycle.
*
* The contents of this file are split into two parts. First there are the
* bmesh_cycle family of functions which are generic circular double linked list
* procedures. The second part contains higher level procedures for supporting
* modification of specific cycle types.
*
* The three cycles explicitly stored in the BM data structure are as follows:
* 1: The Disk Cycle - A circle of edges around a vertex
* Base: vertex->edge pointer.
*
* This cycle is the most complicated in terms of its structure. Each bmesh_Edge contains
* two bmesh_CycleNode structures to keep track of that edges membership in the disk cycle
* of each of its vertices. However for any given vertex it may be the first in some edges
* in its disk cycle and the second for others. The bmesh_disk_XXX family of functions contain
* some nice utilities for navigating disk cycles in a way that hides this detail from the
* tool writer.
*
* Note that the disk cycle is completely independent from face data. One advantage of this
* is that wire edges are fully integrated into the topology database. Another is that the
* the disk cycle has no problems dealing with non-manifold conditions involving faces.
*
* Functions relating to this cycle:
* - #bmesh_disk_vert_replace
* - #bmesh_disk_edge_append
* - #bmesh_disk_edge_remove
* - #bmesh_disk_edge_next
* - #bmesh_disk_edge_prev
* - #bmesh_disk_facevert_count
* - #bmesh_disk_faceedge_find_first
* - #bmesh_disk_faceedge_find_next
* 2: The Radial Cycle - A circle of face edges (bmesh_Loop) around an edge
* Base: edge->l->radial structure.
*
* The radial cycle is similar to the radial cycle in the radial edge data structure.*
* Unlike the radial edge however, the radial cycle does not require a large amount of memory
* to store non-manifold conditions since BM does not keep track of region/shell information.
*
* Functions relating to this cycle:
* - #bmesh_radial_loop_append
* - #bmesh_radial_loop_remove
* - #bmesh_radial_facevert_count
* - #bmesh_radial_facevert_check
* - #bmesh_radial_faceloop_find_first
* - #bmesh_radial_faceloop_find_next
* - #bmesh_radial_validate
* 3: The Loop Cycle - A circle of face edges around a polygon.
* Base: polygon->lbase.
*
* The loop cycle keeps track of a faces vertices and edges. It should be noted that the
* direction of a loop cycle is either CW or CCW depending on the face normal, and is
* not oriented to the faces edit-edges.
*
* Functions relating to this cycle:
* - bmesh_cycle_XXX family of functions.
* \note the order of elements in all cycles except the loop cycle is undefined. This
* leads to slightly increased seek time for deriving some adjacency relations, however the
* advantage is that no intrinsic properties of the data structures are dependent upon the
* cycle order and all non-manifold conditions are represented trivially.
*/
void bmesh_disk_edge_append(BMEdge *e, BMVert *v)
{
if (!v->e) {
BMDiskLink *dl1 = bmesh_disk_edge_link_from_vert(e, v);
v->e = e;
dl1->next = dl1->prev = e;
}
else {
BMDiskLink *dl1, *dl2, *dl3;
dl1 = bmesh_disk_edge_link_from_vert(e, v);
dl2 = bmesh_disk_edge_link_from_vert(v->e, v);
dl3 = dl2->prev ? bmesh_disk_edge_link_from_vert(dl2->prev, v) : nullptr;
dl1->next = v->e;
dl1->prev = dl2->prev;
dl2->prev = e;
if (dl3) {
dl3->next = e;
}
}
}
void bmesh_disk_edge_remove(BMEdge *e, BMVert *v)
{
BMDiskLink *dl1, *dl2;
dl1 = bmesh_disk_edge_link_from_vert(e, v);
if (dl1->prev) {
dl2 = bmesh_disk_edge_link_from_vert(dl1->prev, v);
dl2->next = dl1->next;
}
if (dl1->next) {
dl2 = bmesh_disk_edge_link_from_vert(dl1->next, v);
dl2->prev = dl1->prev;
}
if (v->e == e) {
v->e = (e != dl1->next) ? dl1->next : nullptr;
}
dl1->next = dl1->prev = nullptr;
}
BMEdge *bmesh_disk_edge_exists(const BMVert *v1, const BMVert *v2)
{
if (v1->e) {
BMEdge *e_iter, *e_first;
e_first = e_iter = v1->e;
do {
if (BM_verts_in_edge(v1, v2, e_iter)) {
return e_iter;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v1)) != e_first);
}
return nullptr;
}
int bmesh_disk_count(const BMVert *v)
{
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
e_iter = e_first = v->e;
do {
count++;
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
int bmesh_disk_count_at_most(const BMVert *v, const int count_max)
{
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
e_iter = e_first = v->e;
do {
count++;
if (count == count_max) {
break;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
bool bmesh_disk_validate(int len, BMEdge *e, BMVert *v)
{
BMEdge *e_iter;
if (!BM_vert_in_edge(e, v)) {
return false;
}
if (len == 0 || bmesh_disk_count_at_most(v, len + 1) != len) {
return false;
}
e_iter = e;
do {
if (len != 1 && bmesh_disk_edge_prev(e_iter, v) == e_iter) {
return false;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return true;
}
int bmesh_disk_facevert_count(const BMVert *v)
{
/* is there an edge on this vert at all */
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
/* first, loop around edge */
e_first = e_iter = v->e;
do {
if (e_iter->l) {
count += bmesh_radial_facevert_count(e_iter->l, v);
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
int bmesh_disk_facevert_count_at_most(const BMVert *v, const int count_max)
{
/* is there an edge on this vert at all */
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
/* first, loop around edge */
e_first = e_iter = v->e;
do {
if (e_iter->l) {
count += bmesh_radial_facevert_count_at_most(e_iter->l, v, count_max - count);
if (count == count_max) {
break;
}
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (e_iter->l != nullptr) {
return const_cast<BMEdge *>((e_iter->l->v == v) ? e_iter : e_iter->l->next->e);
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (e_iter->l != nullptr) {
return (e_iter->l->v == v) ? e_iter->l : e_iter->l->next;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMLoop *bmesh_disk_faceloop_find_first_visible(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (!BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN)) {
if (e_iter->l != nullptr) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e_iter->l;
do {
if (!BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) {
return (l_iter->v == v) ? l_iter : l_iter->next;
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMEdge *bmesh_disk_faceedge_find_next(const BMEdge *e, const BMVert *v)
{
BMEdge *e_find;
e_find = bmesh_disk_edge_next(e, v);
do {
if (e_find->l && bmesh_radial_facevert_check(e_find->l, v)) {
return e_find;
}
} while ((e_find = bmesh_disk_edge_next(e_find, v)) != e);
return const_cast<BMEdge *>(e);
}
bool bmesh_radial_validate(int radlen, BMLoop *l)
{
BMLoop *l_iter = l;
int i = 0;
if (bmesh_radial_length(l) != radlen) {
return false;
}
do {
if (UNLIKELY(!l_iter)) {
BMESH_ASSERT(0);
return false;
}
if (l_iter->e != l->e) {
return false;
}
if (!ELEM(l_iter->v, l->e->v1, l->e->v2)) {
return false;
}
if (UNLIKELY(i > BM_LOOP_RADIAL_MAX)) {
BMESH_ASSERT(0);
return false;
}
i++;
} while ((l_iter = l_iter->radial_next) != l);
return true;
}
void bmesh_radial_loop_append(BMEdge *e, BMLoop *l)
{
if (e->l == nullptr) {
e->l = l;
l->radial_next = l->radial_prev = l;
}
else {
l->radial_prev = e->l;
l->radial_next = e->l->radial_next;
e->l->radial_next->radial_prev = l;
e->l->radial_next = l;
e->l = l;
}
if (UNLIKELY(l->e && l->e != e)) {
/* l is already in a radial cycle for a different edge */
BMESH_ASSERT(0);
}
l->e = e;
}
void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l)
{
/* if e is non-nullptr, l must be in the radial cycle of e */
if (UNLIKELY(e != l->e)) {
BMESH_ASSERT(0);
}
if (l->radial_next != l) {
if (l == e->l) {
e->l = l->radial_next;
}
l->radial_next->radial_prev = l->radial_prev;
l->radial_prev->radial_next = l->radial_next;
}
else {
if (l == e->l) {
e->l = nullptr;
}
else {
BMESH_ASSERT(0);
}
}
/* l is no longer in a radial cycle; empty the links
* to the cycle and the link back to an edge */
l->radial_next = l->radial_prev = nullptr;
l->e = nullptr;
}
void bmesh_radial_loop_unlink(BMLoop *l)
{
if (l->radial_next != l) {
l->radial_next->radial_prev = l->radial_prev;
l->radial_prev->radial_next = l->radial_next;
}
/* l is no longer in a radial cycle; empty the links
* to the cycle and the link back to an edge */
l->radial_next = l->radial_prev = nullptr;
l->e = nullptr;
}
BMLoop *bmesh_radial_faceloop_find_first(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
l_iter = l;
do {
if (l_iter->v == v) {
return const_cast<BMLoop *>(l_iter);
}
} while ((l_iter = l_iter->radial_next) != l);
return nullptr;
}
BMLoop *bmesh_radial_faceloop_find_next(const BMLoop *l, const BMVert *v)
{
BMLoop *l_iter;
l_iter = l->radial_next;
do {
if (l_iter->v == v) {
return l_iter;
}
} while ((l_iter = l_iter->radial_next) != l);
return const_cast<BMLoop *>(l);
}
int bmesh_radial_length(const BMLoop *l)
{
const BMLoop *l_iter = l;
int i = 0;
if (!l) {
return 0;
}
do {
if (UNLIKELY(!l_iter)) {
/* Radial cycle is broken (not a circular loop). */
BMESH_ASSERT(0);
return 0;
}
i++;
if (UNLIKELY(i >= BM_LOOP_RADIAL_MAX)) {
BMESH_ASSERT(0);
return -1;
}
} while ((l_iter = l_iter->radial_next) != l);
return i;
}
int bmesh_radial_facevert_count(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
int count = 0;
l_iter = l;
do {
if (l_iter->v == v) {
count++;
}
} while ((l_iter = l_iter->radial_next) != l);
return count;
}
int bmesh_radial_facevert_count_at_most(const BMLoop *l, const BMVert *v, const int count_max)
{
const BMLoop *l_iter;
int count = 0;
l_iter = l;
do {
if (l_iter->v == v) {
count++;
if (count == count_max) {
break;
}
}
} while ((l_iter = l_iter->radial_next) != l);
return count;
}
bool bmesh_radial_facevert_check(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
l_iter = l;
do {
if (l_iter->v == v) {
return true;
}
} while ((l_iter = l_iter->radial_next) != l);
return false;
}
bool bmesh_loop_validate(BMFace *f)
{
int i;
int len = f->len;
BMLoop *l_iter, *l_first;
l_first = BM_FACE_FIRST_LOOP(f);
if (l_first == nullptr) {
return false;
}
/* Validate that the face loop cycle is the length specified by f->len */
for (i = 1, l_iter = l_first->next; i < len; i++, l_iter = l_iter->next) {
if ((l_iter->f != f) || (l_iter == l_first)) {
return false;
}
}
if (l_iter != l_first) {
return false;
}
/* Validate the loop->prev links also form a cycle of length f->len */
for (i = 1, l_iter = l_first->prev; i < len; i++, l_iter = l_iter->prev) {
if (l_iter == l_first) {
return false;
}
}
if (l_iter != l_first) {
return false;
}
return true;
}
} // namespace blender

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* The lowest level of functionality for manipulating bmesh structures.
* None of these functions should ever be exported to the rest of Blender.
*
* in the vast majority of cases there shouldn't be used directly.
* if absolutely necessary, see function definitions in code for
* descriptive comments. but seriously, don't use this stuff.
*/
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Loop Cycle Management
* Loop cycle functions, e.g. loops surrounding a face.
* \{ */
bool bmesh_loop_validate(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Disk Cycle Management
* \{ */
void bmesh_disk_edge_append(BMEdge *e, BMVert *v) ATTR_NONNULL();
void bmesh_disk_edge_remove(BMEdge *e, BMVert *v) ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_next_safe(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_prev_safe(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_next(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_prev(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
int bmesh_disk_facevert_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief DISK COUNT FACE VERT
*
* Counts the number of loop users
* for this vertex. Note that this is
* equivalent to counting the number of
* faces incident upon this vertex
*/
int bmesh_disk_facevert_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief FIND FIRST FACE EDGE
*
* Finds the first edge in a vertices
* Disk cycle that has one of this
* vert's loops attached
* to it.
*/
BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Special case for BM_LOOPS_OF_VERT & BM_FACES_OF_VERT, avoids 2x calls.
*
* The returned BMLoop.e matches the result of #bmesh_disk_faceedge_find_first
*/
BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* A version of #bmesh_disk_faceloop_find_first that ignores hidden faces.
*/
BMLoop *bmesh_disk_faceloop_find_first_visible(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMEdge *bmesh_disk_faceedge_find_next(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Radial Cycle Management
*
* Radial cycle functions, e.g. loops surrounding edges.
* \{ */
void bmesh_radial_loop_append(BMEdge *e, BMLoop *l) ATTR_NONNULL();
/**
* \brief BMESH RADIAL REMOVE LOOP
*
* Removes a loop from an radial cycle. If edge e is non-NULL
* it should contain the radial cycle, and it will also get
* updated (in the case that the edge's link into the radial
* cycle was the loop which is being removed from the cycle).
*/
void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l) ATTR_NONNULL();
/**
* A version of #bmesh_radial_loop_remove which only performs the radial unlink,
* leaving the edge untouched.
*/
void bmesh_radial_loop_unlink(BMLoop *l) ATTR_NONNULL();
/* NOTE:
* bmesh_radial_loop_next(BMLoop *l) / prev.
* just use member access l->radial_next, l->radial_prev now */
int bmesh_radial_facevert_count_at_most(const BMLoop *l,
const BMVert *v,
int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief RADIAL COUNT FACE VERT
*
* Returns the number of times a vertex appears
* in a radial cycle
*/
int bmesh_radial_facevert_count(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief RADIAL CHECK FACE VERT
*
* Quicker check for `bmesh_radial_facevert_count(...) != 0`.
*/
bool bmesh_radial_facevert_check(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BME RADIAL FIND FIRST FACE VERT
*
* Finds the first loop of v around radial
* cycle
*/
BMLoop *bmesh_radial_faceloop_find_first(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMLoop *bmesh_radial_faceloop_find_next(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMLoop *bmesh_radial_faceloop_find_vert(const BMFace *f, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool bmesh_radial_validate(int radlen, BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Utilities
* \{ */
void bmesh_disk_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
/**
* Handles all connected data, use with care.
*
* Assumes caller has setup correct state before the swap is done.
*/
void bmesh_edge_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
BMEdge *bmesh_disk_edge_exists(const BMVert *v1, const BMVert *v2) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool bmesh_disk_validate(int len, BMEdge *e, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
} // namespace blender
#include "intern/bmesh_structure_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline operator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
#include "intern/bmesh_query.hh"
namespace blender {
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2)
BLI_INLINE BMDiskLink *bmesh_disk_edge_link_from_vert(const BMEdge *e, const BMVert *v)
{
BLI_assert(BM_vert_in_edge(e, v));
return const_cast<BMDiskLink *>(&(&e->v1_disk_link)[v == e->v2]);
}
/**
* \brief Next Disk Edge
*
* Find the next edge in a disk cycle
*
* \return Pointer to the next edge in the disk cycle for the vertex v.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
BLI_INLINE BMEdge *bmesh_disk_edge_next_safe(const BMEdge *e, const BMVert *v)
{
if (v == e->v1) {
return e->v1_disk_link.next;
}
if (v == e->v2) {
return e->v2_disk_link.next;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
BLI_INLINE BMEdge *bmesh_disk_edge_prev_safe(const BMEdge *e, const BMVert *v)
{
if (v == e->v1) {
return e->v1_disk_link.prev;
}
if (v == e->v2) {
return e->v2_disk_link.prev;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMEdge *bmesh_disk_edge_next(const BMEdge *e,
const BMVert *v)
{
return BM_DISK_EDGE_NEXT(e, v);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMEdge *bmesh_disk_edge_prev(const BMEdge *e,
const BMVert *v)
{
return BM_DISK_EDGE_PREV(e, v);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "bmesh_class.hh"
#include "BLI_vector_list.hh"
namespace blender {
/** \file
* \ingroup bmesh
*
* Overview
* ========
*
* The `BM_uvselect_*` API deals with synchronizing selection
* between UV's and selected vertices edges & faces,
* where a selected vertex in the 3D viewport may only have some of its
* UV vertices selected in the UV editor.
*
* Supporting this involves flushing in both directions depending on the selection being edited.
*
* \note See #78393 for a user-level overview of this functionality.
* This describes the motivation to synchronize selection between UV's and the mesh.
*
* \note A short-hand term for vertex/edge/face selection used
* in this file is View3D abbreviated to `v3d`, since this is the section
* manipulated in the viewport, e.g. #BM_mesh_uvselect_sync_to_mesh.
*
* \note This is quite involved. As a last resort the UV selection can always be cleared
* and re-set from the mesh (v3d) selection, however it's good to keep UV selection
* if possible because resetting may extend vertex selection to other UV islands.
*
* Terms
* =====
*
* - Synchronized Selection (abbreviated to "sync"). See #BMesh::uv_select_sync_valid.
* When the UV synchronized data is valid, it means there is a valid relationship
* between the UV selection flags (#BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE)
* and the meshes selection (#BM_ELEM_SELECT_UV).
*
* - When the UV selection changes (from the UV editor)
* this needs to be synchronized to the mesh.
* - When the base-selection flags change (from the 3D viewport)
* this needs to be synchronized to the UV's.
* Synchronizing in this direction may be lossy, although (depending on the operation),
* support for maintaining a synchronized selection may be possible.
*
* - Flushing Selection ("flush")
* When an element is selected or de-selected, the selection state
* of connected geometry may change too.
* So, de-selecting a vertex must de-select all faces that use that vertex.
*
* The rules for flushing may depend on the selection mode.
* When de-selecting a face in vertex-select-mode, all its vertices & edges
* must also be de-selected. When de-selecting a face in face-select-mode,
* only vertices and edges no longer connected to any selected faces will be de-selected.
*
* Since applying these rules while selecting individual elements is often impractical,
* it's common to adjust the selection, then flush based on the selection mode afterwards.
*
* - Flushing up:
* Flushing the selection from [verts -> edges/faces], [edges -> faces].
* - Flushing down:
* Flushing the selection from [faces -> verts/edges], [edges -> verts].
*
* - Isolated vertex or edge selection:
* When a vertex or edge is selected without being connected to a selected face.
*
* UV Selection Flags
* ==================
*
* - UV selection uses:
* - #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE for #BMLoop
* to define selected vertices & edges.
* - #BM_ELEM_SELECT_UV for #BMFace.
*
* Hidden Flags
* ============
*
* Unlike viewport selection there is no requirement for hidden elements not to be selected.
* Therefor, UV selection checks must check the underlying geometry is not hidden.
* In practice this means hidden faces must be assumed unselected,
* since UV's are part of the faces (there is no such thing as a hidden face-corner)
* and any hidden edge or vertex causes connected faces to be hidden.
*
* UV Selection Flushing
* =====================
*
* Selection setting functions flush down (unless the `_noflush(...)` version is used),
* this means selecting a face also selects all verts & edges,
* selecting an edge selects its vertices.
*
* However it's expected the selection is flushed,
* de-selecting a vertex or edge must de-select it's faces (flushing up).
* For this, there are various flushing functions,
* exactly what is needed depends on the selection operation performed and the selection mode.
*
* There are also situations that shouldn't be allowed such as a single selected vertex in face
* select mode.
*
* Flushing & Synchronizing
* ========================
*
* Properly handling the selection state is important for operators that adjust the UV selection.
* This typically involves the following steps:
*
* - The UV selection changes.
* - The UV selection must be flushed between elements to ensure the selection is valid,
* (see: `BM_mesh_uvselect_flush_*` & `BM_mesh_uvselect_mode_flush_*` functions).
* - The UV selection must be synchronized to the mesh selection
* (see #BM_mesh_uvselect_sync_to_mesh).
* - The mesh must then flush selection to its elements
* (see: `BM_mesh_select_flush_*` & `BM_mesh_select_mode_flush_*` functions).
*
* Valid State
* ===========
*
* For a valid state:
* - A selected UV-vertex must have its underlying mesh vertex selected.
* - A selected mesh-vertex must have at least one UV-vertex selected.
*
* This is *mostly* true for edges/faces too, however there cases where
* the UV selection causes an edge/face to be selected in mesh space but not UV space.
*
* See #BM_mesh_uvselect_is_valid for details.
*
* Clearing the Valid State
* ========================
*
* As already noted, tools should maintain the synchronized UV selection where possible.
* However when this information *isn't* needed it should be cleared aggressively
* (see #BM_mesh_uvselect_clear), since it adds both computation & memory overhead.
*
* For actions that overwrite the selection such as selecting or de-selecting all,
* it's safe to "clear" the data, other actions such as adding new geometry that replaces
* the selection can also safely "clear" the UV selection.
*
* In practice users modeling in the 3D viewport are likely to clear the UV selection data
* since selecting the mesh without extending the selection is effectively a "De-select All".
* So the chances this data persists when it's not needed over many editing operations are low.
*/
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions (low level)
*
* Selection checking functions.
* These should be used instead of checking #BM_ELEM_SELECT_UV,
* so hidden geometry is never considered selected.
* \{ */
bool BM_face_uvselect_test(const BMFace *f);
bool BM_loop_vert_uvselect_test(const BMLoop *l);
bool BM_loop_edge_uvselect_test(const BMLoop *l);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Connectivity Checks
*
* Regarding the `hflag` parameter: this is typically set to:
* - #BM_ELEM_SELECT for mesh selection.
* - #BM_ELEM_SELECT_UV for selected UV vertices.
* - #BM_ELEM_SELECT_UV_EDGE for selected UV edges.
* - #BM_ELEM_SELECT_TAG to allow the caller to use a separate non-selection flag.
*
* Each function asserts that a supported `hflag` is passed in.
* \{ */
bool BM_loop_vert_uvselect_check_other_loop_vert(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_edge_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_edge_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_face_uvselect_check_edges_all(BMFace *f);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions
* \{ */
/** Set the UV selection flag for `f` without flushing down to edges & vertices. */
void BM_face_uvselect_set_noflush(BMesh *bm, BMFace *f, bool select);
/** Set the UV selection flag for `f` & flush down to edges & vertices. */
void BM_face_uvselect_set(BMesh *bm, BMFace *f, bool select);
/** Set the UV selection flag for `e` without flushing down to vertices. */
void BM_loop_edge_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select);
/** Set the UV selection flag for `e` & flush down to vertices. */
void BM_loop_edge_uvselect_set(BMesh *bm, BMLoop *l, bool select);
/**
* Set the UV selection flag for `v` without flushing down.
* since there is nothing to flush down to.
*/
void BM_loop_vert_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select);
/**
* Call this function when selecting mesh elements in the viewport and
* the relationship with UV's is lost.
*
* \return True if UV select is cleared (a change was made).
*
* This has two purposes:
*
* - Maintaining the UV selection isn't needed:
* Some operations such as adding a new mesh primitive clear the selection,
* selecting all geometry from the new primitive.
* In this case a UV selection is redundant & should be cleared.
*
* - Maintaining the UV selection isn't supported:
* Some selection operations don't support maintaining a valid UV selection,
* in that case it's necessary to clear the UV selection otherwise tools may
* seem to be broken if they aren't operating on the selection properly.
*
* NOTE(@ideasman42): It's worth noting that in this case clearing the selection is "lossy",
* users may wish that all selection operations would handle UV selection data too.
* Supporting additional operations is always possible, at the time of writing it's
* impractical to do so, see: #131642 design task for details.
*
* Internally this marks the UV selection data as invalid,
* using the mesh selection as the "source-of-truth".
*
* \note By convention call this immediately after flushing.
*
* \note In many cases the UV selection can be maintained and this function removed,
* although it adds some complexity & overhead.
* See #UVSyncSelectFromMesh.
*
* \note Calls to this function that should *not* be removed in favor of supporting UV selection,
* this should be mentioned in a code-comment, making it clear this is not a limitation to *fix*.
*/
bool BM_mesh_uvselect_clear(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions (Shared)
* \{ */
void BM_loop_vert_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset);
void BM_loop_edge_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset);
void BM_face_uvselect_set_shared(BMesh *bm, BMFace *f, bool select, int cd_loop_uv_offset);
void BM_mesh_uvselect_set_elem_shared(BMesh *bm,
bool select,
int cd_loop_uv_offset,
Span<BMLoop *> loop_verts,
Span<BMLoop *> loop_edges,
Span<BMFace *> faces);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Picking
* \{ */
struct BMUVSelectPickParams {
/**
* The custom data offset for the active UV layer.
* May be -1, in this case UV connectivity checks are skipped.
*/
int cd_loop_uv_offset = -1;
/**
* If true, selection changes propagate to all other UV elements
* that share the same UV coordinates (contiguous selection).
*
* Typically derived from #ToolSettings::uv_sticky, although in some cases
* it's assumed to be true (when switching selection modes for example)
* because the tool settings aren't available in that context.
*
* A boolean can be used since "Shared Vertex" (uv_sticky mode)
* can check the meshes vertex selection directly.
*/
bool shared = true;
};
void BM_vert_uvselect_set_pick(BMesh *bm,
BMVert *v,
bool select,
const BMUVSelectPickParams &params);
void BM_edge_uvselect_set_pick(BMesh *bm,
BMEdge *e,
bool select,
const BMUVSelectPickParams &params);
void BM_face_uvselect_set_pick(BMesh *bm,
BMFace *f,
bool select,
const BMUVSelectPickParams &params);
/**
* Select/deselect elements in the viewport,
* then integrate the selection with the UV selection,
* without clearing an re-initializing the synchronized state.
* (likely to re-select islands bounds from a user-perspective).
*/
void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm,
bool select,
const BMUVSelectPickParams &params,
Span<BMVert *> verts,
Span<BMEdge *> edges,
Span<BMFace *> faces);
/** \copydoc #BM_mesh_uvselect_set_elem_from_mesh. */
void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm,
bool select,
const BMUVSelectPickParams &params,
const VectorList<BMVert *> &verts,
const VectorList<BMEdge *> &edges,
const VectorList<BMFace *> &faces);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Only Select/De-Select)
*
* \note In most cases flushing assumes selection has already been flushed down.
*
* This means:
* - A selected edge must have both UV vertices selected.
* - A selected faces has all its edges & vertices selected.
*
* It's often useful to call #BM_mesh_uvselect_flush_shared_only_select
* after using these non-UV-coordinate aware flushing functions.
* \{ */
void BM_mesh_uvselect_flush_from_loop_verts_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_verts_only_deselect(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_edges_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_edges_only_deselect(BMesh *bm);
void BM_mesh_uvselect_flush_from_faces_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_faces_only_deselect(BMesh *bm);
/**
* A useful utility so simple selection operations can be performed on edges/faces,
* afterwards this can be used to select UV's that are connected.
* This avoids having to use more involved UV connectivity aware logic inline.
*/
void BM_mesh_uvselect_flush_shared_only_select(BMesh *bm, int cd_loop_uv_offset);
void BM_mesh_uvselect_flush_shared_only_deselect(BMesh *bm, int cd_loop_uv_offset);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Between Elements)
*
* Regarding the `flush_down` argument.
*
* Primitive UV selection functions always flush down:
* - #BM_face_uvselect_set
* - #BM_loop_edge_uvselect_set
* - #BM_loop_vert_uvselect_set
*
* This means it's often only necessary to flush up after the selection has been changed.
* \{ */
/**
* Mode independent UV selection/de-selection flush from UV vertices.
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_loop_verts(BMesh *bm);
/**
* Mode independent UV selection/de-selection flush from UV edges.
*
* Flush from loop edges up to faces and optionally down to vertices (when `flush_down` is true).
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_loop_edges(BMesh *bm, bool flush_down);
/**
* Mode independent UV selection/de-selection flush from UV faces.
*
* Flush from faces down to edges & vertices (when `flush_down` is true).
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_faces(BMesh *bm, bool flush_down);
/**
* Mode independent UV selection/de-selection flush from UV vertices.
*
* Use this when it's know geometry was only selected/de-selected.
*
* \note An equivalent to #BM_mesh_select_flush_from_verts for the UV selection.
*/
void BM_mesh_uvselect_flush_from_verts(BMesh *bm, bool select);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Selection Mode Aware)
* \{ */
/**
* \param flush_down: See #BMSelectFlushFlag::Down for notes on flushing down.
*/
void BM_mesh_uvselect_mode_flush_ex(BMesh *bm, const short selectmode, bool flush_down);
void BM_mesh_uvselect_mode_flush(BMesh *bm);
/**
* Select elements based on the selection mode.
* (flushes the selection *up* based on the mode).
*
* - With vertex selection mode enabled: flush up to edges and faces.
* - With edge selection mode enabled: flush to faces.
* - With *only* face selection mode enabled: do nothing.
*
* \note An "only deselect" version function could be added, it's not needed at the moment.a
*/
void BM_mesh_uvselect_mode_flush_only_select(BMesh *bm);
/**
* When the select mode changes, update to ensure the selection is valid.
* So single vertices aren't selected in edge-select mode for example.
*
* The mesh selection flushing must have already run.
*/
void BM_mesh_uvselect_mode_flush_update(BMesh *bm,
short selectmode_old,
short selectmode_new,
int cd_loop_uv_offset);
/**
* A specialized flushing that fills in selection information after subdividing.
*
* It's important this runs:
* - After subdivision.
* - After the mesh selection has already been flushed.
*
* \note Intended to be a generic utility to be used in any situation
* new geometry is created by splitting existing geometry.
*/
void BM_mesh_uvselect_flush_post_subdivide(BMesh *bm, int cd_loop_uv_offset);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (From/To Mesh)
* \{ */
/* From 3D viewport to UV selection.
*
* These functions correspond to #ToolSettings::uv_sticky options. */
void BM_mesh_uvselect_sync_from_mesh_sticky_location(BMesh *bm, int cd_loop_uv_offset);
void BM_mesh_uvselect_sync_from_mesh_sticky_disabled(BMesh *bm);
void BM_mesh_uvselect_sync_from_mesh_sticky_vert(BMesh *bm);
/**
* Synchronize selection: from the UV selection to the 3D viewport.
*
* \note #BMesh::uv_select_sync_valid must be true.
*/
void BM_mesh_uvselect_sync_to_mesh(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Validation
* \{ */
/**
* UV/Mesh Synchronization
*
* Check the selection has been properly synchronized between the mesh and the UV's.
*
* \note It is essential for this to be correct and return no errors.
* Other checks are useful to ensure the selection state meets the expectations of the caller
* but the state is not invalid - as it is when the selection is out-of-sync.
*/
struct UVSelectValidateInfo_Sync {
/** When a vertex is unselected none of it's UV's may be selected. */
int count_uv_vert_any_selected_with_vert_unselected = 0;
/** When a vertex is selected at least one UV must be selected. */
int count_uv_vert_none_selected_with_vert_selected = 0;
/** When a edge is unselected none of it's UV's may be selected. */
int count_uv_edge_any_selected_with_edge_unselected = 0;
/** When a edge is selected at least one UV must be selected. */
int count_uv_edge_none_selected_with_edge_selected = 0;
};
/**
* Flushing between elements.
*
* Check the selection has been properly flushing between elements.
*/
struct UVSelectValidateInfo_Flush {
/** Edges are selected without selected vertices. */
int count_uv_edge_selected_with_any_verts_unselected = 0;
/** Edges are unselected with all selected vertices. */
int count_uv_edge_unselected_with_all_verts_selected = 0;
/** Faces are selected without selected vertices. */
int count_uv_face_selected_with_any_verts_unselected = 0;
/** Faces are unselected with all selected vertices. */
int count_uv_face_unselected_with_all_verts_selected = 0;
/** Faces are selected without selected edges. */
int count_uv_face_selected_with_any_edges_unselected = 0;
/** Faces are unselected with all selected edges. */
int count_uv_face_unselected_with_all_edges_selected = 0;
};
/**
* Contiguous.
*
* Check the selected UV's are contiguous,
* in situations where it's expected selecting a UV will select all "connected" UV's
* (UV's sharing the same vertex with the same UV coordinate).
*/
struct UVSelectValidateInfo_Contiguous {
/** When a vertices connected UV's are co-located without matching selection. */
int count_uv_vert_non_contiguous_selected = 0;
/** When a edges connected UV's are co-located without matching selection. */
int count_uv_edge_non_contiguous_selected = 0;
};
/**
* Flush & contiguous.
*
* In some cases it's necessary to check flushing and contiguous UV's are correct.
*/
struct UVSelectValidateInfo_FlushAndContiguous {
/** A vertex is selected in edge/face modes without being part of a selected edge/face. */
int count_uv_vert_isolated_in_edge_or_face_mode = 0;
/** A vertex is selected in face modes without being part of a selected face. */
int count_uv_vert_isolated_in_face_mode = 0;
/** An edge is selected in face modes without being part of a selected face. */
int count_uv_edge_isolated_in_face_mode = 0;
};
struct UVSelectValidateInfo {
UVSelectValidateInfo_Sync sync;
UVSelectValidateInfo_Flush flush;
UVSelectValidateInfo_Contiguous contiguous;
UVSelectValidateInfo_FlushAndContiguous flush_contiguous;
};
/**
* Check the UV selection is valid, mainly for debugging & testing purposes.
*
* The primary check which should remain valid is: `check_sync`,
* if there is ever a selected vertex without any selected UV's or a selected
* UV without it's vertex being selected (and similar kinds of issues),
* then the selection is out-of-sync, which Blender should *never* allow.
*
* While an invalid selection should not crash, tools that operate on selection
* may behave unpredictably.
*
* The other checks may be desired or not although this depends more on the situation.
*
* \param cd_loop_uv_offset: The UV custom-data layer to check.
* Ignored when -1 (UV checks wont be used).
*
* \param check_sync: When true, check the selection is synchronized
* between the UV and mesh selection. This should practically always be true,
* as it doesn't make sense to check the UV selection if valid otherwise,
* unless the UV selection is being set and has not yet been synchronized.
* \param check_flush: When true, check the selection is flushed based on #BMesh::selectmode.
* \param check_contiguous: When true, check that UV selection is contiguous.
* Note that this is not considered an *error* since users may cause this to happen and
* tools are expected to work properly, however some operations are expected to maintain
* a contiguous selection. This check is included to ensure those operations are working.
*/
bool BM_mesh_uvselect_is_valid(BMesh *bm,
int cd_loop_uv_offset,
bool check_sync,
bool check_flush,
bool check_contiguous,
UVSelectValidateInfo *info);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,203 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh Walker API.
*
* NOTE(@joeedh): Details on design.
*
* Original design: walkers directly emulation recursive functions.
* functions save their state onto a #BMWalker.worklist, and also add new states
* to implement recursive or looping behavior.
* Generally only one state push per call with a specific state is desired.
*
* basic design pattern: the walker step function goes through its
* list of possible choices for recursion, and recurses (by pushing a new state)
* using the first non-visited one. This choice is the flagged as visited using the #GHash.
* Each step may push multiple new states onto the #BMWalker.worklist at once.
*
* - Walkers use tool flags, not header flags.
* - Walkers now use #GHash for storing visited elements,
* rather than stealing flags. #GHash can be rewritten
* to be faster if necessary, in the far future :) .
* - tools should ALWAYS have necessary error handling
* for if walkers fail.
*/
#include <cstdlib>
#include <cstring> /* For `memcpy`. */
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "bmesh_walkers_private.hh"
namespace blender {
void *BMW_begin(BMWalker *walker, void *start)
{
BLI_assert(((BMHeader *)start)->htype & walker->begin_htype);
walker->begin(walker, start);
return BMW_current_state(walker) ? walker->step(walker) : nullptr;
}
void BMW_init(BMWalker *walker,
BMesh *bm,
int type,
short mask_vert,
short mask_edge,
short mask_face,
BMWFlag flag,
int layer,
BMWDelimitFlag delimit)
{
memset(walker, 0, sizeof(BMWalker));
walker->layer = layer;
walker->flag = flag;
walker->delimit = delimit;
walker->bm = bm;
walker->mask_vert = mask_vert;
walker->mask_edge = mask_edge;
walker->mask_face = mask_face;
walker->visit_set = MEM_new<Set<const void *>>("bmesh walkers");
walker->visit_set_alt = MEM_new<Set<const void *>>("bmesh walkers sec");
if (UNLIKELY(type >= BMW_MAXWALKERS || type < 0)) {
fprintf(stderr,
"%s: Invalid walker type in BMW_init; type: %d, "
"searchmask: (v:%d, e:%d, f:%d), flag: %d, layer: %d\n",
__func__,
type,
mask_vert,
mask_edge,
mask_face,
flag,
layer);
BLI_assert(0);
return;
}
if (type != BMW_CUSTOM) {
walker->begin_htype = bm_walker_types[type]->begin_htype;
walker->begin = bm_walker_types[type]->begin;
walker->yield = bm_walker_types[type]->yield;
walker->step = bm_walker_types[type]->step;
walker->structsize = bm_walker_types[type]->structsize;
walker->order = bm_walker_types[type]->order;
walker->valid_mask = bm_walker_types[type]->valid_mask;
walker->delimit_supported = bm_walker_types[type]->delimit_supported;
/* safety checks */
/* if this raises an error either the caller is wrong or
* 'bm_walker_types' needs updating */
BLI_assert(mask_vert == 0 || (walker->valid_mask & BM_VERT));
BLI_assert(mask_edge == 0 || (walker->valid_mask & BM_EDGE));
BLI_assert(mask_face == 0 || (walker->valid_mask & BM_FACE));
BLI_assert((delimit & ~walker->delimit_supported) == 0);
}
walker->worklist = BLI_mempool_create(walker->structsize, 0, 128, BLI_MEMPOOL_NOP);
walker->states.clear_no_delete();
}
void BMW_end(BMWalker *walker)
{
BLI_mempool_destroy(walker->worklist);
MEM_delete(walker->visit_set);
MEM_delete(walker->visit_set_alt);
}
void *BMW_step(BMWalker *walker)
{
BMHeader *head;
head = static_cast<BMHeader *>(BMW_walk(walker));
return head;
}
int BMW_current_depth(BMWalker *walker)
{
return walker->depth;
}
void *BMW_walk(BMWalker *walker)
{
void *current = nullptr;
while (BMW_current_state(walker)) {
current = walker->step(walker);
if (current) {
return current;
}
}
return nullptr;
}
void *BMW_current_state(BMWalker *walker)
{
BMwGenericWalker *currentstate = static_cast<BMwGenericWalker *>(walker->states.first);
if (currentstate) {
/* Automatic update of depth. For most walkers that
* follow the standard "Step" pattern of:
* - read current state
* - remove current state
* - push new states
* - return walk result from just-removed current state
* this simple automatic update should keep track of depth
* just fine. Walkers that deviate from that pattern may
* need to manually update the depth if they care about
* keeping it correct. */
walker->depth = currentstate->depth + 1;
}
return currentstate;
}
void BMW_state_remove(BMWalker *walker)
{
void *oldstate;
oldstate = BMW_current_state(walker);
BLI_remlink(&walker->states, oldstate);
BLI_mempool_free(walker->worklist, oldstate);
}
void *BMW_state_add(BMWalker *walker)
{
BMwGenericWalker *newstate;
newstate = static_cast<BMwGenericWalker *>(BLI_mempool_alloc(walker->worklist));
newstate->depth = walker->depth;
switch (walker->order) {
case BMW_DEPTH_FIRST:
BLI_addhead(&walker->states, newstate);
break;
case BMW_BREADTH_FIRST:
BLI_addtail(&walker->states, newstate);
break;
default:
BLI_assert(0);
break;
}
return newstate;
}
void BMW_reset(BMWalker *walker)
{
while (BMW_current_state(walker)) {
BMW_state_remove(walker);
}
walker->depth = 0;
walker->visit_set->clear();
walker->visit_set_alt->clear();
}
} // namespace blender

View File

@@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "BLI_set.hh"
namespace blender {
/*
* NOTE: do NOT modify topology while walking a mesh!
*/
struct BMwGenericWalker;
enum BMWOrder {
BMW_DEPTH_FIRST,
BMW_BREADTH_FIRST,
};
enum BMWFlag {
BMW_FLAG_NOP = 0,
BMW_FLAG_TEST_HIDDEN = (1 << 0),
};
enum BMWDelimitFlag {
BMW_DELIMIT_NONE = 0,
BMW_DELIMIT_EDGE_LOOP_INNER_CORNERS = 1 << 0,
BMW_DELIMIT_EDGE_LOOP_OUTER_CORNERS = 1 << 1,
BMW_DELIMIT_EDGE_LOOP_NGONS = 1 << 2,
BMW_DELIMIT_EDGE_RING_NGONS = 1 << 3,
BMW_DELIMIT_EDGE_MARK_SEAM = 1 << 4,
BMW_DELIMIT_EDGE_MARK_SHARP = 1 << 5,
BMW_DELIMIT_FACE_MARK_MATERIAL = 1 << 6,
};
ENUM_OPERATORS(BMWDelimitFlag)
/*Walkers*/
struct BMWalker {
char begin_htype; /* only for validating input */
void (*begin)(struct BMWalker *walker, void *start);
void *(*step)(struct BMWalker *walker);
void *(*yield)(struct BMWalker *walker);
int structsize;
BMWOrder order;
int valid_mask;
BMWDelimitFlag delimit_supported;
/* runtime */
int layer;
BMesh *bm;
BLI_mempool *worklist;
ListBaseT<BMwGenericWalker> states;
/* these masks are to be tested against elements BMO_elem_flag_test(),
* should never be accessed directly only through BMW_init() and bmw_mask_check_*() functions */
short mask_vert;
short mask_edge;
short mask_face;
BMWFlag flag;
BMWDelimitFlag delimit;
Set<const void *> *visit_set;
Set<const void *> *visit_set_alt;
int depth;
};
/* define to make BMW_init more clear */
#define BMW_MASK_NOP 0
/**
* \brief Initialize Walker
*
* Allocates and returns a new mesh walker of a given type.
* The elements visited are filtered by the bit-mask `searchmask`.
*/
void BMW_init(struct BMWalker *walker,
BMesh *bm,
int type,
short mask_vert,
short mask_edge,
short mask_face,
BMWFlag flag,
int layer,
BMWDelimitFlag delimit);
void *BMW_begin(BMWalker *walker, void *start);
/**
* \brief Step Walker
*/
void *BMW_step(struct BMWalker *walker);
/**
* \brief End Walker
*
* Frees a walker's worklist.
*/
void BMW_end(struct BMWalker *walker);
/**
* \brief Walker Current Depth
*
* Returns the current depth of the walker.
*/
int BMW_current_depth(BMWalker *walker);
/* These are used by custom walkers. */
/**
* \brief Current Walker State
*
* Returns the first state from the walker state
* worklist. This state is the next in the
* worklist for processing.
*/
void *BMW_current_state(BMWalker *walker);
/**
* \brief Add a new Walker State
*
* Allocate a new empty state and put it on the worklist.
* A pointer to the new state is returned so that the caller
* can fill in the state data. The new state will be inserted
* at the front for depth-first walks, and at the end for
* breadth-first walks.
*/
void *BMW_state_add(BMWalker *walker);
/**
* \brief Remove Current Walker State
*
* Remove and free an item from the end of the walker state
* worklist.
*/
void BMW_state_remove(BMWalker *walker);
/**
* \brief Main Walking Function
*
* Steps a mesh walker forward by one element
*/
void *BMW_walk(BMWalker *walker);
/**
* \brief Reset Walker
*
* Frees all states from the worklist, resetting the walker
* for reuse in a new walk.
*/
void BMW_reset(BMWalker *walker);
#define BMW_ITER(ele, walker, data) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMW_begin(walker, (BM_CHECK_TYPE_ELEM(data), data)); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMW_step(walker))
/*
* example of usage, walking over an island of tool flagged faces:
*
* BMWalker walker;
* BMFace *f;
*
* BMW_init(&walker, bm, BMW_ISLAND, SOME_OP_FLAG);
*
* for (f = BMW_begin(&walker, some_start_face); f; f = BMW_step(&walker)) {
* // do something with f
* }
* BMW_end(&walker);
*/
enum {
BMW_VERT_SHELL,
BMW_LOOP_SHELL,
BMW_LOOP_SHELL_WIRE,
BMW_FACE_SHELL,
BMW_EDGELOOP,
BMW_FACELOOP,
BMW_EDGERING,
BMW_EDGEBOUNDARY,
BMW_EDGELOOP_NONMANIFOLD,
/* BMW_RING, */
BMW_LOOPDATA_ISLAND,
BMW_ISLANDBOUND,
BMW_ISLAND,
BMW_ISLAND_MANIFOLD,
BMW_CONNECTED_VERTEX,
/* End of array index enum values. */
/* Do not initialize function pointers and struct size in #BMW_init. */
BMW_CUSTOM,
BMW_MAXWALKERS,
};
/* use with BMW_init, so as not to confuse with restrict flags */
#define BMW_NIL_LAY 0
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* BMesh walker API.
*/
#include "bmesh_class.hh"
namespace blender {
struct BMWalker;
extern const BMWalker *bm_walker_types[];
extern const int bm_totwalkers;
/* Pointer hiding */
struct BMwGenericWalker {
Link link;
int depth;
};
struct BMwShellWalker {
BMwGenericWalker header;
BMEdge *curedge;
};
struct BMwLoopShellWalker {
BMwGenericWalker header;
BMLoop *curloop;
};
struct BMwLoopShellWireWalker {
BMwGenericWalker header;
BMElem *curelem;
};
struct BMwIslandboundWalker {
BMwGenericWalker header;
BMLoop *base;
BMVert *lastv;
BMLoop *curloop;
};
struct BMwIslandWalker {
BMwGenericWalker header;
BMFace *cur;
};
struct BMwEdgeLoopWalker {
BMwGenericWalker header;
BMEdge *cur, *start;
BMVert *lastv, *startv;
BMFace *f_hub;
bool is_boundary; /* boundary looping changes behavior */
bool is_single; /* single means the edge verts are only connected to 1 face */
};
struct BMwFaceLoopWalker {
BMwGenericWalker header;
BMLoop *l;
bool no_calc;
};
struct BMwEdgeringWalker {
BMwGenericWalker header;
BMLoop *l;
BMEdge *wireedge;
bool no_calc;
};
struct BMwEdgeboundaryWalker {
BMwGenericWalker header;
BMEdge *e;
};
struct BMwNonManifoldEdgeLoopWalker {
BMwGenericWalker header;
BMEdge *start, *cur;
BMVert *startv, *lastv;
int face_count; /* face count around the edge. */
};
struct BMwUVEdgeWalker {
BMwGenericWalker header;
BMLoop *l;
};
struct BMwConnectedVertexWalker {
BMwGenericWalker header;
BMVert *curvert;
};
} // namespace blender