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,73 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Beautify the mesh by rotating edges between triangles
* to more attractive positions until no more rotations can be made.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
#define ELE_NEW 1
#define FACE_MARK 2
void bmo_beautify_fill_exec(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter siter;
BMFace *f;
BMEdge *e;
const bool use_restrict_tag = BMO_slot_bool_get(op->slots_in, "use_restrict_tag");
const short flag =
((use_restrict_tag ? VERT_RESTRICT_TAG : 0) |
/* Enable to avoid iterative edge rotation to cause the direction of faces to flip. */
EDGE_RESTRICT_DEGENERATE);
const short method = short(BMO_slot_int_get(op->slots_in, "method"));
BMEdge **edge_array;
int edge_array_len = 0;
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len == 3) {
BMO_face_flag_enable(bm, f, FACE_MARK);
}
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_disable(e, BM_ELEM_TAG);
}
/* will over alloc if some edges can't be rotated */
edge_array = MEM_new_array_uninitialized<BMEdge *>(
size_t(BMO_slot_buffer_len(op->slots_in, "edges")), __func__);
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
/* edge is manifold and can be rotated */
if (BM_edge_rotate_check(e) &&
/* faces are tagged */
BMO_face_flag_test(bm, e->l->f, FACE_MARK) &&
BMO_face_flag_test(bm, e->l->radial_next->f, FACE_MARK))
{
edge_array[edge_array_len] = e;
edge_array_len++;
}
}
BM_mesh_beautify_fill(
bm, edge_array, edge_array_len, flag, method, ELE_NEW, FACE_MARK | ELE_NEW);
MEM_delete(edge_array);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_EDGE | BM_FACE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Bevel wrapper around #BM_mesh_bevel
*/
#include "DNA_curveprofile_types.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "BKE_customdata.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_bevel_exec(BMesh *bm, BMOperator *op)
{
const float offset = BMO_slot_float_get(op->slots_in, "offset");
const int offset_type = BMO_slot_int_get(op->slots_in, "offset_type");
const int profile_type = BMO_slot_int_get(op->slots_in, "profile_type");
const int seg = BMO_slot_int_get(op->slots_in, "segments");
const int affect_type = BMO_slot_int_get(op->slots_in, "affect");
const float profile = BMO_slot_float_get(op->slots_in, "profile");
const bool clamp_overlap = BMO_slot_bool_get(op->slots_in, "clamp_overlap");
const int material = BMO_slot_int_get(op->slots_in, "material");
const bool loop_slide = BMO_slot_bool_get(op->slots_in, "loop_slide");
const bool mark_seam = BMO_slot_bool_get(op->slots_in, "mark_seam");
const bool mark_sharp = BMO_slot_bool_get(op->slots_in, "mark_sharp");
const bool harden_normals = BMO_slot_bool_get(op->slots_in, "harden_normals");
const int face_strength_mode = BMO_slot_int_get(op->slots_in, "face_strength_mode");
const int miter_outer = BMO_slot_int_get(op->slots_in, "miter_outer");
const int miter_inner = BMO_slot_int_get(op->slots_in, "miter_inner");
const float spread = BMO_slot_float_get(op->slots_in, "spread");
const CurveProfile *custom_profile = static_cast<const CurveProfile *>(
BMO_slot_ptr_get(op->slots_in, "custom_profile"));
const int vmesh_method = BMO_slot_int_get(op->slots_in, "vmesh_method");
if (offset > 0) {
BMOIter siter;
BMEdge *e;
BMVert *v;
/* first flush 'geom' into flags, this makes it possible to check connected data,
* BM_FACE is cleared so we can put newly created faces into a bmesh slot. */
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
BMO_ITER (e, &siter, op->slots_in, "geom", BM_EDGE) {
if (BM_edge_is_manifold(e)) {
BM_elem_flag_enable(e, BM_ELEM_TAG);
/* in case verts were not also included in the geom */
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
}
}
BM_mesh_bevel(bm,
offset,
offset_type,
profile_type,
seg,
profile,
affect_type,
false,
clamp_overlap,
nullptr,
-1,
material,
loop_slide,
mark_seam,
mark_sharp,
harden_normals,
face_strength_mode,
miter_outer,
miter_inner,
spread,
custom_profile,
vmesh_method,
CustomData_get_offset_named(&bm->vdata, CD_PROP_FLOAT, "bevel_weight_vert"),
CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge"));
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "verts.out", BM_VERT, BM_ELEM_TAG);
}
}
} // namespace blender

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Wrapper around #BM_mesh_bisect_plane
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
#define ELE_CUT 2
#define ELE_INPUT 4
void bmo_bisect_plane_exec(BMesh *bm, BMOperator *op)
{
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const bool use_snap_center = BMO_slot_bool_get(op->slots_in, "use_snap_center");
const bool clear_outer = BMO_slot_bool_get(op->slots_in, "clear_outer");
const bool clear_inner = BMO_slot_bool_get(op->slots_in, "clear_inner");
float plane_co[3];
float plane_no[3];
float plane[4];
BMO_slot_vec_get(op->slots_in, "plane_co", plane_co);
BMO_slot_vec_get(op->slots_in, "plane_no", plane_no);
if (is_zero_v3(plane_no)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Zero normal given");
return;
}
plane_from_point_normal_v3(plane, plane_co, plane_no);
/* tag geometry to bisect */
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "geom", BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_ALL_NOLOOP, ELE_INPUT);
BM_mesh_bisect_plane(bm, plane, use_snap_center, true, ELE_CUT, ELE_NEW, dist);
if (clear_outer || clear_inner) {
/* Use an array of vertices because 'geom' contains both verts and edges that may use them.
* Removing a vert may remove and edge which is later checked by #BMO_ITER.
* over-allocate the total possible vert count. */
const int vert_arr_max = min_ii(bm->totvert, BMO_slot_buffer_len(op->slots_in, "geom"));
BMVert **vert_arr = MEM_new_array_uninitialized<BMVert *>(vert_arr_max, __func__);
BMOIter siter;
BMVert *v;
float plane_inner[4];
float plane_outer[4];
STACK_DECLARE(vert_arr);
copy_v3_v3(plane_outer, plane);
copy_v3_v3(plane_inner, plane);
plane_outer[3] = plane[3] - dist;
plane_inner[3] = plane[3] + dist;
STACK_INIT(vert_arr, vert_arr_max);
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
if ((clear_outer && plane_point_side_v3(plane_outer, v->co) > 0.0f) ||
(clear_inner && plane_point_side_v3(plane_inner, v->co) < 0.0f))
{
STACK_PUSH(vert_arr, v);
}
}
while ((v = STACK_POP(vert_arr))) {
BM_vert_kill(bm, v);
}
MEM_delete(vert_arr);
}
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW | ELE_INPUT);
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_cut.out", BM_VERT | BM_EDGE, ELE_CUT);
}
} // namespace blender

View File

@@ -0,0 +1,666 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts across faces (splits faces) and bridge tool.
*/
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/**
* TODO(@ideasman42): Many connected edge loops can cause an error attempting
* to create faces with duplicate vertices. While this needs to be investigated,
* it's simple enough to check for this case, see: #102232.
*/
#define USE_DUPLICATE_FACE_VERT_CHECK
#define EDGE_MARK 4
#define EDGE_OUT 8
#define FACE_OUT 16
/* el_a and el_b _must_ be same size */
static void bm_bridge_splice_loops(BMesh *bm,
LinkData *el_a,
LinkData *el_b,
const float merge_factor)
{
BMOperator op_weld;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &op_weld, 0, "weld_verts");
slot_targetmap = BMO_slot_get(op_weld.slots_in, "targetmap");
do {
BMVert *v_a = static_cast<BMVert *>(el_a->data), *v_b = static_cast<BMVert *>(el_b->data);
BM_data_interp_from_verts(bm, v_a, v_b, v_b, merge_factor);
interp_v3_v3v3(v_b->co, v_a->co, v_b->co, merge_factor);
BLI_assert(v_a != v_b);
BMO_slot_map_elem_insert(&op_weld, slot_targetmap, v_a, v_b);
} while ((void)(el_b = el_b->next), (el_a = el_a->next));
BMO_op_exec(bm, &op_weld);
BMO_op_finish(bm, &op_weld);
}
/* get the 2 loops matching 2 verts.
* first attempt to get the face corners that use the edge defined by v1 & v2,
* if that fails just get any loop that's on the vert (the first one) */
static void bm_vert_loop_pair(BMesh *bm, BMVert *v1, BMVert *v2, BMLoop **l1, BMLoop **l2)
{
BMEdge *e = BM_edge_exists(v1, v2);
BMLoop *l = e->l;
if (l) {
if (l->v == v1) {
*l1 = l;
*l2 = l->next;
}
else {
*l2 = l;
*l1 = l->next;
}
}
else {
/* fallback to _any_ loop */
*l1 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v1, 0));
*l2 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v2, 0));
}
}
/* el_b can have any offset */
static float bm_edgeloop_offset_length(LinkData *el_a,
LinkData *el_b,
LinkData *el_b_first,
const float len_max)
{
float len = 0.0f;
BLI_assert(el_a->prev == nullptr); /* must be first */
do {
len += len_v3v3((static_cast<BMVert *>(el_a->data))->co,
(static_cast<BMVert *>(el_b->data))->co);
} while ((void)(el_b = el_b->next ? el_b->next : el_b_first),
(el_a = el_a->next) && (len < len_max));
return len;
}
static void bm_bridge_best_rotation(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b)
{
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(el_store_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
LinkData *el_a = static_cast<LinkData *>(lb_a->first);
LinkData *el_b = static_cast<LinkData *>(lb_b->first);
LinkData *el_b_first = el_b;
LinkData *el_b_best = nullptr;
float len_best = FLT_MAX;
for (; el_b; el_b = el_b->next) {
const float len = bm_edgeloop_offset_length(el_a, el_b, el_b_first, len_best);
if (len < len_best) {
el_b_best = el_b;
len_best = len;
}
}
if (el_b_best) {
BLI_listbase_rotate_first(lb_b, el_b_best);
}
}
static void bm_face_edges_tag_out(BMesh *bm, BMFace *f)
{
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMO_edge_flag_enable(bm, l_iter->e, EDGE_OUT);
} while ((l_iter = l_iter->next) != l_first);
}
static bool bm_edge_test_cb(BMEdge *e, void *bm_v)
{
return BMO_edge_flag_test((BMesh *)bm_v, e, EDGE_MARK);
}
static void bridge_loop_pair(BMesh *bm,
BMEdgeLoopStore *el_store_a,
BMEdgeLoopStore *el_store_b,
const bool use_merge,
const float merge_factor,
const int twist_offset)
{
const float eps = 0.00001f;
LinkData *el_a_first, *el_b_first;
const bool is_closed = BM_edgeloop_is_closed(el_store_a) && BM_edgeloop_is_closed(el_store_b);
int el_store_a_len, el_store_b_len;
bool el_store_b_free = false;
float el_dir[3];
float dot_a, dot_b;
const bool use_edgeout = true;
el_store_a_len = BM_edgeloop_length_get(el_store_a);
el_store_b_len = BM_edgeloop_length_get(el_store_b);
if (el_store_a_len < el_store_b_len) {
std::swap(el_store_a_len, el_store_b_len);
std::swap(el_store_a, el_store_b);
}
if (use_merge) {
BLI_assert(el_store_a_len == el_store_b_len);
}
if (el_store_a_len != el_store_b_len) {
BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false);
}
sub_v3_v3v3(el_dir, BM_edgeloop_center_get(el_store_a), BM_edgeloop_center_get(el_store_b));
if (is_closed) {
/* if all loops are closed this will calculate twice for all loops */
BM_edgeloop_calc_normal(bm, el_store_a);
BM_edgeloop_calc_normal(bm, el_store_b);
}
else {
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(el_store_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
/* normalizing isn't strictly needed but without we may get very large values */
float no[3];
float dir_a_orig[3], dir_b_orig[3];
float dir_a[3], dir_b[3];
const float *test_a, *test_b;
sub_v3_v3v3(dir_a_orig,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_a->first))->data))->co,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_a->last))->data))->co);
sub_v3_v3v3(dir_b_orig,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_b->first))->data))->co,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_b->last))->data))->co);
/* make the directions point out from the normals, 'no' is used as a temp var */
cross_v3_v3v3(no, dir_a_orig, el_dir);
cross_v3_v3v3(dir_a, no, el_dir);
cross_v3_v3v3(no, dir_b_orig, el_dir);
cross_v3_v3v3(dir_b, no, el_dir);
if (LIKELY(!is_zero_v3(dir_a) && !is_zero_v3(dir_b))) {
test_a = dir_a;
test_b = dir_b;
}
else {
/**
* This is a corner case:
*
* <pre>
* (loop a) (loop b)
* +--------+ +--------+
* </pre>
*
* When loops are aligned to the direction between
* the loops values of 'dir_a/b' is degenerate,
* in this case compare the original directions
* (before they were corrected by 'el_dir'),
* see: #43013
*/
test_a = dir_a_orig;
test_b = dir_b_orig;
}
if (dot_v3v3(test_a, test_b) < 0.0f) {
BM_edgeloop_flip(bm, el_store_b);
}
normalize_v3_v3(no, el_dir);
BM_edgeloop_calc_normal_aligned(bm, el_store_a, no);
BM_edgeloop_calc_normal_aligned(bm, el_store_b, no);
}
dot_a = dot_v3v3(BM_edgeloop_normal_get(el_store_a), el_dir);
dot_b = dot_v3v3(BM_edgeloop_normal_get(el_store_b), el_dir);
if (UNLIKELY((len_squared_v3(el_dir) < eps) || ((fabsf(dot_a) < eps) && (fabsf(dot_b) < eps)))) {
/* in this case there is no depth between the two loops,
* eg: 2x 2d circles, one scaled smaller,
* in this case 'el_dir' can't be used, just ensure we have matching flipping. */
if (dot_v3v3(BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b)) < 0.0f) {
BM_edgeloop_flip(bm, el_store_b);
}
}
else if ((dot_a < 0.0f) != (dot_b < 0.0f)) {
BM_edgeloop_flip(bm, el_store_b);
}
/* we only care about flipping if we make faces */
if (use_merge == false) {
float no[3];
add_v3_v3v3(no, BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b));
if (dot_v3v3(no, el_dir) < 0.0f) {
BM_edgeloop_flip(bm, el_store_a);
BM_edgeloop_flip(bm, el_store_b);
}
/* vote on winding (so new face winding is based on existing connected faces) */
if (bm->totface) {
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
int winding_votes[2] = {0, 0};
int winding_dir = 1;
for (i = 0; i < 2; i++, winding_dir = -winding_dir) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], &el);
if (el_next) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el.data),
static_cast<BMVert *>(el_next->data));
if (e && BM_edge_is_boundary(e)) {
winding_votes[i] += ((e->l->v == el.data) ? winding_dir : -winding_dir);
}
}
}
}
if (winding_votes[0] || winding_votes[1]) {
bool flip[2] = {false, false};
/* for direction aligned loops we can't rely on the directly we have,
* use the winding defined by the connected faces (see #48356). */
if (fabsf(dot_a) < eps) {
if (winding_votes[0] < 0) {
flip[0] = !flip[0];
winding_votes[0] *= -1;
}
}
if (fabsf(dot_b) < eps) {
if (winding_votes[1] < 0) {
flip[1] = !flip[1];
winding_votes[1] *= -1;
}
}
/* when both loops contradict the winding, flip them so surrounding geometry matches */
if ((winding_votes[0] + winding_votes[1]) < 0) {
flip[0] = !flip[0];
flip[1] = !flip[1];
/* valid but unused */
#if 0
winding_votes[0] *= -1;
winding_votes[1] *= -1;
#endif
}
if (flip[0]) {
BM_edgeloop_flip(bm, el_store_a);
}
if (flip[1]) {
BM_edgeloop_flip(bm, el_store_b);
}
}
}
}
if (el_store_a_len > el_store_b_len) {
el_store_b = BM_edgeloop_copy(el_store_b);
BM_edgeloop_expand(bm, el_store_b, el_store_a_len, false, nullptr);
el_store_b_free = true;
}
if (is_closed) {
bm_bridge_best_rotation(el_store_a, el_store_b);
/* add twist */
if (twist_offset != 0) {
const int len_b = BM_edgeloop_length_get(el_store_b);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
LinkData *el_b = static_cast<LinkData *>(BLI_rfindlink(lb_b, mod_i(twist_offset, len_b)));
BLI_listbase_rotate_first(lb_b, el_b);
}
}
/* Assign after flipping is finalized */
el_a_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_a)->first);
el_b_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_b)->first);
if (use_merge) {
bm_bridge_splice_loops(bm, el_a_first, el_b_first, merge_factor);
}
else {
LinkData *el_a = el_a_first;
LinkData *el_b = el_b_first;
LinkData *el_a_next;
LinkData *el_b_next;
while (true) {
BMFace *f, *f_example;
BMLoop *l_iter;
BMVert *v_a, *v_b, *v_a_next, *v_b_next;
BMLoop *l_a = nullptr;
BMLoop *l_b = nullptr;
BMLoop *l_a_next = nullptr;
BMLoop *l_b_next = nullptr;
if (is_closed) {
el_a_next = BM_EDGELINK_NEXT(el_store_a, el_a);
el_b_next = BM_EDGELINK_NEXT(el_store_b, el_b);
}
else {
el_a_next = el_a->next;
el_b_next = el_b->next;
if (ELEM(nullptr, el_a_next, el_b_next)) {
break;
}
}
v_a = static_cast<BMVert *>(el_a->data);
v_b = static_cast<BMVert *>(el_b->data);
v_a_next = static_cast<BMVert *>(el_a_next->data);
v_b_next = static_cast<BMVert *>(el_b_next->data);
/* get loop data - before making the face */
if (v_b != v_b_next) {
bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
bm_vert_loop_pair(bm, v_b, v_b_next, &l_b, &l_b_next);
}
else {
/* lazy, could be more clever here */
bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
l_b = l_b_next = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v_b, 0));
}
if (l_a && l_a_next == nullptr) {
l_a_next = l_a;
}
if (l_a_next && l_a == nullptr) {
l_a = l_a_next;
}
if (l_b && l_b_next == nullptr) {
l_b_next = l_b;
}
if (l_b_next && l_b == nullptr) {
l_b = l_b_next;
}
f_example = l_a ? l_a->f : (l_b ? l_b->f : nullptr);
if (v_b != v_b_next) {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
BLI_assert((v_b != v_b_next) && (v_a_next != v_a));
if (UNLIKELY(ELEM(v_b, v_a_next, v_a) || ELEM(v_b_next, v_a_next, v_a))) {
f = nullptr;
}
else
#endif
{
BMVert *v_arr[4] = {v_b, v_b_next, v_a_next, v_a};
f = BM_face_exists(v_arr, 4);
if (f == nullptr) {
/* copy if loop data if its is missing on one ring */
f = BM_face_create_verts(bm, v_arr, 4, nullptr, BM_CREATE_NOP, true);
l_iter = BM_FACE_FIRST_LOOP(f);
if (l_b) {
BM_elem_attrs_copy(bm, l_b, l_iter);
}
l_iter = l_iter->next;
if (l_b_next) {
BM_elem_attrs_copy(bm, l_b_next, l_iter);
}
l_iter = l_iter->next;
if (l_a_next) {
BM_elem_attrs_copy(bm, l_a_next, l_iter);
}
l_iter = l_iter->next;
if (l_a) {
BM_elem_attrs_copy(bm, l_a, l_iter);
}
}
}
}
else {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
BLI_assert(v_a_next != v_a);
if (UNLIKELY(ELEM(v_b, v_a_next, v_a))) {
f = nullptr;
}
else
#endif
{
BMVert *v_arr[3] = {v_b, v_a_next, v_a};
f = BM_face_exists(v_arr, 3);
if (f == nullptr) {
/* fan-fill a triangle */
f = BM_face_create_verts(bm, v_arr, 3, nullptr, BM_CREATE_NOP, true);
l_iter = BM_FACE_FIRST_LOOP(f);
if (l_b) {
BM_elem_attrs_copy(bm, l_b, l_iter);
}
l_iter = l_iter->next;
if (l_a_next) {
BM_elem_attrs_copy(bm, l_a_next, l_iter);
}
l_iter = l_iter->next;
if (l_a) {
BM_elem_attrs_copy(bm, l_a, l_iter);
}
}
}
}
#ifdef USE_DUPLICATE_FACE_VERT_CHECK
if (f != nullptr)
#endif
{
if (f_example && (f_example != f)) {
BM_elem_attrs_copy(bm, f_example, f);
}
BMO_face_flag_enable(bm, f, FACE_OUT);
BM_elem_flag_enable(f, BM_ELEM_TAG);
/* tag all edges of the face, untag the loop edges after */
if (use_edgeout) {
bm_face_edges_tag_out(bm, f);
}
}
if (el_a_next == el_a_first) {
break;
}
el_a = el_a_next;
el_b = el_b_next;
}
}
if (el_store_a_len != el_store_b_len) {
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
BMOperator op_sub;
/* when we have to bridge between different sized edge-loops,
* be clever and post-process for best results */
/* triangulate inline */
BMO_op_initf(bm, &op_sub, 0, "triangulate faces=%hf", BM_ELEM_TAG, true);
/* calc normals for input faces before executing */
{
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
BM_face_normal_update(f);
}
}
BMO_op_exec(bm, &op_sub);
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, FACE_OUT);
BMO_slot_buffer_hflag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, BM_ELEM_TAG, false);
BMO_op_finish(bm, &op_sub);
/* tag verts on each side so we can restrict rotation of edges to verts on the same side */
for (i = 0; i < 2; i++) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
BM_elem_flag_set((BMVert *)el.data, BM_ELEM_TAG, i);
}
}
BMO_op_initf(bm,
&op_sub,
0,
"beautify_fill faces=%hf edges=ae use_restrict_tag=%b method=%i",
BM_ELEM_TAG,
true,
1);
if (use_edgeout) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
BMO_face_flag_enable(bm, f, FACE_OUT);
bm_face_edges_tag_out(bm, f);
}
}
BMO_op_exec(bm, &op_sub);
/* there may also be tagged faces that didn't rotate, mark input */
if (use_edgeout) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_out, "geom.out", BM_FACE) {
BMO_face_flag_enable(bm, f, FACE_OUT);
bm_face_edges_tag_out(bm, f);
}
}
else {
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "geom.out", BM_FACE, FACE_OUT);
}
BMO_op_finish(bm, &op_sub);
}
if (use_edgeout && use_merge == false) {
/* we've enabled all face edges above, now disable all loop edges */
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
for (i = 0; i < 2; i++) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], &el);
if (el_next) {
if (el.data != el_next->data) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el.data),
static_cast<BMVert *>(el_next->data));
BMO_edge_flag_disable(bm, e, EDGE_OUT);
}
}
}
}
}
if (el_store_b_free) {
BM_edgeloop_free(el_store_b);
}
}
void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op)
{
ListBaseT<BMEdgeLoopStore> eloops = {nullptr};
/* merge-bridge support */
const bool use_pairs = BMO_slot_bool_get(op->slots_in, "use_pairs");
const bool use_merge = BMO_slot_bool_get(op->slots_in, "use_merge");
const float merge_factor = BMO_slot_float_get(op->slots_in, "merge_factor");
const bool use_cyclic = BMO_slot_bool_get(op->slots_in, "use_cyclic") && (use_merge == false);
const int twist_offset = BMO_slot_int_get(op->slots_in, "twist_offset");
int count;
bool changed = false;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_cb, bm);
BM_mesh_edgeloops_calc_center(bm, &eloops);
if (count < 2) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select at least two edge loops");
goto cleanup;
}
if (use_pairs && (count % 2)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select an even number of loops to bridge pairs");
goto cleanup;
}
if (use_merge) {
bool match = true;
const int eloop_len = BM_edgeloop_length_get(static_cast<BMEdgeLoopStore *>(eloops.first));
for (BMEdgeLoopStore &el_store : eloops) {
if (eloop_len != BM_edgeloop_length_get(&el_store)) {
match = false;
break;
}
}
if (!match) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Selected loops must have equal edge counts");
goto cleanup;
}
}
if (count > 2) {
if (use_pairs) {
BM_mesh_edgeloops_calc_normal(bm, &eloops);
}
BM_mesh_edgeloops_calc_order(bm, &eloops, use_pairs);
}
/* No ListBaseT iterator because of incomplete type. */
for (const Link *el_store = static_cast<const Link *>(eloops.first); el_store;
el_store = el_store->next)
{
Link *el_store_next = el_store->next;
if (el_store_next == nullptr) {
if (use_cyclic && (count > 2)) {
el_store_next = static_cast<Link *>(eloops.first);
}
else {
break;
}
}
bridge_loop_pair(bm,
reinterpret_cast<BMEdgeLoopStore *>(const_cast<Link *>(el_store)),
reinterpret_cast<BMEdgeLoopStore *>(el_store_next),
use_merge,
merge_factor,
twist_offset);
if (use_pairs) {
el_store = el_store->next;
}
changed = true;
}
cleanup:
BM_mesh_edgeloops_free(&eloops);
if (changed) {
if (use_merge == false) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
}
}
}
} // namespace blender

View File

@@ -0,0 +1,747 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Circularize selected boundary chains.
*/
#include "BLI_kdopbvh.hh"
#include "BLI_map.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include <numbers>
#include <optional>
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/** Maximum iterations for the non linear least squares solver. */
constexpr int NON_LINEAR_LEAST_SQUARES_MAX_ITERATIONS = 500;
/** Threshold for considering a vertex to be on the mirror plane. */
constexpr float MIRROR_LIMIT = 0.001f;
/** Used for convergence checks and precision comparisons. */
constexpr float CIRCULARIZE_EPSILON = 1e-6f;
/** Method used for fitting the circle. */
enum FitMethod { FIT_METHOD_LEAST_SQUARE = 0, FIT_METHOD_CONTRACT = 1 };
/** Holds data for a vertex projected onto the local plane. */
struct CircleVert {
BMVert *v;
/** Current position on the plane. */
float2 co_2d;
/** Where it should move to on the circle. */
float2 target_2d;
};
/** Stores the boundary geometry that defines the circle. */
struct VertChain {
/** The ordered vertices that defines the circle's boundary. */
Vector<BMVert *> verts;
/** This is true if the path forms a closed chain, for open chains it's false. */
bool is_closed;
};
/**
* Detects whether an edge should be considered a valid boundary
* edge for circularization.
* Valid boundary edges are edges that are selected, not hidden
* and are not interior. They lie on the boundary between a selected
* face and an unselected face and do not lie on the mirror plane.
*/
static bool is_valid_boundary_edge(BMEdge *e, const char hflag, const bool check_axis[3])
{
if (!BM_elem_flag_test(e, hflag) || BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
return false;
}
/* Wire edges are not valid boundary edges. */
if (!e->l) {
return false;
}
int selected_face_count = 0;
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, hflag)) {
selected_face_count++;
if (selected_face_count > 1) {
break;
}
}
}
if (selected_face_count > 1) {
return false;
}
/* If both vertices of an edge lie close to the same coordinate plane
* (X = 0, Y = 0, or Z = 0), the edge lies on a mirror plane and is not
* considered a valid boundary edge. */
for (int i = 0; i < 3; i++) {
if (check_axis[i] && std::abs(e->v1->co[i]) < MIRROR_LIMIT &&
std::abs(e->v2->co[i]) < MIRROR_LIMIT)
{
return false;
}
}
return true;
}
/**
* Traverses a connected path of boundary edges to form a continuous sequence of vertices.
* This function handles two cases:
* Closed chains: walks until the traversal returns to the start vertex.
* Open chains: walks in one direction until a dead end, then walks in the
* opposite direction from the start edge and merges the results.
*/
static std::optional<VertChain> walk_boundary_chain(BMEdge *start_edge,
Set<BMEdge *> &visited,
const char hflag,
const bool check_axis[3])
{
VertChain chain_data;
/* Finds the next valid boundary edge that isn't visited. */
auto get_next_edge_fn = [&](BMVert *v, BMEdge *exclude_e) -> BMEdge * {
BMIter eiter;
BMEdge *e_next;
BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
if (e_next != exclude_e && !visited.contains(e_next)) {
if (is_valid_boundary_edge(e_next, hflag, check_axis)) {
return e_next;
}
}
}
return nullptr;
};
/* Walks in one direction until a dead end. */
auto walk_fn = [&](BMVert *curr_v, BMEdge *curr_e, Vector<BMVert *> &list) {
while (true) {
BMEdge *next_e = get_next_edge_fn(curr_v, curr_e);
if (!next_e) {
break;
}
/* Move to next vertex. */
curr_v = BM_edge_other_vert(next_e, curr_v);
curr_e = next_e;
list.append(curr_v);
visited.add(curr_e);
}
};
chain_data.verts.append(start_edge->v1);
chain_data.verts.append(start_edge->v2);
visited.add(start_edge);
/* The initial edge direction (v1 -> v2) is arbitrary.
* We walk from v2 to extend this sequence. */
walk_fn(start_edge->v2, start_edge, chain_data.verts);
/* If the traversal forms a closed chain, the last vertex will match the first.
* Remove the duplicate end vertex. */
if (chain_data.verts.size() > 2 && chain_data.verts.first() == chain_data.verts.last()) {
if (chain_data.verts.size() < 4) {
return std::nullopt;
}
chain_data.verts.remove_last();
chain_data.is_closed = true;
return chain_data;
}
/* If we are here, the chain is open.
* We need to check the other direction from the start vertex. */
Vector<BMVert *> pre_chain;
walk_fn(start_edge->v1, start_edge, pre_chain);
if (!pre_chain.is_empty()) {
std::reverse(pre_chain.begin(), pre_chain.end());
pre_chain.extend(chain_data.verts);
chain_data.verts = std::move(pre_chain);
}
chain_data.is_closed = false;
if (chain_data.verts.size() < 3) {
return std::nullopt;
}
return chain_data;
}
/** Collects all valid boundary edge chains from the current selection. */
static void bm_vert_chain_extract_from_boundary_edges(BMesh *bm,
Vector<VertChain> &r_chains,
const char hflag,
const bool check_axis[3])
{
Set<BMEdge *> visited;
BMIter iter;
BMEdge *edge;
BM_ITER_MESH (edge, &iter, bm, BM_EDGES_OF_MESH) {
if (visited.contains(edge)) {
continue;
}
if (!is_valid_boundary_edge(edge, hflag, check_axis)) {
continue;
}
std::optional<VertChain> ld = walk_boundary_chain(edge, visited, hflag, check_axis);
if (ld.has_value()) {
r_chains.append(*ld);
}
}
}
/** Computes the local coordinate system defining the 2D plane of the vertex chain. */
static float3x3 bm_vert_chain_orientation_matrix_calc(Span<BMVert *> chain, float3 &r_center)
{
r_center = float3(0.0f);
for (BMVert *v : chain) {
r_center += float3(v->co);
}
r_center /= float(chain.size());
BMVert *prev = chain.last();
float3 normal = float3(0.0f);
/* Compute a best fit plane normal for the chain using Newell's method. */
for (BMVert *curr : chain) {
add_newell_cross_v3_v3v3(normal, prev->co, curr->co);
prev = curr;
}
normal = math::normalize(normal);
float3 guess = float3(1.0f, 0.0f, 0.0f);
/* If normal is parallel to (1,0,0),the cross product would be zero.
* In that case, we switch the guess to the y axis to allow a valid
* perpendicular vector to be found. */
if (std::abs(math::dot(normal, guess)) > 0.99f) {
guess = float3(0.0f, 1.0f, 0.0f);
}
float3 p = math::normalize(math::cross(normal, guess));
float3 q = math::cross(normal, p);
float3x3 mat;
mat.x_axis() = p;
mat.y_axis() = q;
mat.z_axis() = normal;
return mat;
}
/** Projects 3D vertex coordinates onto a local 2D plane defined by the P and Q basis vectors. */
static void project_chain_to_2d(Span<BMVert *> chain,
const float3 &center,
const float3x3 &mat,
Vector<CircleVert> &r_2d_verts)
{
r_2d_verts.reserve(chain.size());
for (BMVert *v : chain) {
float3 vec = float3(v->co) - center;
CircleVert cv{.v = v, .co_2d = {math::dot(vec, mat.x_axis()), math::dot(vec, mat.y_axis())}};
r_2d_verts.append(cv);
}
}
static void calculate_circle_best_fit(Span<CircleVert> verts,
const std::optional<float2> &fixed_center,
float2 &r_center,
float *r_radius)
{
/* If the center is locked, we skip the solver. The best fit for the fixed center
* is simply the average radius. */
if (fixed_center.has_value()) {
r_center = *fixed_center;
float radius = 0.0f;
for (const CircleVert &cv : verts) {
radius += math::length(cv.co_2d);
}
radius /= verts.size();
*r_radius = radius;
return;
}
/* Initial guesses. */
float2 initial_center = float2(0.0f);
float initial_radius = 1.0f;
for (int iter = 0; iter < NON_LINEAR_LEAST_SQUARES_MAX_ITERATIONS; iter++) {
float3x3 normal_matrix = float3x3::zero();
float3 jacobian_transpose_residual = float3(0.0f);
for (const CircleVert &cv : verts) {
const float2 d_vec = initial_center - cv.co_2d;
const float distance = math::length(d_vec);
if (distance < CIRCULARIZE_EPSILON) {
continue;
}
const float3 j_row = {d_vec / distance, -1.0f};
const float residual = initial_radius - distance;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
normal_matrix[col][row] += j_row[row] * j_row[col];
}
jacobian_transpose_residual[row] += j_row[row] * residual;
}
}
bool success;
float3x3 inverse_normal_matrix = math::invert(normal_matrix, success);
if (!success) {
break;
}
float3 delta = inverse_normal_matrix * jacobian_transpose_residual;
initial_center.x += delta.x;
initial_center.y += delta.y;
initial_radius += delta.z;
/* Check for convergence to stop iterating if we're close enough to the optimal
* solution. */
if (std::abs(delta.x) < CIRCULARIZE_EPSILON && std::abs(delta.y) < CIRCULARIZE_EPSILON &&
std::abs(delta.z) < CIRCULARIZE_EPSILON)
{
break;
}
}
r_center = initial_center;
*r_radius = initial_radius;
}
static void calculate_circle_inside_fit(Span<CircleVert> verts,
const std::optional<float2> &fixed_center,
float2 &r_center,
float *r_radius)
{
float2 center;
if (fixed_center.has_value()) {
center = *fixed_center;
}
else {
float total_edge_length = 0.0f;
center = float2(0.0f);
float2 prev_co = verts.last().co_2d;
for (const CircleVert &cv : verts) {
const float2 &curr_co = cv.co_2d;
const float edge_length = math::distance(prev_co, curr_co);
center += (prev_co + curr_co) * edge_length;
total_edge_length += edge_length;
prev_co = curr_co;
}
if (total_edge_length != 0.0f) {
center *= (0.5f / total_edge_length);
}
}
float radius_sq = FLT_MAX;
for (const CircleVert &cv : verts) {
const float dist_sq = math::distance_squared(center, cv.co_2d);
radius_sq = std::min(radius_sq, dist_sq);
}
r_center = center;
*r_radius = math::sqrt(radius_sq);
}
static void calculate_target_locations(MutableSpan<CircleVert> verts,
const float2 &center,
const float radius,
const bool is_regular,
const bool is_closed,
const float rotation_angle)
{
float step = 0.0f;
float start_angle = 0.0f;
if (is_regular) {
float total_angle = 2.0f * std::numbers::pi_v<float>;
int divisions = verts.size();
/* For open chains, we calculate the total angle obtained by traversing
* the vertices. Unlike closed chains whose total angle is 2*Pi,
* we cannot assume Pi for an open chain because it might span any amount
* of the circle. */
if (!is_closed && divisions > 1) {
total_angle = 0.0f;
divisions = verts.size() - 1;
float2 vec_prev = verts[0].co_2d - center;
vec_prev = math::normalize(vec_prev);
/* Skip the first vertex because it was used to initialize vec_prev otherwise
* we'll end up with a self comparison in the first iteration. */
for (const int i : verts.index_range().drop_front(1)) {
float2 vec_curr = verts[i].co_2d - center;
vec_curr = math::normalize(vec_curr);
total_angle -= angle_signed_v2v2(vec_prev, vec_curr);
vec_prev = vec_curr;
}
/* In case the angle exceeds a full revolution, clamp it to the max angle. */
const float max_angle = 2.0f * std::numbers::pi_v<float>;
total_angle = std::clamp(total_angle, -max_angle, max_angle);
}
step = total_angle / divisions;
float sum_sin = 0.0f;
float sum_cos = 0.0f;
/* Using only one vertex as the basis for the start angle can skew
* the resulting rotation of the circle in an undesirable way.
* So instead, we calculate the circular mean of the rotation by measuring
* the angular deviation for every vertex and averaging them to find the best
* fit alignment.
* Note: We accumulate the sine and cosine of the angular deviations to calculate
* the circular mean because angles wrap around 360 degrees, and averaging them directly
* would give incorrect results. */
for (const int i : verts.index_range()) {
float2 vec = verts[i].co_2d - center;
const float angle_diff = atan2f(vec.y, vec.x) - (step * i);
sum_sin += sinf(angle_diff);
sum_cos += cosf(angle_diff);
}
start_angle = atan2f(sum_sin, sum_cos);
}
for (const int i : verts.index_range()) {
float angle;
if (is_regular) {
angle = start_angle + step * i - rotation_angle;
}
else {
float2 vec = verts[i].co_2d - center;
angle = atan2f(vec.y, vec.x) - rotation_angle;
}
verts[i].target_2d.x = center.x + cosf(angle) * radius;
verts[i].target_2d.y = center.y + sinf(angle) * radius;
}
}
struct NearestTriUserData {
Span<std::array<BMLoop *, 3>> looptris;
};
/** Callback for BLI_bvhtree_find_nearest. Finds the closest point on the given triangle. */
static void nearest_tri_cb(void *userdata, int index, const float co[3], BVHTreeNearest *nearest)
{
const NearestTriUserData *data = static_cast<const NearestTriUserData *>(userdata);
const std::array<BMLoop *, 3> &ltri = data->looptris[index];
float3 closest;
closest_on_tri_to_point_v3(closest, co, ltri[0]->v->co, ltri[1]->v->co, ltri[2]->v->co);
const float dist_sq = math::distance_squared(float3(co), closest);
if (dist_sq < nearest->dist_sq) {
nearest->dist_sq = dist_sq;
nearest->index = index;
copy_v3_v3(nearest->co, closest);
}
}
using FaceTessellationCache = Map<BMFace *, Array<std::array<BMVert *, 3>>>;
static void project_on_mesh(BVHTree *bvh_tree,
NearestTriUserData *bvh_data,
BMVert *v,
const float3 &center_pos,
const float3 &normal,
float3 &r_pos,
FaceTessellationCache &tess_cache)
{
float3 vec = center_pos - float3(v->co);
float length;
vec = math::normalize_and_get_length(vec, length);
/* If vertices are too close, normalization can fail. */
if (length == 0.0f) {
r_pos = center_pos;
return;
}
const float angle = angle_normalized_v3v3(vec, normal);
if (std::abs(angle) < CIRCULARIZE_EPSILON ||
std::abs(std::numbers::pi_v<float> - angle) < CIRCULARIZE_EPSILON)
{
r_pos = float3(v->co);
return;
}
float3 p2 = center_pos + normal;
float best_dist_sq = FLT_MAX;
bool found = false;
auto test_tri_fn = [&](BMVert *v1, BMVert *v2, BMVert *v3) {
float lambda;
float2 uv;
if (isect_line_tri_v3(center_pos, p2, v1->co, v2->co, v3->co, &lambda, uv)) {
float3 hit_pos = center_pos + normal * lambda;
const float dist_sq = math::distance_squared(center_pos, hit_pos);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
r_pos = hit_pos;
found = true;
}
}
};
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (f->len < 3 || BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
continue;
}
if (ELEM(f->len, 3, 4)) {
BMLoop *l_start = f->l_first;
BMVert *v1 = l_start->prev->v;
BMVert *v2 = l_start->v;
BMVert *v3 = l_start->next->v;
test_tri_fn(v1, v2, v3);
if (f->len == 4) {
BMVert *v4 = l_start->next->next->v;
test_tri_fn(v1, v3, v4);
}
}
else {
const Array<std::array<BMVert *, 3>> &cached = tess_cache.lookup_or_add_cb(f, [&]() {
const int tottri = f->len - 2;
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops(f->len);
Array<std::array<uint, 3>, BM_DEFAULT_NGON_STACK_SIZE> index(tottri);
BM_face_calc_tessellation(
f, false, loops.data(), reinterpret_cast<uint(*)[3]>(index.data()));
Array<std::array<BMVert *, 3>> tris(tottri);
for (int i = 0; i < tottri; i++) {
tris[i] = {loops[index[i][0]]->v, loops[index[i][1]]->v, loops[index[i][2]]->v};
}
return tris;
});
for (const std::array<BMVert *, 3> &tri : cached) {
test_tri_fn(tri[0], tri[1], tri[2]);
}
}
}
if (found) {
return;
}
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
float3 closest;
closest_to_line_v3(closest, center_pos, e->v1->co, e->v2->co);
const float fac = line_point_factor_v3(closest, e->v1->co, e->v2->co);
if (fac > CIRCULARIZE_EPSILON && fac < 1.0f - CIRCULARIZE_EPSILON) {
const float dist_sq = math::distance_squared(center_pos, closest);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
r_pos = closest;
found = true;
}
}
}
if (found) {
return;
}
if (bvh_tree) {
BVHTreeNearest nearest;
nearest.dist_sq = FLT_MAX;
nearest.index = -1;
BLI_bvhtree_find_nearest(bvh_tree, center_pos, &nearest, nearest_tri_cb, bvh_data);
if (nearest.index != -1) {
r_pos = float3(nearest.co);
return;
}
}
r_pos = center_pos;
}
void bmo_circularize_exec(BMesh *bm, BMOperator *op)
{
const float factor = BMO_slot_float_get(op->slots_in, "factor");
const float custom_radius = BMO_slot_float_get(op->slots_in, "custom_radius");
const float angle = BMO_slot_float_get(op->slots_in, "angle");
const int fit_method = BMO_slot_int_get(op->slots_in, "fit_method");
const float flatten = BMO_slot_float_get(op->slots_in, "flatten");
const bool regular = BMO_slot_bool_get(op->slots_in, "regular");
const bool check_axis[3] = {
BMO_slot_bool_get(op->slots_in, "mirror_x"),
BMO_slot_bool_get(op->slots_in, "mirror_y"),
BMO_slot_bool_get(op->slots_in, "mirror_z"),
};
const bool lock_x = BMO_slot_bool_get(op->slots_in, "lock_x");
const bool lock_y = BMO_slot_bool_get(op->slots_in, "lock_y");
const bool lock_z = BMO_slot_bool_get(op->slots_in, "lock_z");
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(
bm, op->slots_in, "geom", BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
Vector<VertChain> chains;
bm_vert_chain_extract_from_boundary_edges(bm, chains, BM_ELEM_TAG, check_axis);
/* Builds a BVH tree when flatten is disabled. Without this we would have to iterate
* over every face in the mesh for every vertex which is too slow.
*
* Note: There is the possibility of a feedback loop here, with the geometry
* being manipulated which is used in the BVH tree. However, in practice this
* is an acceptable limitation that is unlikely to cause problems. */
Vector<std::array<BMLoop *, 3>> looptris;
BVHTree *bvh_tree = nullptr;
NearestTriUserData bvh_data = {};
FaceTessellationCache tess_cache;
if (flatten < 1.0f) {
const int tot_tri = poly_to_tri_count(bm->totface, bm->totloop);
looptris.reinitialize(tot_tri);
BM_mesh_calc_tessellation(bm, looptris);
bvh_tree = BLI_bvhtree_new(tot_tri, 0.0f, 8, 8);
for (const int i : looptris.index_range()) {
const std::array<BMLoop *, 3> &ltri = looptris[i];
float3 tri_coords[3] = {
float3(ltri[0]->v->co), float3(ltri[1]->v->co), float3(ltri[2]->v->co)};
BLI_bvhtree_insert(bvh_tree, i, reinterpret_cast<float *>(tri_coords), 3);
}
BLI_bvhtree_balance(bvh_tree);
bvh_data.looptris = looptris;
}
for (VertChain &chain_data : chains) {
Vector<BMVert *> &chain = chain_data.verts;
float3 normal_accum = float3(0.0f);
for (BMVert *v : chain) {
normal_accum += float3(v->no);
}
float3 center_3d;
float3x3 mat = bm_vert_chain_orientation_matrix_calc(chain, center_3d);
/* Reverse the chain winding if the Newell normal opposes the cumulative vertex normal. */
if (math::dot(mat.z_axis(), normal_accum) < 0.0f) {
std::reverse(chain.begin(), chain.end());
mat = bm_vert_chain_orientation_matrix_calc(chain, center_3d);
}
bool is_mirrored = false;
int mirror_axis = -1;
if (!chain_data.is_closed) {
BMVert *v_start = chain.first();
BMVert *v_end = chain.last();
for (int i = 0; i < 3; i++) {
if (check_axis[i] && std::abs(v_start->co[i]) < MIRROR_LIMIT &&
std::abs(v_end->co[i]) < MIRROR_LIMIT)
{
is_mirrored = true;
mirror_axis = i;
}
}
}
/* For open chains on a symmetry plane, force the center to the midpoint of the endpoints
* to keep the circle aligned with the mirror plane. */
if (is_mirrored) {
BMVert *v_start = chain.first();
BMVert *v_end = chain.last();
center_3d = math::midpoint(float3(v_start->co), float3(v_end->co));
float3 p = math::normalize(float3(v_start->co) - center_3d);
float3 q = math::normalize(math::cross(mat.z_axis(), p));
mat.x_axis() = p;
mat.y_axis() = q;
}
Vector<CircleVert> circle_verts;
project_chain_to_2d(chain, center_3d, mat, circle_verts);
float2 circle_center_2d;
float radius;
std::optional<float2> fixed_center = std::nullopt;
if (is_mirrored) {
fixed_center = float2(0.0f);
}
if (fit_method == FIT_METHOD_CONTRACT) {
calculate_circle_inside_fit(circle_verts, fixed_center, circle_center_2d, &radius);
}
else {
calculate_circle_best_fit(circle_verts, fixed_center, circle_center_2d, &radius);
}
if (custom_radius > 0.0f) {
radius = custom_radius;
}
calculate_target_locations(
circle_verts, circle_center_2d, radius, regular, chain_data.is_closed, angle);
for (const CircleVert &cv : circle_verts) {
const float3 target_local(cv.target_2d.x, cv.target_2d.y, 0.0f);
float3 final_pos = center_3d + mat * target_local;
if (flatten < 1.0f) {
float3 projected_pos;
project_on_mesh(
bvh_tree, &bvh_data, cv.v, final_pos, mat.z_axis(), projected_pos, tess_cache);
interp_v3_v3v3(final_pos, projected_pos, final_pos, flatten);
}
/* If this vertex is an endpoint of a mirrored chain, force it
* exactly to 0.0 on the mirror axis.
* There are some cases where a slight floating point drift ends up being
* produced which prevents the mirror modifier from merging vertices. */
if (is_mirrored) {
BLI_assert(mirror_axis != -1);
if (cv.v == chain.first() || cv.v == chain.last()) {
final_pos[mirror_axis] = 0.0f;
}
}
/* If an axis is locked, restore the original coordinate. */
if (lock_x || lock_y || lock_z) {
const float *orig = cv.v->co;
if (lock_x) {
final_pos.x = orig[0];
}
if (lock_y) {
final_pos.y = orig[1];
}
if (lock_z) {
final_pos.z = orig[2];
}
}
interp_v3_v3v3(cv.v->co, cv.v->co, final_pos, factor);
}
}
/* There would be a memory leak if this isn't freed. */
if (bvh_tree) {
BLI_bvhtree_free(bvh_tree);
}
}
} // namespace blender

View File

@@ -0,0 +1,220 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts across faces (splits faces).
*/
#include <array>
#include "BLI_array.hh"
#include "BLI_linklist_stack.h"
#include "BLI_utildefines.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define VERT_INPUT 1
#define EDGE_OUT 1
/* Edge spans 2 VERT_INPUT's, its a NOP,
* but include in "edges.out" */
#define EDGE_OUT_ADJ 2
#define FACE_TAG 2
#define FACE_EXCLUDE 4
static int bm_face_connect_verts(BMesh *bm, BMFace *f, const bool check_degenerate)
{
const uint pair_split_max = f->len / 2;
Array<std::array<BMLoop *, 2>, BM_DEFAULT_NGON_STACK_SIZE> loops_split_buf(pair_split_max);
BMLoop *(*loops_split)[2] = reinterpret_cast<BMLoop *(*)[2]>(loops_split_buf.data());
STACK_DECLARE(loops_split);
Array<std::array<BMVert *, 2>, BM_DEFAULT_NGON_STACK_SIZE> verts_pair_buf(pair_split_max);
std::array<BMVert *, 2> *verts_pair = verts_pair_buf.data();
STACK_DECLARE(verts_pair);
BMLoop *l_tag_prev = nullptr, *l_tag_first = nullptr;
BMLoop *l_iter, *l_first;
uint i;
int result = 1;
STACK_INIT(loops_split, pair_split_max);
STACK_INIT(verts_pair, pair_split_max);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (BMO_vert_flag_test(bm, l_iter->v, VERT_INPUT) &&
/* Ensure this vertex isn't part of a contiguous group. */
((BMO_vert_flag_test(bm, l_iter->prev->v, VERT_INPUT) == 0) ||
(BMO_vert_flag_test(bm, l_iter->next->v, VERT_INPUT) == 0)))
{
if (!l_tag_prev) {
l_tag_prev = l_tag_first = l_iter;
continue;
}
if (!BM_loop_is_adjacent(l_tag_prev, l_iter)) {
BMEdge *e;
e = BM_edge_exists(l_tag_prev->v, l_iter->v);
if (e == nullptr || !BMO_edge_flag_test(bm, e, EDGE_OUT)) {
BMLoop **l_pair = STACK_PUSH_RET(loops_split);
l_pair[0] = l_tag_prev;
l_pair[1] = l_iter;
}
}
l_tag_prev = l_iter;
}
} while ((l_iter = l_iter->next) != l_first);
if (STACK_SIZE(loops_split) == 0) {
return 0;
}
if (!BM_loop_is_adjacent(l_tag_first, l_tag_prev) &&
/* ensure we don't add the same pair twice */
(((loops_split[0][0] == l_tag_first) && (loops_split[0][1] == l_tag_prev)) == 0))
{
BMLoop **l_pair = STACK_PUSH_RET(loops_split);
l_pair[0] = l_tag_first;
l_pair[1] = l_tag_prev;
}
if (check_degenerate) {
BM_face_splits_check_legal(bm, f, loops_split, STACK_SIZE(loops_split));
}
else {
BM_face_splits_check_optimal(f, loops_split, STACK_SIZE(loops_split));
}
for (i = 0; i < STACK_SIZE(loops_split); i++) {
if (loops_split[i][0] == nullptr) {
continue;
}
std::array<BMVert *, 2> &v_pair = STACK_PUSH_RET(verts_pair);
v_pair[0] = loops_split[i][0]->v;
v_pair[1] = loops_split[i][1]->v;
}
/* Clear and re-use to store duplicate faces, to remove after splitting is finished. */
STACK_CLEAR(loops_split);
for (i = 0; i < STACK_SIZE(verts_pair); i++) {
BMFace *f_new;
BMLoop *l_new;
BMLoop *l_pair[2];
/* Note that duplicate edges in this case is very unlikely but it can happen, see #70287. */
bool edge_exists = (BM_edge_exists(verts_pair[i][0], verts_pair[i][1]) != nullptr);
if ((l_pair[0] = BM_face_vert_share_loop(f, verts_pair[i][0])) &&
(l_pair[1] = BM_face_vert_share_loop(f, verts_pair[i][1])))
{
f_new = BM_face_split(bm, f, l_pair[0], l_pair[1], &l_new, nullptr, edge_exists);
/* Check if duplicate faces have been created, store the loops for removal in this case.
* Note that this matches how triangulate works (newly created duplicates get removed). */
if (UNLIKELY(edge_exists)) {
BMLoop **l_pair_deferred_remove = nullptr;
for (int j = 0; j < 2; j++) {
if (BM_face_find_double(l_pair[j]->f)) {
if (l_pair_deferred_remove == nullptr) {
l_pair_deferred_remove = STACK_PUSH_RET(loops_split);
l_pair_deferred_remove[0] = nullptr;
l_pair_deferred_remove[1] = nullptr;
}
l_pair_deferred_remove[j] = l_pair[j];
}
}
}
}
else {
f_new = nullptr;
l_new = nullptr;
}
if (!l_new || !f_new) {
result = -1;
break;
}
f = f_new;
// BMO_face_flag_enable(bm, f_new, FACE_NEW);
BMO_edge_flag_enable(bm, l_new->e, EDGE_OUT);
}
for (i = 0; i < STACK_SIZE(loops_split); i++) {
for (int j = 0; j < 2; j++) {
if (loops_split[i][j] != nullptr) {
BM_face_kill(bm, loops_split[i][j]->f);
}
}
}
return result;
}
void bmo_connect_verts_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMVert *v;
BMFace *f;
const bool check_degenerate = BMO_slot_bool_get(op->slots_in, "check_degenerate");
BLI_LINKSTACK_DECLARE(faces, BMFace *);
BLI_LINKSTACK_INIT(faces);
/* tag so we won't touch ever (typically hidden faces) */
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces_exclude", BM_FACE, FACE_EXCLUDE);
/* add all faces connected to verts */
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BMIter iter;
BMLoop *l_iter;
BMO_vert_flag_enable(bm, v, VERT_INPUT);
BM_ITER_ELEM (l_iter, &iter, v, BM_LOOPS_OF_VERT) {
f = l_iter->f;
if (!BMO_face_flag_test(bm, f, FACE_EXCLUDE)) {
if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
BMO_face_flag_enable(bm, f, FACE_TAG);
if (f->len > 3) {
BLI_LINKSTACK_PUSH(faces, f);
}
}
}
/* flag edges even if these are not newly created
* this way cut-pairs that include co-linear edges will get
* predictable output. */
if (BMO_vert_flag_test(bm, l_iter->prev->v, VERT_INPUT)) {
BMO_edge_flag_enable(bm, l_iter->prev->e, EDGE_OUT_ADJ);
}
if (BMO_vert_flag_test(bm, l_iter->next->v, VERT_INPUT)) {
BMO_edge_flag_enable(bm, l_iter->e, EDGE_OUT_ADJ);
}
}
}
/* connect faces */
while ((f = BLI_LINKSTACK_POP(faces))) {
if (bm_face_connect_verts(bm, f, check_degenerate) == -1) {
BMO_error_raise(bm, op, BMO_ERROR_FATAL, "Could not connect vertices");
}
}
BLI_LINKSTACK_FREE(faces);
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT | EDGE_OUT_ADJ);
}
} // namespace blender

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect vertices so all resulting faces are convex.
*
* Implementation:
*
* - triangulate all concave face (tagging convex verts),
* - rotate edges (beautify) so edges will connect nearby verts.
* - sort long edges (longest first),
* put any edges between 2 convex verts last since they often split convex regions.
* - merge the sorted edges as long as they don't create convex ngons.
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_heap.h"
#include "BLI_linklist.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_memarena.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT (1 << 0)
#define FACE_OUT (1 << 1)
static bool bm_face_split_by_concave(BMesh *bm,
BMFace *f_base,
const float eps,
MemArena *pf_arena,
Heap *pf_heap)
{
const int f_base_len = f_base->len;
int faces_array_tot = f_base_len - 3;
int edges_array_tot = f_base_len - 3;
Array<BMFace *, BM_DEFAULT_NGON_STACK_SIZE> faces_array(faces_array_tot);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_array(edges_array_tot);
const int quad_method = 0, ngon_method = 0; /* beauty */
LinkNode *faces_double = nullptr;
float normal[3];
BLI_assert(f_base->len > 3);
copy_v3_v3(normal, f_base->no);
BM_face_triangulate(bm,
f_base,
faces_array.data(),
&faces_array_tot,
edges_array.data(),
&edges_array_tot,
&faces_double,
quad_method,
ngon_method,
false,
pf_arena,
pf_heap);
BLI_assert(edges_array_tot <= f_base_len - 3);
if (faces_array_tot) {
int i;
for (i = 0; i < faces_array_tot; i++) {
BMFace *f = faces_array[i];
BMO_face_flag_enable(bm, f, FACE_OUT);
}
}
BMO_face_flag_enable(bm, f_base, FACE_OUT);
if (edges_array_tot) {
int i;
std::sort(
edges_array.begin(), edges_array.begin() + edges_array_tot, [](BMEdge *e_a, BMEdge *e_b) {
int e_a_concave = (BM_elem_flag_test(e_a->v1, BM_ELEM_TAG) &&
BM_elem_flag_test(e_a->v2, BM_ELEM_TAG));
int e_b_concave = (BM_elem_flag_test(e_b->v1, BM_ELEM_TAG) &&
BM_elem_flag_test(e_b->v2, BM_ELEM_TAG));
/* merge edges between concave edges last since these
* are most likely to remain and be the main dividers */
if (e_a_concave != e_b_concave) {
return e_a_concave < e_b_concave;
}
/* otherwise shortest edges last */
const float e_a_len = BM_edge_calc_length_squared(e_a);
const float e_b_len = BM_edge_calc_length_squared(e_b);
return e_a_len > e_b_len;
});
for (i = 0; i < edges_array_tot; i++) {
BMLoop *l_pair[2];
BMEdge *e = edges_array[i];
BMO_edge_flag_enable(bm, e, EDGE_OUT);
if (BM_edge_is_contiguous(e) && BM_edge_loop_pair(e, &l_pair[0], &l_pair[1])) {
bool ok = true;
int j;
for (j = 0; j < 2; j++) {
BMLoop *l = l_pair[j];
/* check that merging the edge (on this side)
* wouldn't result in a convex face-loop.
*
* This is the (l->next, l->prev) we would have once joined.
*/
float cross[3];
cross_tri_v3(cross, l->v->co, l->radial_next->next->next->v->co, l->prev->v->co);
if (dot_v3v3(cross, normal) <= eps) {
ok = false;
break;
}
}
if (ok) {
BMFace *f_double;
BMFace *f_new, *f_pair[2] = {l_pair[0]->f, l_pair[1]->f};
f_new = BM_faces_join(bm, f_pair, 2, true, &f_double);
/* If a double is found - queue the new face to be removed
* since it's not expected that this would remove other existing geometry. */
if (f_double) [[unlikely]] {
BLI_linklist_prepend(&faces_double, f_new);
f_new = nullptr;
}
if (f_new) {
BMO_face_flag_enable(bm, f_new, FACE_OUT);
}
}
}
}
}
BLI_heap_clear(pf_heap, nullptr);
while (faces_double) {
LinkNode *next = faces_double->next;
BM_face_kill(bm, static_cast<BMFace *>(faces_double->link));
MEM_delete(faces_double);
faces_double = next;
}
return true;
}
static bool bm_face_convex_tag_verts(BMFace *f)
{
bool is_concave = false;
if (f->len > 3) {
const BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (BM_loop_is_convex(l_iter) == false) {
is_concave = true;
BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG);
}
else {
BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
}
} while ((l_iter = l_iter->next) != l_first);
}
return is_concave;
}
void bmo_connect_verts_concave_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMFace *f;
bool changed = false;
MemArena *pf_arena;
Heap *pf_heap;
pf_arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
pf_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len > 3 && bm_face_convex_tag_verts(f)) {
if (bm_face_split_by_concave(bm, f, FLT_EPSILON, pf_arena, pf_heap)) {
changed = true;
}
}
}
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
BLI_memarena_free(pf_arena);
BLI_heap_free(pf_heap, nullptr);
}
} // namespace blender

View File

@@ -0,0 +1,174 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts non-planer faces iteratively (splits faces).
*/
#include "BLI_array.hh"
#include "BLI_linklist_stack.h"
#include "BLI_math_geom.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT (1 << 0)
#define FACE_OUT (1 << 1)
/**
* Calculates how non-planar the face subset is.
*/
static float bm_face_subset_calc_planar(BMLoop *l_first, BMLoop *l_last, const float no[3])
{
float axis_mat[3][3];
float z_prev;
float delta_z = 0.0f;
/* Newell's Method */
BMLoop *l_iter = l_first;
BMLoop *l_term = l_last->next;
axis_dominant_v3_to_m3(axis_mat, no);
z_prev = dot_m3_v3_row_z(axis_mat, l_last->v->co);
do {
const float z_curr = dot_m3_v3_row_z(axis_mat, l_iter->v->co);
delta_z += fabsf(z_curr - z_prev);
z_prev = z_curr;
} while ((l_iter = l_iter->next) != l_term);
return delta_z;
}
static bool bm_face_split_find(BMesh *bm, BMFace *f, BMLoop *l_pair[2], float *r_angle_cos)
{
BMLoop *l_iter, *l_first;
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> l_arr(f->len);
const uint f_len = f->len;
uint i_a, i_b;
bool found = false;
/* angle finding */
float err_best = FLT_MAX;
float angle_best_cos = -FLT_MAX;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
i_a = 0;
do {
l_arr[i_a++] = l_iter;
} while ((l_iter = l_iter->next) != l_first);
/* now for the big search, O(N^2), however faces normally aren't so large */
for (i_a = 0; i_a < f_len; i_a++) {
BMLoop *l_a = l_arr[i_a];
for (i_b = i_a + 2; i_b < f_len; i_b++) {
BMLoop *l_b = l_arr[i_b];
/* check these are not touching
* (we could be smarter here) */
if (!BM_loop_is_adjacent(l_a, l_b)) {
/* first calculate normals */
float no_a[3], no_b[3];
if (BM_face_calc_normal_subset(l_a, l_b, no_a) != 0.0f &&
BM_face_calc_normal_subset(l_b, l_a, no_b) != 0.0f)
{
const float err_a = bm_face_subset_calc_planar(l_a, l_b, no_a);
const float err_b = bm_face_subset_calc_planar(l_b, l_a, no_b);
const float err_test = err_a + err_b;
if (err_test < err_best) {
/* check we're legal (we could batch this) */
BMLoop *l_split[2] = {l_a, l_b};
BM_face_splits_check_legal(bm, f, &l_split, 1);
if (l_split[0]) {
err_best = err_test;
l_pair[0] = l_a;
l_pair[1] = l_b;
angle_best_cos = dot_v3v3(no_a, no_b);
found = true;
}
}
}
}
}
}
*r_angle_cos = angle_best_cos;
return found;
}
static bool bm_face_split_by_angle(BMesh *bm,
BMFace *f,
BMFace *r_f_pair[2],
const float angle_limit_cos)
{
BMLoop *l_pair[2];
float angle_cos;
if (bm_face_split_find(bm, f, l_pair, &angle_cos) && (angle_cos < angle_limit_cos)) {
BMFace *f_new;
BMLoop *l_new;
f_new = BM_face_split(bm, f, l_pair[0], l_pair[1], &l_new, nullptr, false);
if (f_new) {
r_f_pair[0] = f;
r_f_pair[1] = f_new;
BMO_face_flag_enable(bm, f, FACE_OUT);
BMO_face_flag_enable(bm, f_new, FACE_OUT);
BMO_edge_flag_enable(bm, l_new->e, EDGE_OUT);
return true;
}
}
return false;
}
void bmo_connect_verts_nonplanar_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMFace *f;
bool changed = false;
BLI_LINKSTACK_DECLARE(fstack, BMFace *);
const float angle_limit_cos = cosf(BMO_slot_float_get(op->slots_in, "angle_limit"));
BLI_LINKSTACK_INIT(fstack);
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len > 3) {
BLI_LINKSTACK_PUSH(fstack, f);
}
}
while ((f = BLI_LINKSTACK_POP(fstack))) {
BMFace *f_pair[2];
if (bm_face_split_by_angle(bm, f, f_pair, angle_limit_cos)) {
int j;
for (j = 0; j < 2; j++) {
BM_face_normal_update(f_pair[j]);
if (f_pair[j]->len > 3) {
BLI_LINKSTACK_PUSH(fstack, f_pair[j]);
}
}
changed = true;
}
}
BLI_LINKSTACK_FREE(fstack);
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
}
} // namespace blender

View File

@@ -0,0 +1,745 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect vertex pair across multiple faces (splits faces).
*/
#include "MEM_guardedalloc.h"
#include "BLI_heap_simple.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_mempool.h"
namespace blender {
/**
* Method for connecting across many faces.
*
* - use the line between both verts and their normal average to construct a matrix.
* - using the matrix, we can find all intersecting verts/edges.
* - walk the connected data and find the shortest path.
* - store a heap of paths which are being scanned (#PathContext.states).
* - continuously search the shortest path in the heap.
* - never step over the same element twice (tag elements as #ELE_TOUCHED).
* this avoids going into an eternal loop if there are many possible branches (see #45582).
* - when running into a branch, create a new #PathLinkState state and add to the heap.
* - when the target is reached,
* finish - since none of the other paths can be shorter than the one just found.
* - if the connection can't be found - fail.
* - with the connection found, split all edges tagging verts
* (or tag verts that sit on the intersection).
* - run the standard connect operator.
*/
#define CONNECT_EPS 0.0001f
#define VERT_OUT 1
#define VERT_EXCLUDE 2
/* typically hidden faces */
#define FACE_EXCLUDE 2
/* any element we've walked over (only do it once!) */
#define ELE_TOUCHED 4
#define FACE_WALK_TEST(f) \
(CHECK_TYPE_INLINE(f, BMFace *), BMO_face_flag_test(pc->bm_bmoflag, f, FACE_EXCLUDE) == 0)
#define VERT_WALK_TEST(v) \
(CHECK_TYPE_INLINE(v, BMVert *), BMO_vert_flag_test(pc->bm_bmoflag, v, VERT_EXCLUDE) == 0)
#if 0
# define ELE_TOUCH_TEST(e) \
(CHECK_TYPE_ANY(e, BMVert *, BMEdge *, BMElem *, BMElemF *), \
BMO_elem_flag_test(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED))
#endif
#define ELE_TOUCH_MARK(e) \
{ \
CHECK_TYPE_ANY(e, BMVert *, BMEdge *, BMElem *, BMElemF *); \
BMO_elem_flag_enable(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED); \
} \
((void)0)
#define ELE_TOUCH_TEST_VERT(v) BMO_vert_flag_test(pc->bm_bmoflag, v, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_VERT(v) BMO_vert_flag_enable(pc->bm_bmoflag, (BMElemF *)v, ELE_TOUCHED)
#define ELE_TOUCH_TEST_EDGE(e) BMO_edge_flag_test(pc->bm_bmoflag, e, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_EDGE(e) BMO_edge_flag_enable(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED)
// #define ELE_TOUCH_TEST_FACE(f) BMO_face_flag_test(pc->bm_bmoflag, f, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_FACE(f) BMO_face_flag_enable(pc->bm_bmoflag, (BMElemF *)f, ELE_TOUCHED)
// #define DEBUG_PRINT
struct PathContext {
HeapSimple *states;
float matrix[3][3];
float axis_sep;
/* only to access BMO flags */
BMesh *bm_bmoflag;
BMVert *v_pair[2];
BLI_mempool *link_pool;
};
/**
* Single linked list where each item contains state and points to previous path item.
*/
struct PathLink {
PathLink *next;
BMElem *ele; /* edge or vert */
BMElem *ele_from; /* edge or face we came from (not 'next->ele') */
};
struct PathLinkState {
/* chain of links */
PathLink *link_last;
/* length along links */
float dist;
float co_prev[3];
};
/* -------------------------------------------------------------------- */
/** \name Min Dist Dir Utilities
*
* Simply getting the closest intersecting vert/edge is _not_ good enough. see #43792
* we need to get the closest in both directions since the absolute closest may be a dead-end.
*
* Logic is simple:
*
* - First intersection, store the direction.
* - Successive intersections will update the first distance if its aligned with the first hit.
* otherwise update the opposite distance.
* - Caller stores best outcome in both directions.
* \{ */
struct MinDistDir {
/* distance in both directions (FLT_MAX == uninitialized) */
float dist_min[2];
/* direction of the first intersection found */
float dir[3];
};
#define MIN_DIST_DIR_INIT \
{ \
{ \
FLT_MAX, FLT_MAX \
} \
}
static int min_dist_dir_test(MinDistDir *mddir, const float dist_dir[3], const float dist_sq)
{
if (mddir->dist_min[0] == FLT_MAX) {
return 0;
}
if (dot_v3v3(dist_dir, mddir->dir) > 0.0f) {
if (dist_sq < mddir->dist_min[0]) {
return 0;
}
}
else {
if (dist_sq < mddir->dist_min[1]) {
return 1;
}
}
return -1;
}
static void min_dist_dir_update(MinDistDir *dist, const float dist_dir[3])
{
if (dist->dist_min[0] == FLT_MAX) {
copy_v3_v3(dist->dir, dist_dir);
}
}
/** \} */
static int state_isect_co_pair(const PathContext *pc, const float co_a[3], const float co_b[3])
{
const float diff_a = dot_m3_v3_row_x(pc->matrix, co_a) - pc->axis_sep;
const float diff_b = dot_m3_v3_row_x(pc->matrix, co_b) - pc->axis_sep;
const int test_a = (fabsf(diff_a) < CONNECT_EPS) ? 0 : (diff_a < 0.0f) ? -1 : 1;
const int test_b = (fabsf(diff_b) < CONNECT_EPS) ? 0 : (diff_b < 0.0f) ? -1 : 1;
if ((test_a && test_b) && (test_a != test_b)) {
return 1; /* on either side */
}
return 0;
}
static int state_isect_co_exact(const PathContext *pc, const float co[3])
{
const float diff = dot_m3_v3_row_x(pc->matrix, co) - pc->axis_sep;
return (fabsf(diff) <= CONNECT_EPS);
}
static float state_calc_co_pair_fac(const PathContext *pc,
const float co_a[3],
const float co_b[3])
{
float diff_a, diff_b, diff_tot;
diff_a = fabsf(dot_m3_v3_row_x(pc->matrix, co_a) - pc->axis_sep);
diff_b = fabsf(dot_m3_v3_row_x(pc->matrix, co_b) - pc->axis_sep);
diff_tot = (diff_a + diff_b);
return (diff_tot > FLT_EPSILON) ? (diff_a / diff_tot) : 0.5f;
}
static void state_calc_co_pair(const PathContext *pc,
const float co_a[3],
const float co_b[3],
float r_co[3])
{
const float fac = state_calc_co_pair_fac(pc, co_a, co_b);
interp_v3_v3v3(r_co, co_a, co_b, fac);
}
#ifndef NDEBUG
/**
* Ideally we wouldn't need this and for most cases we don't.
* But when a face has vertices that are on the boundary more than once this becomes tricky.
*/
static bool state_link_find(const PathLinkState *state, BMElem *ele)
{
PathLink *link = state->link_last;
BLI_assert(ELEM(ele->head.htype, BM_VERT, BM_EDGE, BM_FACE));
if (link) {
do {
if (link->ele == ele) {
return true;
}
} while ((link = link->next));
}
return false;
}
#endif
static void state_link_add(PathContext *pc, PathLinkState *state, BMElem *ele, BMElem *ele_from)
{
PathLink *step_new = static_cast<PathLink *>(BLI_mempool_alloc(pc->link_pool));
BLI_assert(ele != ele_from);
BLI_assert(state_link_find(state, ele) == false);
/* never walk onto this again */
ELE_TOUCH_MARK(ele);
#ifdef DEBUG_PRINT
printf("%s: adding to state %p, %.4f - ", __func__, state, state->dist);
if (ele->head.htype == BM_VERT) {
printf("vert %d, ", BM_elem_index_get(ele));
}
else if (ele->head.htype == BM_EDGE) {
printf("edge %d, ", BM_elem_index_get(ele));
}
else {
BLI_assert(0);
}
if (ele_from == nullptr) {
printf("from nullptr\n");
}
else if (ele_from->head.htype == BM_EDGE) {
printf("from edge %d\n", BM_elem_index_get(ele_from));
}
else if (ele_from->head.htype == BM_FACE) {
printf("from face %d\n", BM_elem_index_get(ele_from));
}
else {
BLI_assert(0);
}
#endif
/* track distance */
{
float co[3];
if (ele->head.htype == BM_VERT) {
copy_v3_v3(co, (reinterpret_cast<BMVert *>(ele))->co);
}
else if (ele->head.htype == BM_EDGE) {
state_calc_co_pair(pc,
(reinterpret_cast<BMEdge *>(ele))->v1->co,
(reinterpret_cast<BMEdge *>(ele))->v2->co,
co);
}
else {
BLI_assert(0);
}
/* tally distance */
if (ele_from) {
state->dist += len_v3v3(state->co_prev, co);
}
copy_v3_v3(state->co_prev, co);
}
step_new->ele = ele;
step_new->ele_from = ele_from;
step_new->next = state->link_last;
state->link_last = step_new;
}
static PathLinkState *state_dupe_add(PathLinkState *state, const PathLinkState *state_orig)
{
state = MEM_new_uninitialized<PathLinkState>(__func__);
*state = *state_orig;
return state;
}
static PathLinkState *state_link_add_test(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMElem *ele,
BMElem *ele_from)
{
const bool is_new = (state_orig->link_last != state->link_last);
if (is_new) {
state = state_dupe_add(state, state_orig);
}
state_link_add(pc, state, ele, ele_from);
/* after adding a link so we use the updated 'state->dist' */
if (is_new) {
BLI_heapsimple_insert(pc->states, state->dist, state);
}
return state;
}
/* walk around the face edges */
static PathLinkState *state_step__face_edges(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMLoop *l_iter,
BMLoop *l_last,
MinDistDir *mddir)
{
BMLoop *l_iter_best[2] = {nullptr, nullptr};
int i;
do {
if (state_isect_co_pair(pc, l_iter->v->co, l_iter->next->v->co)) {
float dist_test;
float co_isect[3];
float dist_dir[3];
int index;
state_calc_co_pair(pc, l_iter->v->co, l_iter->next->v->co, co_isect);
sub_v3_v3v3(dist_dir, co_isect, state_orig->co_prev);
dist_test = len_squared_v3(dist_dir);
if ((index = min_dist_dir_test(mddir, dist_dir, dist_test)) != -1) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->e);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
if (FACE_WALK_TEST((BMFace *)ele_next_from) &&
(ELE_TOUCH_TEST_EDGE((BMEdge *)ele_next) == false))
{
min_dist_dir_update(mddir, dist_dir);
mddir->dist_min[index] = dist_test;
l_iter_best[index] = l_iter;
}
}
}
} while ((l_iter = l_iter->next) != l_last);
for (i = 0; i < 2; i++) {
if ((l_iter = l_iter_best[i])) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->e);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
state = state_link_add_test(pc, state, state_orig, ele_next, ele_next_from);
}
}
return state;
}
/* walk around the face verts */
static PathLinkState *state_step__face_verts(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMLoop *l_iter,
BMLoop *l_last,
MinDistDir *mddir)
{
BMLoop *l_iter_best[2] = {nullptr, nullptr};
int i;
do {
if (state_isect_co_exact(pc, l_iter->v->co)) {
float dist_test;
const float *co_isect = l_iter->v->co;
float dist_dir[3];
int index;
sub_v3_v3v3(dist_dir, co_isect, state_orig->co_prev);
dist_test = len_squared_v3(dist_dir);
if ((index = min_dist_dir_test(mddir, dist_dir, dist_test)) != -1) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->v);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
if (FACE_WALK_TEST((BMFace *)ele_next_from) &&
(ELE_TOUCH_TEST_VERT((BMVert *)ele_next) == false))
{
min_dist_dir_update(mddir, dist_dir);
mddir->dist_min[index] = dist_test;
l_iter_best[index] = l_iter;
}
}
}
} while ((l_iter = l_iter->next) != l_last);
for (i = 0; i < 2; i++) {
if ((l_iter = l_iter_best[i])) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->v);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
state = state_link_add_test(pc, state, state_orig, ele_next, ele_next_from);
}
}
return state;
}
static bool state_step(PathContext *pc, PathLinkState *state)
{
PathLinkState state_orig = *state;
BMElem *ele = state->link_last->ele;
const void *ele_from = state->link_last->ele_from;
if (ele->head.htype == BM_EDGE) {
BMEdge *e = reinterpret_cast<BMEdge *>(ele);
BMIter liter;
BMLoop *l_start;
BM_ITER_ELEM (l_start, &liter, e, BM_LOOPS_OF_EDGE) {
if ((l_start->f != ele_from) && FACE_WALK_TEST(l_start->f)) {
MinDistDir mddir = MIN_DIST_DIR_INIT;
/* Very similar to block below. */
state = state_step__face_edges(pc, state, &state_orig, l_start->next, l_start, &mddir);
state = state_step__face_verts(
pc, state, &state_orig, l_start->next->next, l_start, &mddir);
}
}
}
else if (ele->head.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(ele);
/* Vert loops. */
{
BMIter liter;
BMLoop *l_start;
BM_ITER_ELEM (l_start, &liter, v, BM_LOOPS_OF_VERT) {
if ((l_start->f != ele_from) && FACE_WALK_TEST(l_start->f)) {
MinDistDir mddir = MIN_DIST_DIR_INIT;
/* Very similar to block above. */
state = state_step__face_edges(
pc, state, &state_orig, l_start->next, l_start->prev, &mddir);
if (l_start->f->len > 3) {
/* Adjacent verts are handled in #state_step__vert_edges. */
state = state_step__face_verts(
pc, state, &state_orig, l_start->next->next, l_start->prev, &mddir);
}
}
}
}
/* Vert edges. */
{
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
BMVert *v_other = BM_edge_other_vert(e, v);
if ((reinterpret_cast<BMElem *>(e) != ele_from) && VERT_WALK_TEST(v_other)) {
if (state_isect_co_exact(pc, v_other->co)) {
BMElem *ele_next = reinterpret_cast<BMElem *>(v_other);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(e);
if (ELE_TOUCH_TEST_VERT((BMVert *)ele_next) == false) {
state = state_link_add_test(pc, state, &state_orig, ele_next, ele_next_from);
}
}
}
}
}
}
else {
BLI_assert(0);
}
return (state_orig.link_last != state->link_last);
}
/**
* Get a orientation matrix from 2 vertices.
*/
static void bm_vert_pair_to_matrix(BMVert *v_pair[2], float r_unit_mat[3][3])
{
const float eps = 1e-8f;
float basis_dir[3];
float basis_tmp[3];
float basis_nor[3];
sub_v3_v3v3(basis_dir, v_pair[0]->co, v_pair[1]->co);
normalize_v3(basis_dir);
#if 0
add_v3_v3v3(basis_nor, v_pair[0]->no, v_pair[1]->no);
cross_v3_v3v3(basis_tmp, basis_nor, basis_dir);
cross_v3_v3v3(basis_nor, basis_tmp, basis_dir);
#else
/* align both normals to the directions before combining */
{
float basis_nor_a[3];
float basis_nor_b[3];
/* align normal to direction */
project_plane_normalized_v3_v3v3(basis_nor_a, v_pair[0]->no, basis_dir);
project_plane_normalized_v3_v3v3(basis_nor_b, v_pair[1]->no, basis_dir);
/* Don't normalize before combining so as normals approach the direction,
* they have less effect (#46784). */
/* combine the normals */
/* for flipped faces */
if (dot_v3v3(basis_nor_a, basis_nor_b) < 0.0f) {
negate_v3(basis_nor_b);
}
add_v3_v3v3(basis_nor, basis_nor_a, basis_nor_b);
}
#endif
/* get third axis */
normalize_v3(basis_nor);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
/* Try get the axis from surrounding faces, fallback to 'ortho_v3_v3' */
if (UNLIKELY(normalize_v3(basis_tmp) < eps)) {
/* vertex normals are directly opposite */
/* find the loop with the lowest angle */
struct {
float nor[3];
float angle_cos;
} axis_pair[2];
int i;
for (i = 0; i < 2; i++) {
BMIter liter;
BMLoop *l;
zero_v2(axis_pair[i].nor);
axis_pair[i].angle_cos = -FLT_MAX;
BM_ITER_ELEM (l, &liter, v_pair[i], BM_LOOPS_OF_VERT) {
float basis_dir_proj[3];
float angle_cos_test;
/* project basis dir onto the normal to find its closest angle */
project_plane_normalized_v3_v3v3(basis_dir_proj, basis_dir, l->f->no);
if (normalize_v3(basis_dir_proj) > eps) {
angle_cos_test = dot_v3v3(basis_dir_proj, basis_dir);
if (angle_cos_test > axis_pair[i].angle_cos) {
axis_pair[i].angle_cos = angle_cos_test;
copy_v3_v3(axis_pair[i].nor, basis_dir_proj);
}
}
}
}
/* create a new 'basis_nor' from the best direction.
* NOTE: we could add the directions,
* but this more often gives 45d rotated matrix, so just use the best one. */
copy_v3_v3(basis_nor, axis_pair[axis_pair[0].angle_cos < axis_pair[1].angle_cos].nor);
project_plane_normalized_v3_v3v3(basis_nor, basis_nor, basis_dir);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
/* last resort, pick _any_ ortho axis */
if (UNLIKELY(normalize_v3(basis_tmp) < eps)) {
ortho_v3_v3(basis_nor, basis_dir);
normalize_v3(basis_nor);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
normalize_v3(basis_tmp);
}
}
copy_v3_v3(r_unit_mat[0], basis_tmp);
copy_v3_v3(r_unit_mat[1], basis_dir);
copy_v3_v3(r_unit_mat[2], basis_nor);
if (invert_m3(r_unit_mat) == false) {
unit_m3(r_unit_mat);
}
}
void bmo_connect_vert_pair_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *op_verts_slot = BMO_slot_get(op->slots_in, "verts");
PathContext pc;
PathLinkState state_best = {nullptr};
if (op_verts_slot->len != 2) {
/* fail! */
return;
}
pc.bm_bmoflag = bm;
pc.v_pair[0] = (static_cast<BMVert **>(op_verts_slot->data.p))[0];
pc.v_pair[1] = (static_cast<BMVert **>(op_verts_slot->data.p))[1];
/* fail! */
if (!(pc.v_pair[0] && pc.v_pair[1])) {
return;
}
#ifdef DEBUG_PRINT
printf("%s: v_pair[0]: %d\n", __func__, BM_elem_index_get(pc.v_pair[0]));
printf("%s: v_pair[1]: %d\n", __func__, BM_elem_index_get(pc.v_pair[1]));
#endif
/* tag so we won't touch ever (typically hidden faces) */
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces_exclude", BM_FACE, FACE_EXCLUDE);
BMO_slot_buffer_flag_enable(bm, op->slots_in, "verts_exclude", BM_VERT, VERT_EXCLUDE);
/* setup context */
{
pc.states = BLI_heapsimple_new();
pc.link_pool = BLI_mempool_create(sizeof(PathLink), 0, 512, BLI_MEMPOOL_NOP);
}
/* calculate matrix */
{
bm_vert_pair_to_matrix(pc.v_pair, pc.matrix);
pc.axis_sep = dot_m3_v3_row_x(pc.matrix, pc.v_pair[0]->co);
}
/* add first vertex */
{
PathLinkState *state;
state = MEM_new_zeroed<PathLinkState>(__func__);
state_link_add(&pc, state, reinterpret_cast<BMElem *>(pc.v_pair[0]), nullptr);
BLI_heapsimple_insert(pc.states, state->dist, state);
}
while (!BLI_heapsimple_is_empty(pc.states)) {
#ifdef DEBUG_PRINT
printf("\n%s: stepping %u\n", __func__, BLI_heapsimple_len(pc.states));
#endif
while (!BLI_heapsimple_is_empty(pc.states)) {
PathLinkState *state = static_cast<PathLinkState *>(BLI_heapsimple_pop_min(pc.states));
/* either we insert this into 'pc.states' or its freed */
bool continue_search;
if (state->link_last->ele == reinterpret_cast<BMElem *>(pc.v_pair[1])) {
/* pass, wait until all are found */
#ifdef DEBUG_PRINT
printf("%s: state %p loop found %.4f\n", __func__, state, state->dist);
#endif
state_best = *state;
/* we're done, exit all loops */
BLI_heapsimple_clear(pc.states, MEM_delete_void);
continue_search = false;
}
else if (state_step(&pc, state)) {
continue_search = true;
}
else {
/* didn't reach the end, remove it,
* links are shared between states so just free the link_pool at the end */
#ifdef DEBUG_PRINT
printf("%s: state %p removed\n", __func__, state);
#endif
continue_search = false;
}
if (continue_search) {
BLI_heapsimple_insert(pc.states, state->dist, state);
}
else {
MEM_delete(state);
}
}
}
if (state_best.link_last) {
PathLink *link;
/* find the best state */
link = state_best.link_last;
do {
if (link->ele->head.htype == BM_EDGE) {
BMEdge *e = reinterpret_cast<BMEdge *>(link->ele);
BMVert *v_new;
float e_fac = state_calc_co_pair_fac(&pc, e->v1->co, e->v2->co);
v_new = BM_edge_split(bm, e, e->v1, nullptr, e_fac);
/* Adding vertices makes the face-normals stale.
* These are used for the `connect_verts` call next for projecting onto the face,
* so the normals must be recalculated here. */
if (e->l) {
BMLoop *l_iter = e->l;
do {
BM_face_normal_update(l_iter->f);
} while ((l_iter = l_iter->radial_next) != e->l);
}
BMO_vert_flag_enable(bm, v_new, VERT_OUT);
}
else if (link->ele->head.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(link->ele);
BMO_vert_flag_enable(bm, v, VERT_OUT);
}
else {
BLI_assert(0);
}
} while ((link = link->next));
}
BMO_vert_flag_enable(bm, pc.v_pair[0], VERT_OUT);
BMO_vert_flag_enable(bm, pc.v_pair[1], VERT_OUT);
BLI_mempool_destroy(pc.link_pool);
BLI_heapsimple_free(pc.states, MEM_delete_void);
#if 1
if (state_best.link_last) {
BMOperator op_sub;
BMO_op_initf(bm,
&op_sub,
0,
"connect_verts verts=%fv faces_exclude=%s check_degenerate=%b",
VERT_OUT,
op,
"faces_exclude",
true);
BMO_op_exec(bm, &op_sub);
BMO_slot_copy(&op_sub, slots_out, "edges.out", op, slots_out, "edges.out");
BMO_op_finish(bm, &op_sub);
}
#endif
}
} // namespace blender

View File

@@ -0,0 +1,299 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Create faces or edges (F-key by default).
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
#define ELE_OUT 2
void bmo_contextual_create_exec(BMesh *bm, BMOperator *op)
{
/* NOTE(@ideasman42): doing the best thing here isn't always easy create vs dissolve,
* its nice to support but it _really_ gives issues we might have to not call dissolve. */
BMOIter oiter;
BMHeader *h;
int totv = 0, tote = 0, totf = 0;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
/* count number of each element type we were passe */
BMO_ITER (h, &oiter, op->slots_in, "geom", BM_VERT | BM_EDGE | BM_FACE) {
switch (h->htype) {
case BM_VERT:
BMO_vert_flag_enable(bm, (BMVert *)h, ELE_NEW);
totv++;
break;
case BM_EDGE:
BMO_edge_flag_enable(bm, (BMEdge *)h, ELE_NEW);
tote++;
break;
case BM_FACE:
BMO_face_flag_enable(bm, (BMFace *)h, ELE_NEW);
totf++;
break;
}
}
/* --- Support Edge Creation ---
* simple case when we only have 2 verts selected.
*/
if (totv == 2 && tote == 0 && totf == 0) {
BMVert *verts[2];
BMEdge *e;
if (BMO_iter_as_array(op->slots_in, "geom", BM_VERT, reinterpret_cast<void **>(verts), 2) == 2)
{
/* create edge */
e = BM_edge_create(bm, verts[0], verts[1], nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_OUT);
tote += 1;
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_OUT);
}
return;
}
/* --- Support for Special Case ---
* where there is a contiguous edge ring with one isolated vertex.
*
* This example shows 2 edges created from 3 verts
* with 1 free standing vertex. Dotted lines denote the 2 edges that are created.
*
* note that this works for any sided shape.
*
* +--------+
* | .
* | .
* | .
* | .
* +........+ <-- starts out free standing.
*/
/* Here we check for consistency and create 2 edges */
if (totf == 0 && totv >= 4 && totv == tote + 2) {
/* find a free standing vertex and 2 endpoint verts */
BMVert *v, *v_free = nullptr, *v_a = nullptr, *v_b = nullptr;
bool ok = true;
BMO_ITER (v, &oiter, op->slots_in, "geom", BM_VERT) {
/* count how many flagged edges this vertex uses */
const int tot_edges = BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, v, ELE_NEW, true);
if (tot_edges == 0) {
/* only accept 1 free vert */
if (v_free == nullptr) {
v_free = v;
}
else {
ok = false;
} /* only ever want one of these */
}
else if (tot_edges == 1) {
if (v_a == nullptr) {
v_a = v;
}
else if (v_b == nullptr) {
v_b = v;
}
else {
ok = false;
} /* only ever want 2 of these */
}
else if (tot_edges == 2) {
/* do nothing, regular case */
}
else {
ok = false; /* if a vertex has 3+ edge users then cancel - this is only simple cases */
}
if (ok == false) {
break;
}
}
if (ok == true && v_free && v_a && v_b) {
BMEdge *e;
e = BM_edge_create(bm, v_free, v_a, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
e = BM_edge_create(bm, v_free, v_b, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
tote += 2;
}
}
/* --- end special case support, continue as normal --- */
/* -------------------------------------------------------------------- */
/* EdgeNet Create */
if (tote != 0) {
/* call edgenet prepare op so additional face creation cases work */
BMOperator op_sub;
BMO_op_initf(bm, &op_sub, op->flag, "edgenet_prepare edges=%fe", ELE_NEW);
BMO_op_exec(bm, &op_sub);
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "edges.out", BM_EDGE, ELE_NEW);
BMO_op_finish(bm, &op_sub);
BMO_op_initf(bm,
&op_sub,
op->flag,
"edgenet_fill edges=%fe mat_nr=%i use_smooth=%b sides=%i",
ELE_NEW,
mat_nr,
use_smooth,
10000);
BMO_op_exec(bm, &op_sub);
/* return if edge net create did something */
if (BMO_slot_buffer_len(op_sub.slots_out, "faces.out")) {
BMO_slot_copy(&op_sub, slots_out, "faces.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Dissolve Face */
if (totf != 0) { /* should be (totf > 1)... see below */
/* NOTE: allow this to run on single faces so running on a single face
* won't go on to create a face, treating them as random */
BMOperator op_sub;
BMO_op_initf(bm, &op_sub, op->flag, "dissolve_faces faces=%ff", ELE_NEW);
BMO_op_exec(bm, &op_sub);
/* if we dissolved anything, then return */
if (BMO_slot_buffer_len(op_sub.slots_out, "region.out")) {
BMO_slot_copy(&op_sub, slots_out, "region.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Fill EdgeLoop's - fills isolated loops, different from edgenet */
if (tote > 2) {
BMOperator op_sub;
/* NOTE: in most cases 'edgenet_fill' will handle this case since in common cases
* users fill in empty spaces, however its possible to have an edge selection around
* existing geometry that makes 'edgenet_fill' fail. */
BMO_op_initf(bm, &op_sub, op->flag, "edgeloop_fill edges=%fe", ELE_NEW);
BMO_op_exec(bm, &op_sub);
/* return if edge loop fill did something */
if (BMO_slot_buffer_len(op_sub.slots_out, "faces.out")) {
BMO_slot_copy(&op_sub, slots_out, "faces.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Continue with ad-hoc fill methods since operators fail,
* edge, vcloud... may add more */
if (false) { /* nice feature but perhaps it should be a different tool? */
/* tricky feature for making a line/edge from selection history...
*
* Rather than do nothing, when 5+ verts are selected, check if they are in our history,
* when this is so, we can make edges from them, but _not_ a face,
* if it is the intention to make a face the user can just hit F again
* since there will be edges next time around.
*
* if all history verts have ELE_NEW flagged and the total number of history verts == totv,
* then we know the history contains all verts here and we can continue...
*/
int tot_ese_v = 0;
for (BMEditSelection &ese : bm->selected) {
if (ese.htype == BM_VERT) {
if (BMO_vert_flag_test(bm, (BMVert *)ese.ele, ELE_NEW)) {
tot_ese_v++;
}
else {
/* unflagged vert means we are not in sync */
tot_ese_v = -1;
break;
}
}
}
if (tot_ese_v == totv) {
BMVert *v_prev = nullptr;
/* yes, all select-history verts are accounted for, now make edges */
for (BMEditSelection &ese : bm->selected) {
if (ese.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(ese.ele);
if (v_prev) {
BMEdge *e = BM_edge_create(bm, v, v_prev, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_OUT);
}
v_prev = v;
}
}
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_OUT);
/* done creating edges */
return;
}
/* -------------------------------------------------------------------- */
/* Fill Vertex Cloud
*
* last resort when all else fails.
*/
if (totv > 2) {
/* TODO: some of these vertices may be connected by edges,
* this connectivity could be used rather than treating
* them as a bunch of isolated verts. */
BMVert **vert_arr = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMFace *f;
totv = BMO_iter_as_array(
op->slots_in, "geom", BM_VERT, reinterpret_cast<void **>(vert_arr), totv);
BM_verts_sort_radial_plane(vert_arr, totv);
/* create edges and find the winding (if faces are attached to any existing edges) */
f = BM_face_create_ngon_verts(bm, vert_arr, totv, nullptr, BM_CREATE_NO_DOUBLE, true, true);
if (f) {
BMO_face_flag_enable(bm, f, ELE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
BM_face_copy_shared(bm, f, nullptr, nullptr);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_OUT);
}
MEM_delete(vert_arr);
}
}
} // namespace blender

View File

@@ -0,0 +1,969 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Removes isolated geometry regions without creating holes in the mesh.
*/
#include <cmath>
#include "MEM_guardedalloc.h"
#include "BLI_math_vector.h"
#include "BLI_stack.h"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
/* ***_ISGC: mark for garbage-collection */
#define FACE_MARK 1
#define FACE_ORIG 2
#define FACE_NEW 4
#define FACE_TAG 8
#define EDGE_MARK 1
#define EDGE_TAG 2
#define EDGE_ISGC 8
/**
* Set when the edge is part of a chain,
* where at least of it's vertices has exactly one other connected edge.
*/
#define EDGE_CHAIN 16
#define VERT_MARK 1
#define VERT_MARK_PAIR 4
#define VERT_TAG 2
#define VERT_ISGC 8
#define VERT_MARK_TEAR 16
/* -------------------------------------------------------------------- */
/** \name Internal Utility API
* \{ */
static bool UNUSED_FUNCTION(check_hole_in_region)(BMesh *bm, BMFace *f)
{
BMWalker regwalker;
BMIter liter2;
BMLoop *l2, *l3;
BMFace *f2;
/* Checks if there are any unmarked boundary edges in the face region. */
BMW_init(&regwalker,
bm,
BMW_ISLAND,
BMW_MASK_NOP,
BMW_MASK_NOP,
FACE_MARK,
BMW_FLAG_NOP,
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
for (f2 = static_cast<BMFace *>(BMW_begin(&regwalker, f)); f2;
f2 = static_cast<BMFace *>(BMW_step(&regwalker)))
{
BM_ITER_ELEM (l2, &liter2, f2, BM_LOOPS_OF_FACE) {
l3 = l2->radial_next;
if (BMO_face_flag_test(bm, l3->f, FACE_MARK) != BMO_face_flag_test(bm, l2->f, FACE_MARK)) {
if (!BMO_edge_flag_test(bm, l2->e, EDGE_MARK)) {
return false;
}
}
}
}
BMW_end(&regwalker);
return true;
}
/**
* Calculates the angle of an edge pair, from a combination of raw angle and normal angle.
*/
static float bmo_vert_calc_edge_angle_blended(const BMVert *v)
{
BMEdge *e_pair[2];
const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
BLI_assert(is_edge_pair);
UNUSED_VARS_NDEBUG(is_edge_pair);
/* Compute the angle between the edges. Start with the raw angle. */
BMVert *v_a = BM_edge_other_vert(e_pair[0], v);
BMVert *v_b = BM_edge_other_vert(e_pair[1], v);
float angle = M_PI - angle_v3v3v3(v_a->co, v->co, v_b->co);
/* There are two ways to measure the angle around a vert with two edges. The first is to
* measure the raw angle between the two neighboring edges, the second is to measure the
* angle of the edges around the vertex normal vector. When the vert is an edge pair
* between two faces, The normal measurement is better in general. In the specific case of
* a vert between two faces, but the faces have a *very* sharp angle between them, then the
* raw angle is better, because the normal is perpendicular to average of the two faces,
* and if the faces are folded almost 180 degrees, the vertex normal becomes more an more
* edge-on to the faces, meaning the angle *around the normal* becomes more and more flat,
* even if it makes a sharp angle when viewed from the side.
*
* When the faces become very folded, the `raw_factor` adds some of the "as seen from the side"
* angle back into the computation, making the algorithm behave more intuitively.
*
* The `raw_factor` is computed as follows:
* - When not a face pair, part this is skipped, and the raw angle is used.
* - When a face pair is co-planar, or has an angle up to 90 degrees, `raw_factor` is 0.0.
* - As angle increases from 90 to 180 degrees, `raw_factor` increases from 0.0 to 1.0.
*/
BMFace *f_pair[2];
if (BM_edge_face_pair(v->e, &f_pair[0], &f_pair[1])) {
/* Due to merges, the normals are not currently trustworthy. Compute them. */
float no_a[3], no_b[3];
BM_face_calc_normal(f_pair[0], no_a);
BM_face_calc_normal(f_pair[1], no_b);
/* Now determine the raw factor based on how folded the faces are. */
const float raw_factor = std::clamp(-dot_v3v3(no_a, no_b), 0.0f, 1.0f);
/* Blend the two ways of computing the angle. */
float normal_angle = M_PI - angle_on_axis_v3v3v3_v3(v_a->co, v->co, v_b->co, v->no);
angle = interpf(angle, normal_angle, raw_factor);
}
return angle;
}
/**
* A wrapper for #BM_vert_collapse_edge which ensures correct hidden state & merges edge flags.
*/
static BMEdge *bm_vert_collapse_edge_and_merge(BMesh *bm, BMVert *v, const bool do_del)
{
/* Merge the header flags on the two edges that will be merged. */
BMEdge *e_pair[2];
const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
BLI_assert(is_edge_pair);
UNUSED_VARS_NDEBUG(is_edge_pair);
BM_elem_flag_merge_ex(e_pair[0], e_pair[1], BM_ELEM_HIDDEN);
/* Dissolve the vertex. */
BMEdge *e_new = BM_vert_collapse_edge(bm, v->e, v, do_del, true, true);
if (e_new) {
/* Ensure the result of dissolving never leaves visible edges connected to hidden vertices.
* From a user perspective this is an invalid state which tools should not allow. */
if (!BM_elem_flag_test(e_new, BM_ELEM_HIDDEN)) {
if (BM_elem_flag_test(e_new->v1, BM_ELEM_HIDDEN) ||
BM_elem_flag_test(e_new->v2, BM_ELEM_HIDDEN))
{
if (BM_elem_flag_test(e_new, BM_ELEM_SELECT)) {
BM_edge_select_set_noflush(bm, e_new, false);
}
BM_elem_flag_enable(e_new, BM_ELEM_HIDDEN);
}
}
}
return e_new;
}
static void bm_face_split(BMesh *bm, const short oflag, bool use_edge_delete)
{
BLI_Stack *edge_delete_verts;
BMIter iter;
BMVert *v;
if (use_edge_delete) {
edge_delete_verts = BLI_stack_new(sizeof(BMVert *), __func__);
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag)) {
if (BM_vert_is_edge_pair(v) == false) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (l->f->len > 3) {
if (BMO_vert_flag_test(bm, l->next->v, oflag) == 0 &&
BMO_vert_flag_test(bm, l->prev->v, oflag) == 0)
{
BM_face_split(bm, l->f, l->next, l->prev, nullptr, nullptr, true);
}
}
}
if (use_edge_delete) {
BLI_stack_push(edge_delete_verts, &v);
}
}
}
}
if (use_edge_delete) {
while (!BLI_stack_is_empty(edge_delete_verts)) {
/* remove surrounding edges & faces */
BLI_stack_pop(edge_delete_verts, &v);
while (v->e) {
BM_edge_kill(bm, v->e);
}
}
BLI_stack_free(edge_delete_verts);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Execute Functions
* \{ */
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
{
BMOIter oiter;
BMFace *f;
BMWalker regwalker;
const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts");
if (use_verts) {
/* tag verts that start out with only 2 edges,
* don't remove these later */
BMIter viter;
BMVert *v;
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BMO_vert_flag_set(bm, v, VERT_MARK, !BM_vert_is_edge_pair(v));
}
}
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_MARK | FACE_TAG);
/* List of regions which are themselves a list of faces. */
Vector<Vector<BMFace *>> regions;
/* collect region */
BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
continue;
}
BMW_init(&regwalker,
bm,
BMW_ISLAND_MANIFOLD,
BMW_MASK_NOP,
BMW_MASK_NOP,
FACE_MARK,
/* no need to check BMW_FLAG_TEST_HIDDEN, faces are already marked by the bmo. */
BMW_FLAG_NOP,
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
/* Check there are at least two faces before creating the array. */
BMFace *faces_init[2];
if ((faces_init[0] = static_cast<BMFace *>(BMW_begin(&regwalker, f))) &&
(faces_init[1] = static_cast<BMFace *>(BMW_step(&regwalker))))
{
Vector<BMFace *> faces;
faces.append(faces_init[0]);
faces.append(faces_init[1]);
BMFace *f_iter;
while ((f_iter = static_cast<BMFace *>(BMW_step(&regwalker)))) {
faces.append(f_iter);
}
for (BMFace *face : faces) {
BMO_face_flag_disable(bm, face, FACE_TAG);
BMO_face_flag_enable(bm, face, FACE_ORIG);
}
regions.append_as(std::move(faces));
}
BMW_end(&regwalker);
}
/* track how many faces we should end up with */
int totface_target = bm->totface;
for (Vector<BMFace *> &faces : regions) {
const int64_t faces_len = faces.size();
BMFace *f_double;
BMFace *f_new = BM_faces_join(bm, faces.data(), faces_len, true, &f_double);
if (LIKELY(f_new)) {
/* All the joined faces are gone and the fresh f_new represents their union. */
totface_target -= faces_len - 1;
if (UNLIKELY(f_double)) {
/* `BM_faces_join()` succeeded, but there is a double. Keep the pre-existing face
* and retain its custom-data. Remove the newly made merge result. */
BM_face_kill(bm, f_new);
totface_target -= 1;
f_new = f_double;
}
/* Un-mark the joined face to ensure it is not garbage collected later. */
BMO_face_flag_disable(bm, f_new, FACE_ORIG);
/* Mark the joined face so it can be added to the selection later. */
BMO_face_flag_enable(bm, f_new, FACE_NEW);
}
else {
/* `BM_faces_join()` failed. */
/* NOTE: prior to 3.0 this raised an error: "Could not create merged face".
* Change behavior since it's not useful to fail entirely when a single face-group
* can't be merged into one face. Continue with other face groups instead.
*
* This could optionally do a partial merge, where some faces are joined. */
/* Prevent these faces from being removed. */
for (BMFace *face : faces) {
BMO_face_flag_disable(bm, face, FACE_ORIG);
}
}
}
/* Typically no faces need to be deleted */
if (totface_target != bm->totface) {
BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", FACE_ORIG, DEL_FACES);
}
if (use_verts) {
BMIter viter;
BMVert *v, *v_next;
BM_ITER_MESH_MUTABLE (v, v_next, &viter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
if (BM_vert_is_edge_pair(v)) {
bm_vert_collapse_edge_and_merge(bm, v, true);
}
}
}
BLI_assert(!BMO_error_occurred_at_level(bm, BMO_ERROR_FATAL));
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW);
}
/**
* Given an edge, and vert that are part of a chain, finds the vert at the far end of the chain.
*
* If `edge_oflag` is provided, each edge along the chain is tagged,
* and walking stops when an edge that is already tagged is found.
* This avoids repeatedly re-walking the chain.
*
* Returns `nullptr` if already tagged edges are found, or if the chain loops.
*/
static BMVert *bmo_find_end_of_chain(BMesh *bm, BMEdge *e, BMVert *v, const short edge_oflag = 0)
{
BMVert *v_init = v;
while (BM_vert_is_edge_pair(v)) {
/* Move one step down the chain. */
e = BM_DISK_EDGE_NEXT(e, v);
v = BM_edge_other_vert(e, v);
/* If we walk to an edge that has already been processed, there's no need to keep working.
* If `edge_oflag` is 0, this test never returns true,
* so iteration will truly go to the end. */
if (BMO_edge_flag_test(bm, e, edge_oflag)) {
return nullptr;
}
/* Optionally mark along the chain.
* If `edge_oflag` is 0, `hflag |= 0` is still faster than if + test + jump. */
BMO_edge_flag_enable(bm, e, edge_oflag);
/* While this should never happen in the context this function is called.
* Avoid an eternal loop even in the case of degenerate geometry. */
BLI_assert(v != v_init);
if (UNLIKELY(v == v_init)) {
return nullptr;
}
}
return v;
}
/**
* Determines if a vert touches an unselected face that would be altered if the vert was dissolved.
* This is sometimes desirable (T-junction) and sometimes not (other cases).
*/
static bool bmo_vert_touches_unselected_face(BMesh *bm, BMVert *v)
{
/* If the vert was already tested and marked, don't test again. */
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
return false;
}
/* Check each face at this vert by checking each loop. */
BMIter iter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &iter, v, BM_LOOPS_OF_VERT) {
BMLoop *l_b = BM_loop_other_edge_loop(l_a, v);
/* `l_a` and `l_b` are now the two edges of the face that share this vert.
* if both are untagged, return true. */
if (!BMO_edge_flag_test(bm, l_a->e, EDGE_TAG) && !BMO_edge_flag_test(bm, l_b->e, EDGE_TAG)) {
return true;
}
}
return false;
}
/**
* Counts how many edges touching a vert are tagged with the specified `edge_oflag`.
*/
static int bmo_vert_tagged_edges_count_at_most(BMesh *bm,
BMVert *v,
const short edge_oflag,
const int max)
{
int retval = 0;
BMIter iter;
BMEdge *e;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e, edge_oflag)) {
retval++;
}
if (retval == max) {
return retval;
}
}
return retval;
}
void bmo_dissolve_edges_init(BMOperator *op)
{
/* Set the default not to limit dissolving at all. */
BMO_slot_float_set(op->slots_in, "angle_threshold", M_PI);
}
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op)
{
// BMOperator fop;
BMOIter eiter;
BMIter iter;
BMEdge *e, *e_next;
BMVert *v, *v_next;
/* Even when geometry has exact angles like 0 or 90 or 180 deg, `angle_on_axis_v3v3v3_v3`
* can return slightly incorrect values due to cos/sin functions, floating point error, etc.
* This lets the test ignore that tiny bit of math error so users won't notice. */
const float angle_epsilon = RAD2DEGF(0.0001f);
const float angle_threshold = BMO_slot_float_get(op->slots_in, "angle_threshold");
/* Use verts when told to... except, do *not* use verts when angle_threshold is 0.0. */
const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts") &&
(angle_threshold > angle_epsilon);
/* If angle threshold is 180, don't bother with angle math, just dissolve everything. */
const bool dissolve_all = (angle_threshold > M_PI - angle_epsilon);
const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
const bool use_preserve_quads = BMO_slot_bool_get(op->slots_in, "use_preserve_quads");
if (use_face_split || use_verts) {
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_TAG);
}
/* Tag certain geometry around the selected edges, for later processing. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
/* Connected edge chains have endpoints with edge pairs. The existing behavior was to dissolve
* the verts, both in the middle, and at the ends, of any selected edges in chains. Mark these
* kind of edges, so we know to skip the angle threshold test later. */
if (BM_vert_is_edge_pair(e->v1) || BM_vert_is_edge_pair(e->v2)) {
BMO_edge_flag_enable(bm, e, EDGE_CHAIN);
}
BMFace *f_pair[2];
if (BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
/* Tag all the edges and verts of the two faces on either side of this edge.
* This edge is going to be dissolved, and after that happens, some of those elements of the
* surrounding faces might end up as loose geometry, depending on how the dissolve affected
* geometry near them. Tag them `*_ISGC`, to be checked later, and cleaned up if loose. */
uint j;
for (j = 0; j < 2; j++) {
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_pair[j]);
do {
BMO_vert_flag_enable(bm, l_iter->v, VERT_ISGC);
BMO_edge_flag_enable(bm, l_iter->e, EDGE_ISGC);
} while ((l_iter = l_iter->next) != l_first);
}
/* If using verts, and this edge is part of a chain that will be dissolved, then extend
* `EDGE_TAG` to both ends of the chain. This marks any edges that, even though they might
* not be selected, will also be dissolved when the face merge happens. This allows counting
* how many edges will remain after the dissolves are done later. */
if (use_verts && BMO_edge_flag_test(bm, e, EDGE_CHAIN)) {
bmo_find_end_of_chain(bm, e, e->v1, EDGE_TAG);
bmo_find_end_of_chain(bm, e, e->v2, EDGE_TAG);
}
}
}
if (use_verts) {
/* Mark all verts that are candidates to be dissolved. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
/* Edges only dissolve if they are manifold, so if the edge won't be dissolved, then there's
* no reason to mark either of its ends for dissolve. */
BMFace *f_pair[2];
if (!BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
continue;
}
/* if `BM_faces_join_pair` will be done, mark the correct two verts at the ends for
* dissolve. */
for (int i = 0; i < 2; i++) {
BMVert *v_edge = *((&e->v1) + i);
/* An edge between two triangles should dissolve to a quad, akin to un-triangulate.
* Prevent dissolving either corner, if doing so would collapse the corner, converting
* the quad to a triangle or wire. This happens when two triangles join, and the vert
* has two untagged edges, and the _only_ other tagged edge is this edge that's about
* to be dissolved. When that case is found, skip it, do not tag it.
* The edge count test ensures that if we're dissolving a chain, the crossing loop cuts
* will still be dissolved, even if they happen to make an "un-triangulate" case.
* This is not done when face split is active, because face split often creates triangle
* pairs on edges that touch boundaries, resulting in the boundary vert not dissolving. */
if (use_preserve_quads && f_pair[0]->len == 3 && f_pair[1]->len == 3 &&
bmo_vert_tagged_edges_count_at_most(bm, v_edge, EDGE_TAG, 2) == 1)
{
continue;
}
/* If a chain, follow the chain until the end is found. The whole chain will dissolve, so
* the test needs to happen there, at the end of the chain, where it meets other geometry,
* not here, at the end of a selected edge that only touches other parts of the chain. */
if (BM_vert_is_edge_pair(v_edge)) {
v_edge = bmo_find_end_of_chain(bm, e, v_edge, EDGE_CHAIN);
}
/* If the end of the chain was searched for and was not located, take no action. */
if (v_edge == nullptr) {
continue;
}
/* When the user selected multiple edges that meet at one vert, and there are existing
* faces at that vert that are *not* selected, then remove that vert from consideration for
* dissolve.
*
* This logic implements the following:
* - When several dissolved edges cross a loop cut, the loop cut vert should be dissolved.
* (`bmo_vert_touches_unselected_face()` will be false).
* - When dissolve edges *end* at a T on a loop cut, the loop cut vert should be dissolved.
* (`bmo_vert_tagged_edges_count_at_most()` will be 1).
* - When multiple dissolve edges touch the corner of a quad or triangle, but leave in a
* different direction, regard that contact is 'incidental' and the face should stay.
* (both tests will be true).
*/
if (bmo_vert_touches_unselected_face(bm, v_edge) &&
bmo_vert_tagged_edges_count_at_most(bm, v_edge, EDGE_TAG, 2) != 1)
{
continue;
}
/* Mark for dissolve. */
BMO_vert_flag_enable(bm, v_edge, VERT_MARK);
}
}
}
if (use_face_split) {
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BMIter itersub;
int untag_count = 0;
BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, EDGE_TAG)) {
untag_count++;
}
}
/* check that we have 2 edges remaining after dissolve */
if (untag_count <= 2) {
BMO_vert_flag_enable(bm, v, VERT_TAG);
}
}
bm_face_split(bm, VERT_TAG, false);
}
/* Merge any face pairs that straddle a selected edge. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e, &l_a, &l_b)) {
BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
}
}
/* Cleanup geometry. Remove any edges that are garbage collectible and that have became
* irrelevant (no loops) because of face merges. */
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
BM_edge_kill(bm, e);
}
}
/* Cleanup geometry. Remove any verts that are garbage collectible and that have became
* isolated verts (no edges) because of edge dissolves. */
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
BM_vert_kill(bm, v);
}
}
/* If dissolving verts, then evaluate each VERT_MARK vert. */
if (use_verts) {
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
/* If it is not an edge pair, it cannot be merged. */
BMEdge *e_pair[2];
if (BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]) == false) {
BMO_vert_flag_disable(bm, v, VERT_MARK);
continue;
}
/* At an angle threshold of 180, dissolve everything, skip the math of the angle test. */
if (dissolve_all) {
/* VERT_MARK remains enabled. */
continue;
}
/* Verts in edge chains ignore the angle test. This maintains the previous behavior,
* where such verts were not subject to the angle threshold.
*
* When edge chains are selected for dissolve, all edge-pair verts at *both* ends of each
* selected edge will be dissolved, combining the selected edges into their neighbors.
*
* Note that when only *part* of a chain is selected, this *will* alter unselected edges,
* because selected edges will merge *into their unselected neighbors*. This too, has been
* maintained, for consistency with the previous (but possibly unintentional) behavior. */
if (BMO_edge_flag_test(bm, e_pair[0], EDGE_CHAIN) ||
BMO_edge_flag_test(bm, e_pair[1], EDGE_CHAIN))
{
/* VERT_MARK remains enabled. */
continue;
}
/* If the angle at the vert is larger than the threshold, it cannot be merged. */
if (bmo_vert_calc_edge_angle_blended(v) > angle_threshold - angle_epsilon) {
BMO_vert_flag_disable(bm, v, VERT_MARK);
continue;
}
}
/* Dissolve all verts that remain tagged. This is done in a separate iteration pass. Otherwise
* the early dissolves would alter the angles measured at neighboring verts tested later. */
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
/* Even though pairs were checked before, the process of performing edge merges
* might change a neighboring vert such that it is no longer an edge pair. */
if (!BM_vert_is_edge_pair(v)) {
continue;
}
bm_vert_collapse_edge_and_merge(bm, v, true);
}
}
}
void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op)
{
BMOIter oiter;
BMIter iter;
BMVert *v, *v_next;
BMEdge *e, *e_next;
const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
const bool use_boundary_tear = BMO_slot_bool_get(op->slots_in, "use_boundary_tear");
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMO_vert_flag_enable(bm, v, VERT_MARK | VERT_ISGC);
}
if (use_face_split) {
bm_face_split(bm, VERT_MARK, false);
}
if (use_boundary_tear) {
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
if (!BM_vert_is_edge_pair(v)) {
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
BMO_vert_flag_enable(bm, v, VERT_MARK_TEAR);
break;
}
}
}
}
bm_face_split(bm, VERT_MARK_TEAR, true);
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMIter itersub;
BMLoop *l_first;
BMEdge *e_first = nullptr;
BM_ITER_ELEM (l_first, &itersub, v, BM_LOOPS_OF_VERT) {
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_enable(bm, l_iter->v, VERT_ISGC);
BMO_edge_flag_enable(bm, l_iter->e, EDGE_ISGC);
} while ((l_iter = l_iter->next) != l_first);
e_first = l_first->e;
}
/* important e_first won't be deleted */
if (e_first) {
e = e_first;
do {
e_next = BM_DISK_EDGE_NEXT(e, v);
if (BM_edge_is_wire(e)) {
BM_edge_kill(bm, e);
}
} while ((e = e_next) != e_first);
}
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
/* tag here so we avoid feedback loop (checking topology as we edit) */
if (BM_vert_is_edge_pair(v)) {
BMO_vert_flag_enable(bm, v, VERT_MARK_PAIR);
}
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMIter itersub;
/* Merge across every edge that touches `v`. This does a `BM_faces_join_pair()` for each edge.
* There may be a possible performance improvement available here, for high valence verts.
* Collecting a list of 20 faces and performing a single `BM_faces_join` would almost certainly
* more performant than doing 19 separate `BM_faces_join_pair()` of 2 faces each in sequence.
* Low valence verts would need benchmarking, to check that such a change isn't harmful. */
if (!BMO_vert_flag_test(bm, v, VERT_MARK_PAIR)) {
BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e, &l_a, &l_b)) {
BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
}
}
}
}
/* Cleanup geometry (#BM_faces_join_pair, but it removes geometry we're looping on)
* so do this in a separate pass instead. */
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
BM_edge_kill(bm, e);
}
}
/* final cleanup */
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
if (BM_vert_is_edge_pair(v)) {
bm_vert_collapse_edge_and_merge(bm, v, false);
}
}
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
BM_vert_kill(bm, v);
}
}
/* done with cleanup */
}
void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *einput = BMO_slot_get(op->slots_in, "edges");
BMOpSlot *vinput = BMO_slot_get(op->slots_in, "verts");
const float angle_max = M_PI_2;
const float angle_limit = min_ff(angle_max, BMO_slot_float_get(op->slots_in, "angle_limit"));
const bool do_dissolve_boundaries = BMO_slot_bool_get(op->slots_in, "use_dissolve_boundaries");
const BMO_Delimit delimit = BMO_Delimit(BMO_slot_int_get(op->slots_in, "delimit"));
BM_mesh_decimate_dissolve_ex(bm,
angle_limit,
do_dissolve_boundaries,
delimit,
reinterpret_cast<BMVert **> BMO_SLOT_AS_BUFFER(vinput),
vinput->len,
reinterpret_cast<BMEdge **> BMO_SLOT_AS_BUFFER(einput),
einput->len,
FACE_NEW);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW);
}
#define EDGE_MARK 1
#define EDGE_COLLAPSE 2
static void bm_mesh_edge_collapse_flagged(BMesh *bm, const int flag, const short oflag)
{
BMO_op_callf(bm, flag, "collapse edges=%fe uvs=%b", oflag, true);
}
void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op)
{
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const float dist_sq = dist * dist;
bool found;
BMIter eiter;
BMEdge *e;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
/* collapse zero length edges, this accounts for zero area faces too */
found = false;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, EDGE_MARK)) {
if (BM_edge_calc_length_squared(e) < dist_sq) {
BMO_edge_flag_enable(bm, e, EDGE_COLLAPSE);
found = true;
}
}
/* clear all loop tags (checked later) */
if (e->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
if (found) {
bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE);
}
/* clip degenerate ears from the face */
found = false;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (e->l && BMO_edge_flag_test(bm, e, EDGE_MARK)) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
if (
/* check the loop hasn't already been tested (and flag not to test again) */
!BM_elem_flag_test(l_iter, BM_ELEM_TAG) &&
((void)BM_elem_flag_enable(l_iter, BM_ELEM_TAG),
/* check we're marked to tested (radial edge already tested) */
BMO_edge_flag_test(bm, l_iter->prev->e, EDGE_MARK) &&
/* check edges are not already going to be collapsed */
!BMO_edge_flag_test(bm, l_iter->e, EDGE_COLLAPSE) &&
!BMO_edge_flag_test(bm, l_iter->prev->e, EDGE_COLLAPSE)))
{
/* test if the faces loop (ear) is degenerate */
float dir_prev[3], len_prev;
float dir_next[3], len_next;
sub_v3_v3v3(dir_prev, l_iter->prev->v->co, l_iter->v->co);
sub_v3_v3v3(dir_next, l_iter->next->v->co, l_iter->v->co);
len_prev = normalize_v3(dir_prev);
len_next = normalize_v3(dir_next);
if ((len_v3v3(dir_prev, dir_next) * min_ff(len_prev, len_next)) <= dist) {
bool reset = false;
if (fabsf(len_prev - len_next) <= dist) {
/* both edges the same length */
if (l_iter->f->len == 3) {
/* ideally this would have been discovered with short edge test above */
BMO_edge_flag_enable(bm, l_iter->next->e, EDGE_COLLAPSE);
found = true;
}
else {
/* add a joining edge and tag for removal */
BMLoop *l_split;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
reset = true;
}
}
}
else if (len_prev < len_next) {
/* split 'l_iter->e', then join the vert with next */
BMVert *v_new;
BMEdge *e_new;
BMLoop *l_split;
v_new = BM_edge_split(bm, l_iter->e, l_iter->v, &e_new, len_prev / len_next);
BLI_assert(v_new == l_iter->next->v);
(void)v_new;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
}
reset = true;
}
else if (len_next < len_prev) {
/* split 'l_iter->prev->e', then join the vert with next */
BMVert *v_new;
BMEdge *e_new;
BMLoop *l_split;
v_new = BM_edge_split(bm, l_iter->prev->e, l_iter->v, &e_new, len_next / len_prev);
BLI_assert(v_new == l_iter->prev->v);
(void)v_new;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
}
reset = true;
}
if (reset) {
/* we can't easily track where we are on the radial edge, reset! */
l_first = l_iter;
}
}
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
if (found) {
bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE);
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,747 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Duplicate, Split, Split operators.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/* local flag define */
#define DUPE_INPUT 1 /* input from operator */
#define DUPE_NEW 2
#define DUPE_DONE 4
// #define DUPE_MAPPED 8 // UNUSED
/**
* COPY VERTEX
*
* Copy an existing vertex from one bmesh to another.
*/
static BMVert *bmo_vert_copy(BMOperator *op,
BMOpSlot *slot_vertmap_out,
BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_vert_map,
BMVert *v_src,
Map<BMVert *, BMVert *> &vhash)
{
BMVert *v_dst;
/* Create a new vertex */
v_dst = BM_vert_create(bm_dst, v_src->co, nullptr, BM_CREATE_SKIP_CD);
BMO_slot_map_elem_insert(op, slot_vertmap_out, v_src, v_dst);
BMO_slot_map_elem_insert(op, slot_vertmap_out, v_dst, v_src);
/* Insert new vertex into the vert hash */
vhash.add(v_src, v_dst);
/* Copy attributes */
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);
}
/* Mark the vert for output */
BMO_vert_flag_enable(bm_dst, v_dst, DUPE_NEW);
return v_dst;
}
/**
* COPY EDGE
*
* Copy an existing edge from one bmesh to another.
*/
static BMEdge *bmo_edge_copy(BMOperator *op,
BMOpSlot *slot_edgemap_out,
BMOpSlot *slot_boundarymap_out,
BMesh *bm_dst,
BMesh *bm_src,
const std::optional<BMCustomDataCopyMap> &cd_edge_map,
BMEdge *e_src,
Map<BMVert *, BMVert *> &vhash,
Map<BMEdge *, BMEdge *> &ehash,
const bool use_edge_flip_from_face)
{
BMEdge *e_dst;
BMVert *e_dst_v1, *e_dst_v2;
uint rlen;
/* see if any of the neighboring faces are
* not being duplicated. in that case,
* add it to the new/old map. */
/* lookup edge */
rlen = 0;
if (e_src->l) {
BMLoop *l_iter_src, *l_first_src;
l_iter_src = l_first_src = e_src->l;
do {
if (BMO_face_flag_test(bm_src, l_iter_src->f, DUPE_INPUT)) {
rlen++;
}
} while ((l_iter_src = l_iter_src->radial_next) != l_first_src);
}
/* Lookup v1 and v2 */
e_dst_v1 = vhash.lookup(e_src->v1);
e_dst_v2 = vhash.lookup(e_src->v2);
/* Create a new edge */
e_dst = BM_edge_create(bm_dst, e_dst_v1, e_dst_v2, nullptr, BM_CREATE_SKIP_CD);
BMO_slot_map_elem_insert(op, slot_edgemap_out, e_src, e_dst);
BMO_slot_map_elem_insert(op, slot_edgemap_out, e_dst, e_src);
/* Add to new/old edge map if necessary. */
if (rlen < 2) {
/* not sure what non-manifold cases of greater than three
* radial should do. */
BMO_slot_map_elem_insert(op, slot_boundarymap_out, e_src, e_dst);
}
/* Insert new edge into the edge hash */
ehash.add(e_src, e_dst);
/* Copy attributes */
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);
}
/* Mark the edge for output */
BMO_edge_flag_enable(bm_dst, e_dst, DUPE_NEW);
if (use_edge_flip_from_face) {
/* Take winding from previous face (if we had one),
* otherwise extruding a duplicated edges gives bad normals, see: #62487. */
if (BM_edge_is_boundary(e_src) && (e_src->l->v == e_src->v1)) {
BM_edge_verts_swap(e_dst);
}
}
return e_dst;
}
/**
* COPY FACE
*
* Copy an existing face from one bmesh to another.
*/
static BMFace *bmo_face_copy(BMOperator *op,
BMOpSlot *slot_facemap_out,
BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_face_map,
const std::optional<BMCustomDataCopyMap> &cd_loop_map,
BMFace *f_src,
Map<BMVert *, BMVert *> &vhash,
Map<BMEdge *, BMEdge *> &ehash)
{
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 edge */
l_iter_src = l_first_src;
i = 0;
do {
vtar[i] = vhash.lookup(l_iter_src->v);
edar[i] = ehash.lookup(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);
BMO_slot_map_elem_insert(op, slot_facemap_out, f_src, f_dst);
BMO_slot_map_elem_insert(op, slot_facemap_out, f_dst, f_src);
/* 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);
/* Mark the face for output */
BMO_face_flag_enable(bm_dst, f_dst, DUPE_NEW);
return f_dst;
}
/**
* COPY MESH
*
* Internal Copy function.
*/
static void bmo_mesh_copy(BMOperator *op, BMesh *bm_dst, BMesh *bm_src)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
const bool use_edge_flip_from_face = BMO_slot_bool_get(op->slots_in, "use_edge_flip_from_face");
BMVert *v = nullptr, *v2;
BMEdge *e = nullptr;
BMFace *f = nullptr;
BMIter viter, eiter, fiter;
BMOpSlot *slot_boundary_map_out = BMO_slot_get(op->slots_out, "boundary_map.out");
BMOpSlot *slot_isovert_map_out = BMO_slot_get(op->slots_out, "isovert_map.out");
BMOpSlot *slot_vert_map_out = BMO_slot_get(op->slots_out, "vert_map.out");
BMOpSlot *slot_edge_map_out = BMO_slot_get(op->slots_out, "edge_map.out");
BMOpSlot *slot_face_map_out = BMO_slot_get(op->slots_out, "face_map.out");
/* initialize pointer hashes */
Map<BMVert *, BMVert *> vhash;
Map<BMEdge *, BMEdge *> ehash;
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)};
/* duplicate flagged vertices */
BM_ITER_MESH (v, &viter, bm_src, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm_src, v, DUPE_INPUT) &&
BMO_vert_flag_test(bm_src, v, DUPE_DONE) == false)
{
BMIter iter;
bool isolated = true;
v2 = bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, v, vhash);
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (BMO_face_flag_test(bm_src, f, DUPE_INPUT)) {
isolated = false;
break;
}
}
if (isolated) {
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm_src, e, DUPE_INPUT)) {
isolated = false;
break;
}
}
}
if (isolated) {
BMO_slot_map_elem_insert(op, slot_isovert_map_out, v, v2);
}
BMO_vert_flag_enable(bm_src, v, DUPE_DONE);
}
}
/* now we dupe all the edges */
BM_ITER_MESH (e, &eiter, bm_src, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm_src, e, DUPE_INPUT) &&
BMO_edge_flag_test(bm_src, e, DUPE_DONE) == false)
{
/* make sure that verts are copied */
if (!BMO_vert_flag_test(bm_src, e->v1, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, e->v1, vhash);
BMO_vert_flag_enable(bm_src, e->v1, DUPE_DONE);
}
if (!BMO_vert_flag_test(bm_src, e->v2, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, e->v2, vhash);
BMO_vert_flag_enable(bm_src, e->v2, DUPE_DONE);
}
/* now copy the actual edge */
bmo_edge_copy(op,
slot_edge_map_out,
slot_boundary_map_out,
bm_dst,
bm_src,
cd_edge_map,
e,
vhash,
ehash,
use_edge_flip_from_face);
BMO_edge_flag_enable(bm_src, e, DUPE_DONE);
}
}
/* first we dupe all flagged faces and their elements from source */
BM_ITER_MESH (f, &fiter, bm_src, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm_src, f, DUPE_INPUT)) {
/* vertex pass */
BM_ITER_ELEM (v, &viter, f, BM_VERTS_OF_FACE) {
if (!BMO_vert_flag_test(bm_src, v, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, v, vhash);
BMO_vert_flag_enable(bm_src, v, DUPE_DONE);
}
}
/* edge pass */
BM_ITER_ELEM (e, &eiter, f, BM_EDGES_OF_FACE) {
if (!BMO_edge_flag_test(bm_src, e, DUPE_DONE)) {
bmo_edge_copy(op,
slot_edge_map_out,
slot_boundary_map_out,
bm_dst,
bm_src,
cd_edge_map,
e,
vhash,
ehash,
use_edge_flip_from_face);
BMO_edge_flag_enable(bm_src, e, DUPE_DONE);
}
}
bmo_face_copy(op, slot_face_map_out, bm_dst, cd_face_map, cd_loop_map, f, vhash, ehash);
BMO_face_flag_enable(bm_src, f, DUPE_DONE);
}
}
if (use_select_history) {
BLI_assert(bm_src == bm_dst);
BMO_mesh_selected_remap(
bm_dst, slot_vert_map_out, slot_edge_map_out, slot_face_map_out, false);
}
}
/**
* Duplicate Operator
*
* Duplicates verts, edges and faces of a mesh.
*
* INPUT SLOTS:
*
* BMOP_DUPE_VINPUT: Buffer containing pointers to mesh vertices to be duplicated
* BMOP_DUPE_EINPUT: Buffer containing pointers to mesh edges to be duplicated
* BMOP_DUPE_FINPUT: Buffer containing pointers to mesh faces to be duplicated
*
* OUTPUT SLOTS:
*
* BMOP_DUPE_VORIGINAL: Buffer containing pointers to the original mesh vertices
* BMOP_DUPE_EORIGINAL: Buffer containing pointers to the original mesh edges
* BMOP_DUPE_FORIGINAL: Buffer containing pointers to the original mesh faces
* BMOP_DUPE_VNEW: Buffer containing pointers to the new mesh vertices
* BMOP_DUPE_ENEW: Buffer containing pointers to the new mesh edges
* BMOP_DUPE_FNEW: Buffer containing pointers to the new mesh faces
*/
void bmo_duplicate_exec(BMesh *bm, BMOperator *op)
{
BMOperator *dupeop = op;
BMesh *bm_dst = static_cast<BMesh *>(BMO_slot_ptr_get(op->slots_in, "dest"));
if (!bm_dst) {
bm_dst = bm;
}
/* flag input */
BMO_slot_buffer_flag_enable(bm, dupeop->slots_in, "geom", BM_ALL_NOLOOP, DUPE_INPUT);
/* use the internal copy function */
bmo_mesh_copy(dupeop, bm_dst, bm);
/* Output */
/* First copy the input buffers to output buffers - original data */
BMO_slot_copy(dupeop, slots_in, "geom", dupeop, slots_out, "geom_orig.out");
/* Now alloc the new output buffers */
BMO_slot_buffer_from_enabled_flag(
bm, dupeop, dupeop->slots_out, "geom.out", BM_ALL_NOLOOP, DUPE_NEW);
}
#if 0 /* UNUSED */
/**
* executes the duplicate operation, feeding elements of
* type flag etypeflag and header flag to it.
* \note to get more useful information (such as the mapping from
* original to new elements) you should run the dupe op manually.
*/
void BMO_dupe_from_flag(BMesh *bm, int htype, const char hflag)
{
BMOperator dupeop;
BMO_op_init(bm, &dupeop, "duplicate");
BMO_slot_buffer_from_enabled_hflag(bm, &dupeop, "geom", htype, hflag);
BMO_op_exec(bm, &dupeop);
BMO_op_finish(bm, &dupeop);
}
#endif
/**
* Split Operator
*
* Duplicates verts, edges and faces of a mesh but also deletes the originals.
*
* INPUT SLOTS:
*
* BMOP_DUPE_VINPUT: Buffer containing pointers to mesh vertices to be split
* BMOP_DUPE_EINPUT: Buffer containing pointers to mesh edges to be split
* BMOP_DUPE_FINPUT: Buffer containing pointers to mesh faces to be split
*
* OUTPUT SLOTS:
*
* BMOP_DUPE_VOUTPUT: Buffer containing pointers to the split mesh vertices
* BMOP_DUPE_EOUTPUT: Buffer containing pointers to the split mesh edges
* BMOP_DUPE_FOUTPUT: Buffer containing pointers to the split mesh faces
*
* \note Lower level uses of this operator may want to use #BM_mesh_separate_faces
* Since it's faster for the 'use_only_faces' case.
*/
void bmo_split_exec(BMesh *bm, BMOperator *op)
{
#define SPLIT_INPUT 1
BMOperator *splitop = op;
BMOperator dupeop;
const bool use_only_faces = BMO_slot_bool_get(op->slots_in, "use_only_faces");
/* initialize our sub-operator */
BMO_op_init(bm, &dupeop, op->flag, "duplicate");
BMO_slot_copy(splitop, slots_in, "geom", &dupeop, slots_in, "geom");
BMO_op_exec(bm, &dupeop);
BMFace *new_act_face = static_cast<BMFace *>(
BMO_slot_map_elem_get(BMO_slot_get(dupeop.slots_out, "face_map.out"), bm->act_face));
if (new_act_face) {
bm->act_face = new_act_face;
}
BMO_slot_buffer_flag_enable(bm, splitop->slots_in, "geom", BM_ALL_NOLOOP, SPLIT_INPUT);
if (use_only_faces) {
BMVert *v;
BMEdge *e;
BMFace *f;
BMIter iter, iter2;
/* make sure to remove edges and verts we don't need */
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
bool found = false;
BM_ITER_ELEM (f, &iter2, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, SPLIT_INPUT)) {
found = true;
break;
}
}
if (found == false) {
BMO_edge_flag_enable(bm, e, SPLIT_INPUT);
}
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
bool found = false;
BM_ITER_ELEM (e, &iter2, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, SPLIT_INPUT)) {
found = true;
break;
}
}
if (found == false) {
BMO_vert_flag_enable(bm, v, SPLIT_INPUT);
}
}
}
BMO_slot_copy(&dupeop, slots_out, "geom.out", splitop, slots_out, "geom.out");
BMO_slot_copy(&dupeop, slots_out, "isovert_map.out", splitop, slots_out, "isovert_map.out");
/* connect outputs of dupe to delete, excluding keep geometry */
BMO_mesh_delete_oflag_context(
bm,
SPLIT_INPUT,
DEL_FACES,
/* Call before deletion so deleted geometry isn't copied. */
[&bm, &dupeop, &splitop]() {
/* Now we make our outputs by copying the dupe output. */
/* NOTE: `boundary_map.out` can't use #BMO_slot_copy because some of the "source"
* geometry has been removed. In this case the (source -> destination) map doesn't work.
* In this case there isn't an especially good option.
* The geometry needs to be included so the boundary is accessible.
* Use the "destination" as the key and the value since it avoids adding freed
* geometry into the map and can be easily detected by other operators.
* See: #142633. */
const char *slot_name_boundary_map = "boundary_map.out";
BMOpSlot *splitop_boundary_map = BMO_slot_get(splitop->slots_out, slot_name_boundary_map);
BMOIter siter;
BMElem *ele_key;
BMO_ITER (ele_key, &siter, dupeop.slots_out, slot_name_boundary_map, 0) {
BMElem *ele_val = static_cast<BMElem *>(BMO_iter_map_value_ptr(&siter));
if (BMO_elem_flag_test(bm, ele_key, SPLIT_INPUT)) {
ele_key = ele_val;
}
BMO_slot_map_elem_insert(splitop, splitop_boundary_map, ele_key, ele_val);
}
});
/* cleanup */
BMO_op_finish(bm, &dupeop);
#undef SPLIT_INPUT
}
void bmo_delete_exec(BMesh *bm, BMOperator *op)
{
#define DEL_INPUT 1
BMOperator *delop = op;
/* Mark Buffer */
BMO_slot_buffer_flag_enable(bm, delop->slots_in, "geom", BM_ALL_NOLOOP, DEL_INPUT);
BMO_mesh_delete_oflag_context(bm, DEL_INPUT, BMO_slot_int_get(op->slots_in, "context"), nullptr);
#undef DEL_INPUT
}
/**
* Spin Operator
*
* Extrude or duplicate geometry a number of times,
* rotating and possibly translating after each step
*/
void bmo_spin_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupop, extop;
float cent[3], dvec[3];
float axis[3];
BMO_slot_vec_get(op->slots_in, "cent", cent);
BMO_slot_vec_get(op->slots_in, "axis", axis);
normalize_v3(axis);
BMO_slot_vec_get(op->slots_in, "dvec", dvec);
const bool use_dvec = !is_zero_v3(dvec);
const int steps = BMO_slot_int_get(op->slots_in, "steps");
const float angle_total = BMO_slot_float_get(op->slots_in, "angle");
const bool do_dupli = BMO_slot_bool_get(op->slots_in, "use_duplicate");
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
/* Caller needs to perform other sanity checks (such as the spin being 360d). */
const bool use_merge = BMO_slot_bool_get(op->slots_in, "use_merge") &&
/* Don't create duplicate geometry. */
(steps >= 3) &&
/* Only the "extrude" code path supports merging. */
(do_dupli == false);
BMVert **vtable = nullptr;
float (*vtable_coords)[3] = nullptr;
/* When merging, store the original vertices to splice them back together. */
if (use_merge) {
vtable = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
}
/* When extruding, always restore the original location before rotating. */
if (do_dupli == false) {
vtable_coords = MEM_new_array_uninitialized<float[3]>(bm->totvert, __func__);
}
if (vtable || vtable_coords) {
int i = 0;
BMIter iter;
BMVert *v;
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
if (vtable) {
vtable[i] = v;
}
if (vtable_coords) {
copy_v3_v3(vtable_coords[i], v->co);
}
/* Evil! store original index in normal,
* this is duplicated into every other vertex.
* So we can read the original from the final.
*
* The normals must be recalculated anyway. */
*(reinterpret_cast<int *>(&v->no[0])) = i;
}
}
BMO_slot_copy(op, slots_in, "geom", op, slots_out, "geom_last.out");
for (int a = 0; a < steps; a++) {
/* Calculate rotation matrix for this step independently to avoid floating-point error
* accumulation. */
float rmat[3][3];
{
const float step_angle = angle_total * (float(a + 1) / float(steps));
axis_angle_normalized_to_mat3(rmat, axis, step_angle);
}
if (do_dupli) {
/* For duplicate mode, duplicate from original geometry
* and rotate by total angle for this step. */
BMO_op_initf(bm, &dupop, op->flag, "duplicate geom=%s", op, "geom");
BMO_op_exec(bm, &dupop);
BMO_op_callf(bm,
op->flag,
"rotate cent=%v matrix=%m3 space=%s verts=%S",
cent,
rmat,
op,
"space",
&dupop,
"geom.out");
BMO_slot_copy(&dupop, slots_out, "geom.out", op, slots_out, "geom_last.out");
BMO_op_finish(bm, &dupop);
}
else {
BMO_op_initf(bm,
&extop,
op->flag,
"extrude_face_region "
"geom=%S "
"use_keep_orig=%b "
"use_normal_flip=%b "
"use_normal_from_adjacent=%b "
"skip_input_flip=%b",
op,
"geom_last.out",
use_merge,
use_normal_flip && (a == 0),
(a != 0),
true);
BMO_op_exec(bm, &extop);
if ((use_merge && (a == steps - 1)) == false) {
/* Reset each new vert's location to its un-rotated origin so the rotate below
* runs as a single fresh rotation from the original position
* (avoids precision loss from chained rotations, see: #148890). */
if (a != 0) {
BMOpSlot *slot_geom_out = BMO_slot_get(extop.slots_out, "geom.out");
BMElem **elem_array = reinterpret_cast<BMElem **>(slot_geom_out->data.buf);
const int elem_array_len = slot_geom_out->len;
for (int i = 0; i < elem_array_len; i++) {
if (elem_array[i]->head.htype == BM_VERT) {
BMVert *v_src = reinterpret_cast<BMVert *>(elem_array[i]);
const int index = *(reinterpret_cast<const int *>(&v_src->no[0]));
copy_v3_v3(v_src->co, vtable_coords[index]);
}
}
}
BMO_op_callf(bm,
op->flag,
"rotate cent=%v matrix=%m3 space=%s verts=%S",
cent,
rmat,
op,
"space",
&extop,
"geom.out");
BMO_slot_copy(&extop, slots_out, "geom.out", op, slots_out, "geom_last.out");
}
else {
/* Merge first/last vertices and edges (maintaining 'geom.out' state). */
BMOpSlot *slot_geom_out = BMO_slot_get(extop.slots_out, "geom.out");
BMElem **elem_array = reinterpret_cast<BMElem **>(slot_geom_out->data.buf);
int elem_array_len = slot_geom_out->len;
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_VERT) {
BMVert *v_src = reinterpret_cast<BMVert *>(elem_array[i]);
BMVert *v_dst = vtable[*(reinterpret_cast<const int *>(&v_src->no[0]))];
BM_vert_splice(bm, v_dst, v_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
}
else {
i++;
}
}
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_EDGE) {
BMEdge *e_src = reinterpret_cast<BMEdge *>(elem_array[i]);
BMEdge *e_dst = BM_edge_find_double(e_src);
if (e_dst != nullptr) {
BM_edge_splice(bm, e_dst, e_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
continue;
}
}
i++;
}
/* Full copies of faces may cause overlap. */
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_FACE) {
BMFace *f_src = reinterpret_cast<BMFace *>(elem_array[i]);
BMFace *f_dst = BM_face_find_double(f_src);
if (f_dst != nullptr) {
BM_face_kill(bm, f_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
continue;
}
}
i++;
}
slot_geom_out->len = elem_array_len;
}
BMO_op_finish(bm, &extop);
}
if (use_dvec) {
float dvec_step[3];
mul_v3_m3v3(dvec_step, rmat, dvec);
mul_v3_fl(dvec_step, float(a + 1));
BMO_op_callf(bm,
op->flag,
"translate vec=%v space=%s verts=%S",
dvec_step,
op,
"space",
op,
"geom_last.out");
}
}
if (vtable) {
MEM_delete(vtable);
}
if (vtable_coords) {
MEM_delete(vtable_coords);
}
}
} // namespace blender

View File

@@ -0,0 +1,239 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Edge-Net for filling in open edge-loops.
*/
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_MARK 1
#define EDGE_VIS 2
#define ELE_NEW 1
void bmo_edgenet_fill_exec(BMesh *bm, BMOperator *op)
{
BMOperator op_attr;
BMOIter siter;
BMFace *f;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
// const int sides = BMO_slot_int_get(op->slots_in, "sides");
if (!bm->totvert || !bm->totedge) {
return;
}
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "edges", BM_EDGE, BM_ELEM_TAG, false);
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
BM_mesh_edgenet(bm, true, true); /* TODO: sides. */
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_ITER (f, &siter, op->slots_out, "faces.out", BM_FACE) {
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
/* Normals are zeroed. */
BM_face_normal_update(f);
}
/* --- Attribute Fill --- */
/* may as well since we have the faces already in a buffer */
BMO_op_initf(bm,
&op_attr,
op->flag,
"face_attribute_fill faces=%S use_normals=%b use_data=%b",
op,
"faces.out",
true,
true);
BMO_op_exec(bm, &op_attr);
/* check if some faces couldn't be touched */
if (BMO_slot_buffer_len(op_attr.slots_out, "faces_fail.out")) {
BMO_op_callf(bm, op->flag, "recalc_face_normals faces=%S", &op_attr, "faces_fail.out");
}
BMO_op_finish(bm, &op_attr);
}
static BMEdge *edge_next(BMesh *bm, BMEdge *e)
{
BMIter iter;
BMEdge *e2;
int i;
for (i = 0; i < 2; i++) {
BM_ITER_ELEM (e2, &iter, i ? e->v2 : e->v1, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e2, EDGE_MARK) &&
(BMO_edge_flag_test(bm, e2, EDGE_VIS) == false) && (e2 != e))
{
return e2;
}
}
}
return nullptr;
}
void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMEdge *e;
bool ok = true;
int i, count;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
/* validate that each edge has at most one other tagged edge in the
* disk cycle around each of its vertices */
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
for (i = 0; i < 2; i++) {
count = BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, (i ? e->v2 : e->v1), EDGE_MARK, true);
if (count > 2) {
ok = false;
break;
}
}
if (!ok) {
break;
}
}
/* we don't have valid edge layouts, return */
if (!ok) {
return;
}
Vector<BMEdge *> edges1;
Vector<BMEdge *> edges2;
Vector<BMEdge *> *edges;
/* find connected loops within the input edge */
count = 0;
while (true) {
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
if (!BMO_edge_flag_test(bm, e, EDGE_VIS)) {
if (BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, e->v1, EDGE_MARK, true) == 1 ||
BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, e->v2, EDGE_MARK, true) == 1)
{
break;
}
}
}
if (!e) {
break;
}
if (!count) {
edges = &edges1;
}
else if (count == 1) {
edges = &edges2;
}
else {
break;
}
i = 0;
while (e) {
BMO_edge_flag_enable(bm, e, EDGE_VIS);
edges->append(e);
e = edge_next(bm, e);
i++;
}
count++;
}
if (edges1.size() > 2 && BM_edge_share_vert_check(edges1.first(), edges1.last())) {
if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) {
return;
}
edges1 = edges2;
edges2.clear();
}
if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) {
edges2.clear();
}
/* two unconnected loops, connect the */
if (!edges1.is_empty() && !edges2.is_empty()) {
BMVert *v1, *v2, *v3, *v4;
float dvec1[3];
float dvec2[3];
if (edges1.size() == 1) {
v1 = edges1[0]->v1;
v2 = edges1[0]->v2;
}
else {
v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
i = edges1.size() - 1;
v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
}
if (edges2.size() == 1) {
v3 = edges2[0]->v1;
v4 = edges2[0]->v2;
}
else {
v3 = BM_vert_in_edge(edges2[1], edges2[0]->v1) ? edges2[0]->v2 : edges2[0]->v1;
i = edges2.size() - 1;
v4 = BM_vert_in_edge(edges2[i - 1], edges2[i]->v1) ? edges2[i]->v2 : edges2[i]->v1;
}
/* Avoid bow tie quads using most planar the triangle pair, see: #30367 & #143905. */
normal_tri_v3(dvec1, v1->co, v2->co, v4->co);
normal_tri_v3(dvec2, v1->co, v4->co, v3->co);
const float dot_24 = dot_v3v3(dvec1, dvec2);
normal_tri_v3(dvec1, v1->co, v2->co, v3->co);
normal_tri_v3(dvec2, v1->co, v3->co, v4->co);
const float dot_13 = dot_v3v3(dvec1, dvec2);
if (dot_24 < dot_13) {
std::swap(v3, v4);
}
e = BM_edge_create(bm, v1, v3, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
e = BM_edge_create(bm, v2, v4, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
}
else if (!edges1.is_empty()) {
BMVert *v1, *v2;
if (edges1.size() > 1) {
v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
i = edges1.size() - 1;
v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
}
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,866 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Extrude faces and solidify.
*/
#include "MEM_guardedalloc.h"
#include "DNA_meshdata_types.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define USE_EDGE_REGION_FLAGS
enum {
EXT_INPUT = 1,
EXT_KEEP = 2,
EXT_DEL = 4,
EXT_TAG = 8,
};
#define VERT_MARK 1
#define EDGE_MARK 1
#define FACE_MARK 1
#define VERT_NONMAN 2
#define EDGE_NONMAN 2
void bmo_extrude_discrete_faces_exec(BMesh *bm, BMOperator *op)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
GHash *select_history_map = nullptr;
BMOIter siter;
BMFace *f_org;
if (use_select_history) {
select_history_map = BM_select_history_map_create(bm);
}
BMO_ITER (f_org, &siter, op->slots_in, "faces", BM_FACE) {
BMFace *f_new;
BMLoop *l_org, *l_org_first;
BMLoop *l_new;
BMO_face_flag_enable(bm, f_org, EXT_DEL);
f_new = BM_face_copy(bm, f_org, true, true);
BMO_face_flag_enable(bm, f_new, EXT_KEEP);
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, f_org));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(f_new);
}
}
l_org = l_org_first = BM_FACE_FIRST_LOOP(f_org);
l_new = BM_FACE_FIRST_LOOP(f_new);
do {
BMFace *f_side;
BMLoop *l_side_iter;
BM_elem_attrs_copy(bm, l_org, l_new);
f_side = BM_face_create_quad_tri(
bm, l_org->next->v, l_new->next->v, l_new->v, l_org->v, f_org, BM_CREATE_NOP);
l_side_iter = BM_FACE_FIRST_LOOP(f_side);
BM_elem_attrs_copy(bm, l_org->next, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org->next, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org, l_side_iter);
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, l_org->v));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(l_new->v);
}
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, l_org->e));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(l_new->e);
}
}
} while (((void)(l_new = l_new->next), (l_org = l_org->next)) != l_org_first);
}
if (select_history_map) {
BLI_ghash_free(select_history_map, nullptr, nullptr);
}
BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", EXT_DEL, DEL_ONLYFACES);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, EXT_KEEP);
}
/**
* \brief Copy the loop pair from an adjacent face to both sides of this quad.
*
* The face is assumed to be a quad, created by extruding.
* This function won't crash if its not but won't work right either.
* \a e_b is the new edge.
*
* \note The edge this face comes from needs to be from the first and second verts to the face.
* The caller must ensure this else we will copy from the wrong source.
*/
static void bm_extrude_copy_face_loop_attributes(BMesh *bm, BMFace *f)
{
/* edge we are extruded from */
BMLoop *l_first_0 = BM_FACE_FIRST_LOOP(f);
BMLoop *l_first_1 = l_first_0->next;
BMLoop *l_first_2 = l_first_1->next;
BMLoop *l_first_3 = l_first_2->next;
BMLoop *l_other_0;
BMLoop *l_other_1;
if (UNLIKELY(l_first_0 == l_first_0->radial_next)) {
return;
}
l_other_0 = BM_edge_other_loop(l_first_0->e, l_first_0);
l_other_1 = BM_edge_other_loop(l_first_0->e, l_first_1);
/* copy data */
BM_elem_attrs_copy(bm, l_other_0->f, f);
BM_elem_flag_disable(f, BM_ELEM_HIDDEN); /* possibly we copy from a hidden face */
BM_elem_attrs_copy(bm, l_other_0, l_first_0);
BM_elem_attrs_copy(bm, l_other_0, l_first_3);
BM_elem_attrs_copy(bm, l_other_1, l_first_1);
BM_elem_attrs_copy(bm, l_other_1, l_first_2);
}
/* Disable the skin root flag on the input vert, assumes that the vert
* data includes an CD_MVERT_SKIN layer */
static void bm_extrude_disable_skin_root(BMesh *bm, BMVert *v)
{
MVertSkin *vs;
vs = static_cast<MVertSkin *>(CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN));
vs->flag &= ~MVERT_SKIN_ROOT;
}
void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMOperator dupeop;
BMFace *f;
BMEdge *e, *e_new;
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
BMO_edge_flag_enable(bm, e, EXT_INPUT);
BMO_vert_flag_enable(bm, e->v1, EXT_INPUT);
BMO_vert_flag_enable(bm, e->v2, EXT_INPUT);
}
BMO_op_initf(bm,
&dupeop,
op->flag,
"duplicate geom=%fve use_select_history=%b",
EXT_INPUT,
BMO_slot_bool_get(op->slots_in, "use_select_history"));
BMO_op_exec(bm, &dupeop);
/* disable root flag on all new skin nodes */
if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) {
BMVert *v;
BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) {
bm_extrude_disable_skin_root(bm, v);
}
}
for (e = static_cast<BMEdge *>(BMO_iter_new(&siter, dupeop.slots_out, "boundary_map.out", 0)); e;
e = static_cast<BMEdge *>(BMO_iter_step(&siter)))
{
BMVert *f_verts[4];
e_new = static_cast<BMEdge *>(BMO_iter_map_value_ptr(&siter));
const bool edge_normal_flip = !(e->l && e->v1 != e->l->v);
if (edge_normal_flip == use_normal_flip) {
f_verts[0] = e->v1;
f_verts[1] = e->v2;
f_verts[2] = e_new->v2;
f_verts[3] = e_new->v1;
}
else {
f_verts[0] = e->v2;
f_verts[1] = e->v1;
f_verts[2] = e_new->v1;
f_verts[3] = e_new->v2;
}
/* not sure what to do about example face, pass nullptr for now */
f = BM_face_create_verts(bm, f_verts, 4, nullptr, BM_CREATE_NOP, true);
bm_extrude_copy_face_loop_attributes(bm, f);
if (BMO_edge_flag_test(bm, e, EXT_INPUT)) {
e = e_new;
}
BMO_face_flag_enable(bm, f, EXT_KEEP);
BMO_edge_flag_enable(bm, e, EXT_KEEP);
BMO_vert_flag_enable(bm, e->v1, EXT_KEEP);
BMO_vert_flag_enable(bm, e->v2, EXT_KEEP);
}
BMO_op_finish(bm, &dupeop);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, EXT_KEEP);
}
void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
BMOIter siter;
BMVert *v, *dupev;
BMEdge *e;
const bool has_vskin = CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN);
GHash *select_history_map = nullptr;
if (use_select_history) {
select_history_map = BM_select_history_map_create(bm);
}
for (v = static_cast<BMVert *>(BMO_iter_new(&siter, op->slots_in, "verts", BM_VERT)); v;
v = static_cast<BMVert *>(BMO_iter_step(&siter)))
{
dupev = BM_vert_create(bm, v->co, v, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, dupev, EXT_KEEP);
if (has_vskin) {
bm_extrude_disable_skin_root(bm, v);
}
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, v));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(dupev);
}
}
/* not essential, but ensures face normals from extruded edges are contiguous */
if (BM_vert_is_wire_endpoint(v)) {
if (v->e->v1 == v) {
std::swap(v, dupev);
}
}
e = BM_edge_create(bm, v, dupev, nullptr, BM_CREATE_NOP);
BMO_edge_flag_enable(bm, e, EXT_KEEP);
}
if (select_history_map) {
BLI_ghash_free(select_history_map, nullptr, nullptr);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "verts.out", BM_VERT, EXT_KEEP);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EXT_KEEP);
}
#ifdef USE_EDGE_REGION_FLAGS
/**
* When create an edge for an extruded face region
* check surrounding edge flags before creating a new edge.
*/
static bool bm_extrude_region_edge_flag(const BMVert *v, char r_e_hflag[2])
{
BMEdge *e_iter;
const char hflag_enable = BM_ELEM_SEAM;
const char hflag_disable = BM_ELEM_SMOOTH;
bool ok = false;
r_e_hflag[0] = 0x0;
r_e_hflag[1] = 0xff;
/* clear flags on both disks */
e_iter = v->e;
do {
if (e_iter->l && !BM_edge_is_boundary(e_iter)) {
r_e_hflag[0] |= e_iter->head.hflag;
r_e_hflag[1] &= e_iter->head.hflag;
ok = true;
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != v->e);
if (ok) {
r_e_hflag[0] &= hflag_enable;
r_e_hflag[1] = hflag_disable & ~r_e_hflag[1];
}
return ok;
}
#endif /* USE_EDGE_REGION_FLAGS */
void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupeop, delop;
BMOIter siter;
BMIter iter, fiter, viter;
BMEdge *e, *e_new;
BMVert *v;
BMFace *f;
bool found, delorig = false;
BMOpSlot *slot_facemap_out;
BMOpSlot *slot_edges_exclude;
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
const bool use_normal_from_adjacent = BMO_slot_bool_get(op->slots_in,
"use_normal_from_adjacent");
const bool use_dissolve_ortho_edges = BMO_slot_bool_get(op->slots_in,
"use_dissolve_ortho_edges");
/* initialize our sub-operators */
BMO_op_initf(bm,
&dupeop,
op->flag,
"duplicate use_select_history=%b",
BMO_slot_bool_get(op->slots_in, "use_select_history"));
BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_EDGE | BM_FACE, EXT_INPUT);
/* if one flagged face is bordered by an un-flagged face, then we delete
* original geometry unless caller explicitly asked to keep it. */
if (!BMO_slot_bool_get(op->slots_in, "use_keep_orig")) {
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
int edge_face_tot;
if (!BMO_edge_flag_test(bm, e, EXT_INPUT)) {
continue;
}
found = false; /* found a face that isn't input? */
edge_face_tot = 0; /* edge/face count */
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, EXT_INPUT)) {
found = true;
delorig = true;
break;
}
edge_face_tot++;
}
if ((edge_face_tot > 1) && (found == false)) {
/* edge has a face user, that face isn't extrude input */
BMO_edge_flag_enable(bm, e, EXT_DEL);
}
}
}
/* calculate verts to delete */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (v->e) { /* only deal with verts attached to geometry #33651. */
found = false;
BM_ITER_ELEM (e, &viter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, EXT_INPUT) || !BMO_edge_flag_test(bm, e, EXT_DEL)) {
found = true;
break;
}
}
/* avoid an extra loop */
if (found == false) {
BM_ITER_ELEM (f, &viter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, EXT_INPUT)) {
found = true;
break;
}
}
}
if (found == false) {
BMO_vert_flag_enable(bm, v, EXT_DEL);
}
}
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, EXT_INPUT)) {
BMO_face_flag_enable(bm, f, EXT_DEL);
}
}
if (delorig == true) {
BMO_op_initf(bm, &delop, op->flag, "delete geom=%fvef context=%i", EXT_DEL, DEL_ONLYTAGGED);
}
BMO_slot_copy(op, slots_in, "geom", &dupeop, slots_in, "geom");
BMO_op_exec(bm, &dupeop);
/* disable root flag on all new skin nodes */
if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) {
BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) {
bm_extrude_disable_skin_root(bm, v);
}
}
slot_facemap_out = BMO_slot_get(dupeop.slots_out, "face_map.out");
if (bm->act_face && BMO_face_flag_test(bm, bm->act_face, EXT_INPUT)) {
bm->act_face = static_cast<BMFace *>(BMO_slot_map_elem_get(slot_facemap_out, bm->act_face));
}
if (delorig) {
BMO_op_exec(bm, &delop);
}
const bool skip_input_flip = BMO_slot_bool_get(op->slots_in, "skip_input_flip");
/* Flip input faces only when originals are kept (!delorig)
* and the caller didn't request to skip flipping (!skip_input_flip).*/
if (!delorig && !skip_input_flip) {
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, EXT_INPUT)) {
BM_face_normal_flip(bm, f);
}
}
}
BMVert **dissolve_verts = nullptr;
int dissolve_verts_len = 0;
float average_normal[3];
if (use_dissolve_ortho_edges) {
/* Calc average normal. */
zero_v3(average_normal);
BMO_ITER (f, &siter, dupeop.slots_out, "geom.out", BM_FACE) {
add_v3_v3(average_normal, f->no);
}
if (normalize_v3(average_normal) == 0.0f) {
average_normal[2] = 1.0f;
}
/* Allocate array to store possible vertices that will be dissolved. */
int boundary_edges_len = BMO_slot_map_len(dupeop.slots_out, "boundary_map.out");
/* We do not know the real number of boundary vertices. */
int boundary_verts_len_maybe = 2 * boundary_edges_len;
dissolve_verts = MEM_new_array_uninitialized<BMVert *>(boundary_verts_len_maybe, __func__);
}
BMO_slot_copy(&dupeop, slots_out, "geom.out", op, slots_out, "geom.out");
slot_edges_exclude = BMO_slot_get(op->slots_in, "edges_exclude");
for (e = static_cast<BMEdge *>(BMO_iter_new(&siter, dupeop.slots_out, "boundary_map.out", 0)); e;
e = static_cast<BMEdge *>(BMO_iter_step(&siter)))
{
BMVert *f_verts[4];
#ifdef USE_EDGE_REGION_FLAGS
BMEdge *f_edges[4];
#endif
/* this should always be wire, so this is mainly a speedup to avoid map lookup */
if (BM_edge_is_wire(e) && BMO_slot_map_contains(slot_edges_exclude, e)) {
BMVert *v1 = e->v1, *v2 = e->v2;
/* The original edge was excluded,
* this would result in a standalone wire edge - see #30399. */
BM_edge_kill(bm, e);
/* kill standalone vertices from this edge - see #32341. */
if (!v1->e) {
BM_vert_kill(bm, v1);
}
if (!v2->e) {
BM_vert_kill(bm, v2);
}
continue;
}
/* skip creating face for excluded edges see #35503. */
if (BMO_slot_map_contains(slot_edges_exclude, e)) {
/* simply skip creating the face */
continue;
}
e_new = static_cast<BMEdge *>(BMO_iter_map_value_ptr(&siter));
if (!e_new) {
continue;
}
BMFace *join_face = nullptr;
if (use_dissolve_ortho_edges) {
if (BM_edge_is_boundary(e)) {
join_face = e->l->f;
if (fabs(dot_v3v3(average_normal, join_face->no)) > 0.0001f) {
join_face = nullptr;
}
}
}
bool edge_normal_flip;
if (use_normal_from_adjacent == false) {
/* Orient loop to give same normal as a loop of 'e_new'
* if it exists (will be one of the faces from the region),
* else same normal as a loop of e, if it exists. */
edge_normal_flip = !(e_new->l ? (e_new->l->v == e_new->v1) : (!e->l || !(e->l->v == e->v1)));
}
else {
/* Special case, needed for repetitive extrusions
* that use the normals from the previously created faces. */
edge_normal_flip = !(e->l && e->v1 != e->l->v);
}
if (edge_normal_flip == use_normal_flip) {
f_verts[0] = e->v1;
f_verts[1] = e->v2;
f_verts[2] = e_new->v2;
f_verts[3] = e_new->v1;
}
else {
f_verts[0] = e->v2;
f_verts[1] = e->v1;
f_verts[2] = e_new->v1;
f_verts[3] = e_new->v2;
}
#ifdef USE_EDGE_REGION_FLAGS
/* handle new edges */
f_edges[0] = e;
f_edges[2] = e_new;
f_edges[1] = BM_edge_exists(f_verts[1], f_verts[2]);
if (f_edges[1] == nullptr) {
char e_hflag[2];
bool e_hflag_ok = bm_extrude_region_edge_flag(f_verts[2], e_hflag);
f_edges[1] = BM_edge_create(bm, f_verts[1], f_verts[2], nullptr, BM_CREATE_NOP);
if (e_hflag_ok) {
BM_elem_flag_enable(f_edges[1], e_hflag[0]);
BM_elem_flag_disable(f_edges[1], e_hflag[1]);
}
}
f_edges[3] = BM_edge_exists(f_verts[3], f_verts[0]);
if (f_edges[3] == nullptr) {
char e_hflag[2];
bool e_hflag_ok = bm_extrude_region_edge_flag(f_verts[3], e_hflag);
f_edges[3] = BM_edge_create(bm, f_verts[3], f_verts[0], nullptr, BM_CREATE_NOP);
if (e_hflag_ok) {
BM_elem_flag_enable(f_edges[3], e_hflag[0]);
BM_elem_flag_disable(f_edges[3], e_hflag[1]);
}
}
f = BM_face_create(bm, f_verts, f_edges, 4, nullptr, BM_CREATE_NOP);
#else
f = BM_face_create_verts(bm, f_verts, 4, nullptr, BM_CREATE_NOP, true);
#endif
bm_extrude_copy_face_loop_attributes(bm, f);
if (join_face) {
BMVert *v1 = e->v1;
BMVert *v2 = e->v2;
if (!BMO_elem_flag_test(bm, v1, EXT_TAG)) {
BMO_elem_flag_enable(bm, v1, EXT_TAG);
dissolve_verts[dissolve_verts_len++] = v1;
}
if (!BMO_elem_flag_test(bm, v2, EXT_TAG)) {
BMO_elem_flag_enable(bm, v2, EXT_TAG);
dissolve_verts[dissolve_verts_len++] = v2;
}
/* Tag the edges that can collapse. */
BMO_elem_flag_enable(bm, f_edges[0], EXT_TAG);
BMO_elem_flag_enable(bm, f_edges[1], EXT_TAG);
bmesh_kernel_join_face_kill_edge(bm, join_face, f, e);
}
}
/* link isolated vert */
for (v = static_cast<BMVert *>(BMO_iter_new(&siter, dupeop.slots_out, "isovert_map.out", 0)); v;
v = static_cast<BMVert *>(BMO_iter_step(&siter)))
{
BMVert *v2 = static_cast<BMVert *>(BMO_iter_map_value_ptr(&siter));
/* not essential, but ensures face normals from extruded edges are contiguous */
if (BM_vert_is_wire_endpoint(v)) {
if (v->e->v1 == v) {
std::swap(v, v2);
}
}
BM_edge_create(bm, v, v2, nullptr, BM_CREATE_NO_DOUBLE);
}
if (dissolve_verts) {
BMVert **v_iter = &dissolve_verts[0];
for (int i = dissolve_verts_len; i--; v_iter++) {
v = *v_iter;
e = v->e;
BMEdge *e_other = BM_DISK_EDGE_NEXT(e, v);
if ((e_other == e) || (BM_DISK_EDGE_NEXT(e_other, v) == e)) {
/* Loose edge or BMVert is edge pair. */
BM_edge_collapse(bm, BMO_elem_flag_test(bm, e, EXT_TAG) ? e : e_other, v, true, true);
}
else {
BLI_assert(!BM_vert_is_edge_pair(v));
}
}
MEM_delete(dissolve_verts);
}
/* cleanup */
if (delorig) {
BMO_op_finish(bm, &delop);
}
BMO_op_finish(bm, &dupeop);
}
/*
* Compute higher-quality vertex normals used by solidify.
* Only considers geometry in the marked solidify region.
* Note that this does not work so well for non-manifold
* regions.
*/
static void calc_solidify_normals(BMesh *bm)
{
BMIter viter, eiter, fiter;
BMVert *v;
BMEdge *e;
BMFace *f, *f1, *f2;
float edge_normal[3];
int i;
/* can't use BM_edge_face_count because we need to count only marked faces */
int *edge_face_count = MEM_new_array_zeroed<int>(bm->totedge, __func__);
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
BM_mesh_elem_index_ensure(bm, BM_EDGE);
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BMO_face_flag_test(bm, f, FACE_MARK)) {
continue;
}
BM_ITER_ELEM (e, &eiter, f, BM_EDGES_OF_FACE) {
/* And mark all edges and vertices on the
* marked faces */
BMO_edge_flag_enable(bm, e, EDGE_MARK);
BMO_vert_flag_enable(bm, e->v1, VERT_MARK);
BMO_vert_flag_enable(bm, e->v2, VERT_MARK);
edge_face_count[BM_elem_index_get(e)]++;
}
}
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
i = edge_face_count[BM_elem_index_get(e)];
if (i == 0 || i > 2) {
/* Edge & vertices are non-manifold even when considering
* only marked faces */
BMO_edge_flag_enable(bm, e, EDGE_NONMAN);
BMO_vert_flag_enable(bm, e->v1, VERT_NONMAN);
BMO_vert_flag_enable(bm, e->v2, VERT_NONMAN);
}
}
MEM_delete(edge_face_count);
edge_face_count = nullptr; /* don't re-use */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (!BM_vert_is_manifold(v)) {
BMO_vert_flag_enable(bm, v, VERT_NONMAN);
continue;
}
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
zero_v3(v->no);
}
}
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
/* If the edge is not part of the solidify region
* its normal should not be considered */
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
/* If the edge joins more than two marked faces high
* quality normal computation won't work */
if (BMO_edge_flag_test(bm, e, EDGE_NONMAN)) {
continue;
}
f1 = f2 = nullptr;
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
if (f1 == nullptr) {
f1 = f;
}
else {
BLI_assert(f2 == nullptr);
f2 = f;
}
}
}
BLI_assert(f1 != nullptr);
if (f2 != nullptr) {
const float angle = angle_normalized_v3v3(f1->no, f2->no);
if (angle > 0.0f) {
/* two faces using this edge, calculate the edge normal
* using the angle between the faces as a weighting */
add_v3_v3v3(edge_normal, f1->no, f2->no);
normalize_v3_length(edge_normal, angle);
}
else {
/* can't do anything useful here!
* Set the face index for a vert in case it gets a zero normal */
BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
continue;
}
}
else {
/* only one face attached to that edge */
/* an edge without another attached- the weight on this is undefined,
* M_PI_2 is 90d in radians and that seems good enough */
copy_v3_v3(edge_normal, f1->no);
mul_v3_fl(edge_normal, M_PI_2);
}
add_v3_v3(e->v1->no, edge_normal);
add_v3_v3(e->v2->no, edge_normal);
}
/* normalize accumulated vertex normal */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
if (BMO_vert_flag_test(bm, v, VERT_NONMAN)) {
/* use standard normals for vertices connected to non-manifold edges */
BM_vert_normal_update(v);
}
else if (normalize_v3(v->no) == 0.0f && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
/* exceptional case, totally flat. use the normal
* of any marked face around the vertex */
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
break;
}
}
copy_v3_v3(v->no, f->no);
}
}
}
static void solidify_add_thickness(BMesh *bm, const float dist)
{
BMFace *f;
BMVert *v;
BMLoop *l;
BMIter iter, loopIter;
float *vert_angles = MEM_new_array_zeroed<float>(size_t(bm->totvert) * 2,
"solidify"); /* 2 in 1 */
float *vert_accum = vert_angles + bm->totvert;
int i, index;
Vector<float, BM_DEFAULT_NGON_STACK_SIZE> face_angles;
Vector<float *, BM_DEFAULT_NGON_STACK_SIZE> verts;
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
/* array for passing verts to angle_poly_v3 */
face_angles.resize(f->len);
/* array for receiving angles from angle_poly_v3 */
verts.resize(f->len);
BM_ITER_ELEM_INDEX (l, &loopIter, f, BM_LOOPS_OF_FACE, i) {
verts[i] = l->v->co;
}
angle_poly_v3(face_angles.data(), (const float **)verts.data(), f->len);
i = 0;
BM_ITER_ELEM (l, &loopIter, f, BM_LOOPS_OF_FACE) {
v = l->v;
index = BM_elem_index_get(v);
vert_accum[index] += face_angles[i];
vert_angles[index] += shell_v3v3_normalized_to_dist(v->no, f->no) * face_angles[i];
i++;
}
}
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
index = BM_elem_index_get(v);
if (vert_accum[index]) { /* zero if unselected */
madd_v3_v3fl(v->co, v->no, dist * (vert_angles[index] / vert_accum[index]));
}
}
MEM_delete(vert_angles);
}
void bmo_solidify_face_region_exec(BMesh *bm, BMOperator *op)
{
BMOperator extrudeop;
BMOperator reverseop;
float thickness;
thickness = BMO_slot_float_get(op->slots_in, "thickness");
/* Flip original faces (so the shell is extruded inward) */
BMO_op_init(bm, &reverseop, op->flag, "reverse_faces");
BMO_slot_bool_set(reverseop.slots_in, "flip_multires", true);
BMO_slot_copy(op, slots_in, "geom", &reverseop, slots_in, "faces");
BMO_op_exec(bm, &reverseop);
BMO_op_finish(bm, &reverseop);
/* Extrude the region */
BMO_op_initf(bm, &extrudeop, op->flag, "extrude_face_region use_keep_orig=%b", true);
BMO_slot_copy(op, slots_in, "geom", &extrudeop, slots_in, "geom");
BMO_op_exec(bm, &extrudeop);
/* Push the verts of the extruded faces inward to create thickness */
BMO_slot_buffer_flag_enable(bm, extrudeop.slots_out, "geom.out", BM_FACE, FACE_MARK);
calc_solidify_normals(bm);
solidify_add_thickness(bm, thickness);
BMO_slot_copy(&extrudeop, slots_out, "geom.out", op, slots_out, "geom.out");
BMO_op_finish(bm, &extrudeop);
}
} // namespace blender

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill in geometry with the attributes of their adjacent data.
*/
#include "BLI_linklist_stack.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/**
* Check if all other loops are tagged.
*/
static bool bm_loop_is_all_radial_tag(BMLoop *l)
{
BMLoop *l_iter;
l_iter = l->radial_next;
do {
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) == 0) {
return false;
}
} while ((l_iter = l_iter->radial_next) != l);
return true;
}
/**
* Callback to run on source-loops for #BM_face_copy_shared
*/
static bool bm_loop_is_face_untag(const BMLoop *l, void * /*user_data*/)
{
return (BM_elem_flag_test(l->f, BM_ELEM_TAG) == 0);
}
/**
* Copy all attributes from adjacent untagged faces.
*/
static void bm_face_copy_shared_all(BMesh *bm,
BMLoop *l,
const bool use_normals,
const bool use_data)
{
BMLoop *l_other = l->radial_next;
BMFace *f = l->f, *f_other;
while (BM_elem_flag_test(l_other->f, BM_ELEM_TAG)) {
l_other = l_other->radial_next;
}
f_other = l_other->f;
if (use_data) {
/* copy face-attrs */
BM_elem_attrs_copy(bm, f_other, f);
/* copy loop-attrs */
BM_face_copy_shared(bm, f, bm_loop_is_face_untag, nullptr);
}
if (use_normals) {
/* copy winding (flipping) */
if (l->v == l_other->v) {
BM_face_normal_flip(bm, f);
}
}
}
/**
* Flood fill attributes.
*/
static uint bmesh_face_attribute_fill(BMesh *bm, const bool use_normals, const bool use_data)
{
BLI_LINKSTACK_DECLARE(loop_queue_prev, BMLoop *);
BLI_LINKSTACK_DECLARE(loop_queue_next, BMLoop *);
BMFace *f;
BMIter iter;
BMLoop *l;
uint face_tot = 0;
BLI_LINKSTACK_INIT(loop_queue_prev);
BLI_LINKSTACK_INIT(loop_queue_next);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_TAG)) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (bm_loop_is_all_radial_tag(l_iter) == false) {
BLI_LINKSTACK_PUSH(loop_queue_prev, l_iter);
}
} while ((l_iter = l_iter->next) != l_first);
}
}
while (BLI_LINKSTACK_SIZE(loop_queue_prev)) {
while ((l = BLI_LINKSTACK_POP(loop_queue_prev))) {
/* check we're still un-assigned */
if (BM_elem_flag_test(l->f, BM_ELEM_TAG)) {
BMLoop *l_iter;
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
l_iter = l->next;
do {
BMLoop *l_radial_iter = l_iter->radial_next;
if (l_radial_iter != l_iter) {
do {
if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) {
BLI_LINKSTACK_PUSH(loop_queue_next, l_radial_iter);
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l_iter);
}
} while ((l_iter = l_iter->next) != l);
/* do last because of face flipping */
bm_face_copy_shared_all(bm, l, use_normals, use_data);
face_tot += 1;
}
}
BLI_LINKSTACK_SWAP(loop_queue_prev, loop_queue_next);
}
BLI_LINKSTACK_FREE(loop_queue_prev);
BLI_LINKSTACK_FREE(loop_queue_next);
return face_tot;
}
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op)
{
const bool use_normals = BMO_slot_bool_get(op->slots_in, "use_normals");
const bool use_data = BMO_slot_bool_get(op->slots_in, "use_data");
int face_tot;
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
/* do inline */
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "faces", BM_FACE, BM_ELEM_TAG, false);
/* now we can copy adjacent data */
face_tot = bmesh_face_attribute_fill(bm, use_normals, use_data);
if (face_tot != BMO_slot_buffer_len(op->slots_in, "faces")) {
/* any remaining tags will be skipped */
BMO_slot_buffer_from_enabled_hflag(
bm, op, op->slots_out, "faces_fail.out", BM_FACE, BM_ELEM_TAG);
}
}
} // namespace blender

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill discrete edge loop(s) with faces.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define VERT_USED 1
#define EDGE_MARK 2
#define ELE_OUT 4
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op)
{
/* first collect an array of unique from the edges */
const int tote = BMO_slot_buffer_len(op->slots_in, "edges");
const int totv = tote; /* these should be the same */
BMVert **verts = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMVert *v;
BMEdge *e;
int i;
bool ok = true;
BMOIter oiter;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
/* 'VERT_USED' will be disabled, so enable and fill the array */
i = 0;
BMO_ITER (e, &oiter, op->slots_in, "edges", BM_EDGE) {
BMIter viter;
BMO_edge_flag_enable(bm, e, EDGE_MARK);
BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) {
if (BMO_vert_flag_test(bm, v, VERT_USED) == false) {
if (i == tote) {
goto cleanup;
}
BMO_vert_flag_enable(bm, v, VERT_USED);
verts[i++] = v;
}
}
}
/* we have a different number of verts to edges */
if (i != tote) {
goto cleanup;
}
/* loop over connected flagged edges and fill in faces, this is made slightly more
* complicated because there may be multiple disconnected loops to fill. */
/* sanity check - that each vertex has 2 edge users */
for (i = 0; i < totv; i++) {
v = verts[i];
/* count how many flagged edges this vertex uses */
if (BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, v, EDGE_MARK, true) != 2) {
ok = false;
break;
}
}
if (ok) {
/* NOTE: in the case of multiple loops, this over-allocates (which is fine). */
BMVert **f_verts = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMIter eiter;
/* build array of connected verts and edges */
BMEdge *e_prev = nullptr;
BMEdge *e_next = nullptr;
int totv_used = 0;
while (totv_used < totv) {
for (i = 0; i < totv; i++) {
v = verts[i];
if (BMO_vert_flag_test(bm, v, VERT_USED)) {
break;
}
}
/* this should never fail, as long as (totv_used < totv)
* we should have marked verts available */
BLI_assert(BMO_vert_flag_test(bm, v, VERT_USED));
/* watch it, 'i' is used for final face length */
i = 0;
do {
/* we know that there are 2 edges per vertex so no need to check */
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e, EDGE_MARK)) {
if (e != e_prev) {
e_next = e;
break;
}
}
}
/* fill in the array */
f_verts[i] = v;
BMO_vert_flag_disable(bm, v, VERT_USED);
totv_used++;
/* step over the edges */
v = BM_edge_other_vert(e_next, v);
e_prev = e_next;
i++;
} while (v != f_verts[0]);
if (!BM_face_exists(f_verts, i)) {
BMFace *f;
/* don't use calc_edges option because we already have the edges */
f = BM_face_create_ngon_verts(bm, f_verts, i, nullptr, BM_CREATE_NOP, true, false);
BMO_face_flag_enable(bm, f, ELE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
}
}
MEM_delete(f_verts);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_OUT);
}
cleanup:
MEM_delete(verts);
}
} // namespace blender

View File

@@ -0,0 +1,734 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill 2 isolated, open edge loops with a grid of quads.
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
#define EDGE_MARK 4
#define FACE_OUT 16
#define BARYCENTRIC_INTERP
#ifdef BARYCENTRIC_INTERP
/**
* 2 edge vectors to normal.
*/
static void quad_edges_to_normal(float no[3],
const float co_a1[3],
const float co_a2[3],
const float co_b1[3],
const float co_b2[3])
{
float diff_a[3];
float diff_b[3];
sub_v3_v3v3(diff_a, co_a2, co_a1);
sub_v3_v3v3(diff_b, co_b2, co_b1);
normalize_v3(diff_a);
normalize_v3(diff_b);
add_v3_v3v3(no, diff_a, diff_b);
normalize_v3(no);
}
static void quad_verts_to_barycentric_tri(float tri[3][3],
const float co_a[3],
const float co_b[3],
const float co_a_next[3],
const float co_b_next[3],
const float co_a_prev[3],
const float co_b_prev[3],
const bool is_flip)
{
float no[3];
copy_v3_v3(tri[0], co_a);
copy_v3_v3(tri[1], co_b);
quad_edges_to_normal(no, co_a, co_a_next, co_b, co_b_next);
if (co_a_prev) {
float no_t[3];
quad_edges_to_normal(no_t, co_a_prev, co_a, co_b_prev, co_b);
add_v3_v3(no, no_t);
normalize_v3(no);
}
if (is_flip) {
negate_v3(no);
}
mul_v3_fl(no, len_v3v3(tri[0], tri[1]));
mid_v3_v3v3(tri[2], tri[0], tri[1]);
add_v3_v3(tri[2], no);
}
#endif
/* -------------------------------------------------------------------- */
/** \name Handle Loop Pairs
* \{ */
/**
* Assign a loop pair from 2 verts (which _must_ share an edge)
*/
static void bm_loop_pair_from_verts(BMVert *v_a, BMVert *v_b, BMLoop *l_pair[2])
{
BMEdge *e = BM_edge_exists(v_a, v_b);
if (e->l) {
if (e->l->v == v_a) {
l_pair[0] = e->l;
l_pair[1] = e->l->next;
}
else {
l_pair[0] = e->l->next;
l_pair[1] = e->l;
}
}
else {
l_pair[0] = nullptr;
l_pair[1] = nullptr;
}
}
/**
* Copy loop pair from one side to the other if either is missing,
* this simplifies interpolation code so we only need to check if x/y are missing,
* rather than checking each loop.
*/
static void bm_loop_pair_test_copy(BMLoop *l_pair_a[2], BMLoop *l_pair_b[2])
{
/* if the first one is set, we know the second is too */
if (l_pair_a[0] && l_pair_b[0] == nullptr) {
l_pair_b[0] = l_pair_a[1];
l_pair_b[1] = l_pair_a[0];
}
else if (l_pair_b[0] && l_pair_a[0] == nullptr) {
l_pair_a[0] = l_pair_b[1];
l_pair_a[1] = l_pair_b[0];
}
}
/**
* Interpolate from boundary loops.
*
* \note These weights will be calculated multiple times per vertex.
*/
static void bm_loop_interp_from_grid_boundary_4(BMesh *bm,
BMLoop *l,
BMLoop *l_bound[4],
const float w[4])
{
const void *l_cdata[4] = {
l_bound[0]->head.data, l_bound[1]->head.data, l_bound[2]->head.data, l_bound[3]->head.data};
CustomData_bmesh_interp(&bm->ldata, l_cdata, w, 4, l->head.data);
}
static void bm_loop_interp_from_grid_boundary_2(BMesh *bm,
BMLoop *l,
BMLoop *l_bound[2],
const float t)
{
const void *l_cdata[2] = {l_bound[0]->head.data, l_bound[1]->head.data};
const float w[2] = {1.0f - t, t};
CustomData_bmesh_interp(&bm->ldata, l_cdata, w, 2, l->head.data);
}
/** \} */
/**
* Avoids calling #barycentric_weights_v2_quad often by caching weights into an array.
*/
static void barycentric_weights_v2_grid_cache(const uint xtot,
const uint ytot,
float (*weight_table)[4])
{
float x_step = 1.0f / float(xtot - 1);
float y_step = 1.0f / float(ytot - 1);
uint i = 0;
float xy_fl[2];
uint x, y;
for (y = 0; y < ytot; y++) {
xy_fl[1] = y_step * float(y);
for (x = 0; x < xtot; x++) {
xy_fl[0] = x_step * float(x);
{
const float cos[4][2] = {
{xy_fl[0], 0.0f}, {0.0f, xy_fl[1]}, {xy_fl[0], 1.0f}, {1.0f, xy_fl[1]}};
barycentric_weights_v2_quad(UNPACK4(cos), xy_fl, weight_table[i++]);
}
}
}
}
/**
* This may be useful outside the bmesh operator.
*
* \param v_grid: 2d array of verts, all boundary verts must be set, we fill in the middle.
*/
static void bm_grid_fill_array(BMesh *bm,
BMVert **v_grid,
const uint xtot,
const uint ytot,
const short mat_nr,
const bool use_smooth,
const bool use_flip,
const bool use_interp_simple)
{
const bool use_vert_interp = CustomData_has_interp(&bm->vdata);
const bool use_loop_interp = CustomData_has_interp(&bm->ldata);
uint x, y;
/* for use_loop_interp */
BMLoop *(*larr_x_a)[2], *(*larr_x_b)[2], *(*larr_y_a)[2], *(*larr_y_b)[2];
float (*weight_table)[4];
#define XY(_x, _y) ((_x) + ((_y) * (xtot)))
#ifdef BARYCENTRIC_INTERP
float tri_a[3][3];
float tri_b[3][3];
float tri_t[3][3]; /* temp */
quad_verts_to_barycentric_tri(tri_a,
v_grid[XY(0, 0)]->co,
v_grid[XY(xtot - 1, 0)]->co,
v_grid[XY(0, 1)]->co,
v_grid[XY(xtot - 1, 1)]->co,
nullptr,
nullptr,
false);
quad_verts_to_barycentric_tri(tri_b,
v_grid[XY(0, (ytot - 1))]->co,
v_grid[XY(xtot - 1, (ytot - 1))]->co,
v_grid[XY(0, (ytot - 2))]->co,
v_grid[XY(xtot - 1, (ytot - 2))]->co,
nullptr,
nullptr,
true);
#endif
if (use_interp_simple || use_vert_interp || use_loop_interp) {
weight_table = MEM_new_array_uninitialized<float[4]>(xtot * ytot, __func__);
barycentric_weights_v2_grid_cache(xtot, ytot, weight_table);
}
else {
weight_table = nullptr;
}
/* Store loops */
if (use_loop_interp) {
/* x2 because each edge connects 2 loops */
larr_x_a = MEM_new_array_uninitialized<BMLoop *[2]>((xtot - 1), __func__);
larr_x_b = MEM_new_array_uninitialized<BMLoop *[2]>((xtot - 1), __func__);
larr_y_a = MEM_new_array_uninitialized<BMLoop *[2]>((ytot - 1), __func__);
larr_y_b = MEM_new_array_uninitialized<BMLoop *[2]>((ytot - 1), __func__);
/* fill in the loops */
for (x = 0; x < xtot - 1; x++) {
bm_loop_pair_from_verts(v_grid[XY(x, 0)], v_grid[XY(x + 1, 0)], larr_x_a[x]);
bm_loop_pair_from_verts(v_grid[XY(x, ytot - 1)], v_grid[XY(x + 1, ytot - 1)], larr_x_b[x]);
bm_loop_pair_test_copy(larr_x_a[x], larr_x_b[x]);
}
for (y = 0; y < ytot - 1; y++) {
bm_loop_pair_from_verts(v_grid[XY(0, y)], v_grid[XY(0, y + 1)], larr_y_a[y]);
bm_loop_pair_from_verts(v_grid[XY(xtot - 1, y)], v_grid[XY(xtot - 1, y + 1)], larr_y_b[y]);
bm_loop_pair_test_copy(larr_y_a[y], larr_y_b[y]);
}
}
/* Build Verts */
for (y = 1; y < ytot - 1; y++) {
#ifdef BARYCENTRIC_INTERP
quad_verts_to_barycentric_tri(tri_t,
v_grid[XY(0, y + 0)]->co,
v_grid[XY(xtot - 1, y + 0)]->co,
v_grid[XY(0, y + 1)]->co,
v_grid[XY(xtot - 1, y + 1)]->co,
v_grid[XY(0, y - 1)]->co,
v_grid[XY(xtot - 1, y - 1)]->co,
false);
#endif
for (x = 1; x < xtot - 1; x++) {
float co[3];
BMVert *v;
/* we may want to allow sparse filled arrays, but for now, ensure its empty */
BLI_assert(v_grid[(y * xtot) + x] == nullptr);
/* place the vertex */
#ifdef BARYCENTRIC_INTERP
if (use_interp_simple == false) {
float co_a[3], co_b[3];
transform_point_by_tri_v3(
co_a, v_grid[x]->co, tri_t[0], tri_t[1], tri_t[2], tri_a[0], tri_a[1], tri_a[2]);
transform_point_by_tri_v3(co_b,
v_grid[(xtot * ytot) + (x - xtot)]->co,
tri_t[0],
tri_t[1],
tri_t[2],
tri_b[0],
tri_b[1],
tri_b[2]);
interp_v3_v3v3(co, co_a, co_b, float(y) / (float(ytot) - 1));
}
else
#endif
{
const float *w = weight_table[XY(x, y)];
zero_v3(co);
madd_v3_v3fl(co, v_grid[XY(x, 0)]->co, w[0]);
madd_v3_v3fl(co, v_grid[XY(0, y)]->co, w[1]);
madd_v3_v3fl(co, v_grid[XY(x, ytot - 1)]->co, w[2]);
madd_v3_v3fl(co, v_grid[XY(xtot - 1, y)]->co, w[3]);
}
v = BM_vert_create(bm, co, nullptr, BM_CREATE_NOP);
v_grid[(y * xtot) + x] = v;
/* Interpolate only along one axis, this could be changed
* but from user POV gives predictable results since these are selected loop. */
if (use_vert_interp) {
const float *w = weight_table[XY(x, y)];
const void *v_cdata[4] = {
v_grid[XY(x, 0)]->head.data,
v_grid[XY(0, y)]->head.data,
v_grid[XY(x, ytot - 1)]->head.data,
v_grid[XY(xtot - 1, y)]->head.data,
};
CustomData_bmesh_interp(&bm->vdata, v_cdata, w, 4, v->head.data);
}
}
}
/* Build Faces */
for (x = 0; x < xtot - 1; x++) {
for (y = 0; y < ytot - 1; y++) {
BMFace *f;
if (use_flip) {
f = BM_face_create_quad_tri(bm,
v_grid[XY(x, y + 0)], /* BL */
v_grid[XY(x, y + 1)], /* TL */
v_grid[XY(x + 1, y + 1)], /* TR */
v_grid[XY(x + 1, y + 0)], /* BR */
nullptr,
BM_CREATE_NOP);
}
else {
f = BM_face_create_quad_tri(bm,
v_grid[XY(x + 1, y + 0)], /* BR */
v_grid[XY(x + 1, y + 1)], /* TR */
v_grid[XY(x, y + 1)], /* TL */
v_grid[XY(x, y + 0)], /* BL */
nullptr,
BM_CREATE_NOP);
}
if (use_loop_interp && (larr_x_a[x][0] || larr_y_a[y][0])) {
/* bottom/left/top/right */
BMLoop *l_quad[4];
BMLoop *l_bound[4];
BMLoop *l_tmp;
uint x_side, y_side, i;
char interp_from;
if (larr_x_a[x][0] && larr_y_a[y][0]) {
interp_from = 'B'; /* B == both */
l_tmp = larr_x_a[x][0];
}
else if (larr_x_a[x][0]) {
interp_from = 'X';
l_tmp = larr_x_a[x][0];
}
else {
interp_from = 'Y';
l_tmp = larr_y_a[y][0];
}
BM_elem_attrs_copy(bm, l_tmp->f, f);
BM_face_as_array_loop_quad(f, l_quad);
l_tmp = BM_FACE_FIRST_LOOP(f);
if (use_flip) {
l_quad[0] = l_tmp;
l_tmp = l_tmp->next;
l_quad[1] = l_tmp;
l_tmp = l_tmp->next;
l_quad[3] = l_tmp;
l_tmp = l_tmp->next;
l_quad[2] = l_tmp;
}
else {
l_quad[2] = l_tmp;
l_tmp = l_tmp->next;
l_quad[3] = l_tmp;
l_tmp = l_tmp->next;
l_quad[1] = l_tmp;
l_tmp = l_tmp->next;
l_quad[0] = l_tmp;
}
i = 0;
for (x_side = 0; x_side < 2; x_side++) {
for (y_side = 0; y_side < 2; y_side++) {
if (interp_from == 'B') {
const float *w = weight_table[XY(x + x_side, y + y_side)];
l_bound[0] = larr_x_a[x][x_side]; /* B */
l_bound[1] = larr_y_a[y][y_side]; /* L */
l_bound[2] = larr_x_b[x][x_side]; /* T */
l_bound[3] = larr_y_b[y][y_side]; /* R */
bm_loop_interp_from_grid_boundary_4(bm, l_quad[i++], l_bound, w);
}
else if (interp_from == 'X') {
const float t = float(y + y_side) / float(ytot - 1);
l_bound[0] = larr_x_a[x][x_side]; /* B */
l_bound[1] = larr_x_b[x][x_side]; /* T */
bm_loop_interp_from_grid_boundary_2(bm, l_quad[i++], l_bound, t);
}
else if (interp_from == 'Y') {
const float t = float(x + x_side) / float(xtot - 1);
l_bound[0] = larr_y_a[y][y_side]; /* L */
l_bound[1] = larr_y_b[y][y_side]; /* R */
bm_loop_interp_from_grid_boundary_2(bm, l_quad[i++], l_bound, t);
}
else {
BLI_assert(0);
}
}
}
}
/* end interp */
BMO_face_flag_enable(bm, f, FACE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
}
}
if (use_loop_interp) {
MEM_delete(larr_x_a);
MEM_delete(larr_y_a);
MEM_delete(larr_x_b);
MEM_delete(larr_y_b);
}
if (weight_table) {
MEM_delete(weight_table);
}
#undef XY
}
static void bm_grid_fill(BMesh *bm,
BMEdgeLoopStore *estore_a,
BMEdgeLoopStore *estore_b,
BMEdgeLoopStore *estore_rail_a,
BMEdgeLoopStore *estore_rail_b,
const short mat_nr,
const bool use_smooth,
const bool use_interp_simple)
{
#define USE_FLIP_DETECT
const uint xtot = uint(BM_edgeloop_length_get(estore_a));
const uint ytot = uint(BM_edgeloop_length_get(estore_rail_a));
// BMVert *v;
uint i;
#ifndef NDEBUG
uint x, y;
#endif
LinkData *el;
bool use_flip = false;
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(estore_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(estore_b);
ListBaseT<LinkData> *lb_rail_a = BM_edgeloop_verts_get(estore_rail_a);
ListBaseT<LinkData> *lb_rail_b = BM_edgeloop_verts_get(estore_rail_b);
BMVert **v_grid = MEM_new_array_zeroed<BMVert *>(size_t(xtot * ytot), __func__);
/**
* <pre>
* estore_b
* +------------------+
* ^ | |
* end | | |
* | | |
* | |estore_rail_a |estore_rail_b
* | | |
* start | | |
* |estore_a |
* +------------------+
* --->
* start -> end
* </pre>
*/
BLI_assert(((LinkData *)lb_a->first)->data == ((LinkData *)lb_rail_a->first)->data); /* BL */
BLI_assert(((LinkData *)lb_b->first)->data == ((LinkData *)lb_rail_a->last)->data); /* TL */
BLI_assert(((LinkData *)lb_b->last)->data == ((LinkData *)lb_rail_b->last)->data); /* TR */
BLI_assert(((LinkData *)lb_a->last)->data == ((LinkData *)lb_rail_b->first)->data); /* BR */
for (el = static_cast<LinkData *>(lb_a->first), i = 0; el; el = el->next, i++) {
v_grid[i] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_b->first), i = 0; el; el = el->next, i++) {
v_grid[(ytot * xtot) + (i - xtot)] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_rail_a->first), i = 0; el; el = el->next, i++) {
v_grid[xtot * i] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_rail_b->first), i = 0; el; el = el->next, i++) {
v_grid[(xtot * i) + (xtot - 1)] = static_cast<BMVert *>(el->data);
}
#ifndef NDEBUG
for (x = 1; x < xtot - 1; x++) {
for (y = 1; y < ytot - 1; y++) {
BLI_assert(v_grid[(y * xtot) + x] == nullptr);
}
}
#endif
#ifdef USE_FLIP_DETECT
{
ListBaseT<LinkData> *lb_iter[4] = {lb_a, lb_b, lb_rail_a, lb_rail_b};
const int lb_iter_dir[4] = {-1, 1, 1, -1};
int winding_votes = 0;
for (i = 0; i < 4; i++) {
LinkData *el_next;
for (el = static_cast<LinkData *>(lb_iter[i]->first); el && (el_next = el->next);
el = el->next)
{
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el->data),
static_cast<BMVert *>(el_next->data));
if (BM_edge_is_boundary(e)) {
winding_votes += (e->l->v == el->data) ? lb_iter_dir[i] : -lb_iter_dir[i];
}
}
}
use_flip = (winding_votes < 0);
}
#endif
bm_grid_fill_array(bm, v_grid, xtot, ytot, mat_nr, use_smooth, use_flip, use_interp_simple);
MEM_delete(v_grid);
#undef USE_FLIP_DETECT
}
static void bm_edgeloop_flag_set(BMEdgeLoopStore *estore, char hflag, bool set)
{
/* only handle closed loops in this case */
LinkData *link = static_cast<LinkData *>(BM_edgeloop_verts_get(estore)->first);
link = link->next;
while (link) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(link->data),
static_cast<BMVert *>(link->prev->data));
if (e) {
BM_elem_flag_set(e, hflag, set);
}
link = link->next;
}
}
static bool bm_edge_test_cb(BMEdge *e, void *bm_v)
{
return BMO_edge_flag_test_bool((BMesh *)bm_v, e, EDGE_MARK);
}
static bool bm_edge_test_rail_cb(BMEdge *e, void * /*bm_v*/)
{
/* Normally operators don't check for hidden state
* but alternative would be to pass slot of rail edges. */
if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
return false;
}
return BM_edge_is_wire(e) || BM_edge_is_boundary(e);
}
void bmo_grid_fill_exec(BMesh *bm, BMOperator *op)
{
ListBaseT<BMEdgeLoopStore> eloops = {nullptr, nullptr};
ListBaseT<BMEdgeLoopStore> eloops_rail = {nullptr, nullptr};
BMEdgeLoopStore *estore_a, *estore_b;
BMEdgeLoopStore *estore_rail_a, *estore_rail_b;
BMVert *v_a_first, *v_a_last;
BMVert *v_b_first, *v_b_last;
const short mat_nr = short(BMO_slot_int_get(op->slots_in, "mat_nr"));
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
const bool use_interp_simple = BMO_slot_bool_get(op->slots_in, "use_interp_simple");
std::unique_ptr<Set<BMEdge *>> split_edges;
int count;
bool changed = false;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_cb, static_cast<void *>(bm));
if (count != 2) {
/* Note that this error message has been adjusted to make sense when called
* from the operator `MESH_OT_fill_grid` which has a 'prepare' pass which can
* extract two 'rail' loops from a single edge loop, see #72075. */
BMO_error_raise(bm,
op,
BMO_ERROR_CANCEL,
"Select two edge loops "
"or a single closed edge loop from which two edge loops can be calculated");
goto cleanup;
}
estore_a = static_cast<BMEdgeLoopStore *>(eloops.first);
estore_b = static_cast<BMEdgeLoopStore *>(eloops.last);
v_a_first = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_a)->first))->data);
v_a_last = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_a)->last))->data);
v_b_first = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_b)->first))->data);
v_b_last = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_b)->last))->data);
if (BM_edgeloop_is_closed(estore_a) || BM_edgeloop_is_closed(estore_b)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Closed loops unsupported");
goto cleanup;
}
/* ok. all error checking done, now we can find the rail edges */
/* cheat here, temp hide all edges so they won't be included in rails
* this puts the mesh in an invalid state for a short time. */
bm_edgeloop_flag_set(estore_a, BM_ELEM_HIDDEN, true);
bm_edgeloop_flag_set(estore_b, BM_ELEM_HIDDEN, true);
if (BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_first, v_b_first) &&
BM_mesh_edgeloops_find_path(bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_last, v_b_last))
{
estore_rail_a = static_cast<BMEdgeLoopStore *>(eloops_rail.first);
estore_rail_b = static_cast<BMEdgeLoopStore *>(eloops_rail.last);
}
else {
BM_mesh_edgeloops_free(&eloops_rail);
if (BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_first, v_b_last) &&
BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_last, v_b_first))
{
estore_rail_a = static_cast<BMEdgeLoopStore *>(eloops_rail.first);
estore_rail_b = static_cast<BMEdgeLoopStore *>(eloops_rail.last);
BM_edgeloop_flip(bm, estore_b);
}
else {
BM_mesh_edgeloops_free(&eloops_rail);
}
}
bm_edgeloop_flag_set(estore_a, BM_ELEM_HIDDEN, false);
bm_edgeloop_flag_set(estore_b, BM_ELEM_HIDDEN, false);
if (eloops_rail.is_empty()) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Loops are not connected by wire/boundary edges");
goto cleanup;
}
BLI_assert(estore_a != estore_b);
BLI_assert(v_a_last != v_b_last);
if (BM_edgeloop_overlap_check(estore_rail_a, estore_rail_b)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Connecting edge loops overlap");
goto cleanup;
}
/* add vertices if needed */
{
BMEdgeLoopStore *estore_pairs[2][2] = {
{estore_a, estore_b},
{estore_rail_a, estore_rail_b},
};
int i;
for (i = 0; i < 2; i++) {
const int len_a = BM_edgeloop_length_get(estore_pairs[i][0]);
const int len_b = BM_edgeloop_length_get(estore_pairs[i][1]);
if (len_a != len_b) {
if (split_edges == nullptr) {
split_edges = std::make_unique<Set<BMEdge *>>();
}
if (len_a < len_b) {
BM_edgeloop_expand(bm, estore_pairs[i][0], len_b, true, split_edges.get());
}
else {
BM_edgeloop_expand(bm, estore_pairs[i][1], len_a, true, split_edges.get());
}
}
}
}
/* finally we have all edge loops needed */
bm_grid_fill(
bm, estore_a, estore_b, estore_rail_a, estore_rail_b, mat_nr, use_smooth, use_interp_simple);
changed = true;
if (split_edges) {
for (BMEdge *e : *split_edges) {
BM_edge_collapse(bm, e, e->v2, true, true);
}
}
cleanup:
BM_mesh_edgeloops_free(&eloops);
BM_mesh_edgeloops_free(&eloops_rail);
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
}
} // namespace blender

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill boundary edge loop(s) with faces.
*/
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_holes_fill_exec(BMesh *bm, BMOperator *op)
{
BMOperator op_attr;
const uint sides = BMO_slot_int_get(op->slots_in, "sides");
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "edges", BM_EDGE, BM_ELEM_TAG, false);
BM_mesh_edgenet(bm, true, true); /* TODO: sides. */
/* bad - remove faces after as a workaround */
if (sides != 0) {
BMOIter siter;
BMFace *f;
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_ITER (f, &siter, op->slots_out, "faces.out", BM_FACE) {
if (f->len > sides) {
BM_face_kill(bm, f);
}
}
}
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
/* --- Attribute Fill --- */
/* may as well since we have the faces already in a buffer */
BMO_op_initf(bm,
&op_attr,
op->flag,
"face_attribute_fill faces=%S use_normals=%b use_data=%b",
op,
"faces.out",
true,
true);
BMO_op_exec(bm, &op_attr);
/* check if some faces couldn't be touched */
if (BMO_slot_buffer_len(op_attr.slots_out, "faces_fail.out")) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_attr.slots_out, "faces_fail.out", BM_FACE) {
BM_face_normal_update(f); /* Normals are zeroed. */
}
BMO_op_callf(bm, op->flag, "recalc_face_normals faces=%S", &op_attr, "faces_fail.out");
}
BMO_op_finish(bm, &op_attr);
}
} // namespace blender

View File

@@ -0,0 +1,128 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Flattens vertices on a best-fitting plane.
*/
#include "BLI_array.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
static float3 compute_centroid(Span<BMVert *> verts)
{
float3 center(0.0f);
for (BMVert *v : verts) {
center += float3(v->co);
}
center /= float(verts.size());
return center;
}
static float3 compute_average_face_normal(Span<BMFace *> faces)
{
float3 normal(0.0f);
for (BMFace *f : faces) {
normal += float3(f->no) * BM_face_calc_area(f);
}
return (normalize_v3(normal) != 0.0f) ? normal : float3(0.0f, 0.0f, 1.0f);
}
static Vector<BMVert *> collect_verts_from_faces(Span<BMFace *> faces)
{
VectorSet<BMVert *> verts;
for (BMFace *f : faces) {
BMIter viter;
BMVert *v;
BM_ITER_ELEM (v, &viter, f, BM_VERTS_OF_FACE) {
verts.add(v);
}
}
return verts.extract_vector();
}
void bmo_flatten_exec(BMesh *bm, BMOperator *op)
{
const float factor = BMO_slot_float_get(op->slots_in, "factor");
const FlattenMethod method = static_cast<FlattenMethod>(
BMO_slot_int_get(op->slots_in, "method"));
const bool lock_x = BMO_slot_bool_get(op->slots_in, "lock_x");
const bool lock_y = BMO_slot_bool_get(op->slots_in, "lock_y");
const bool lock_z = BMO_slot_bool_get(op->slots_in, "lock_z");
float3 view_direction(0.0f, 0.0f, 1.0f);
if (method == FLATTEN_VIEW) {
BMO_slot_vec_get(op->slots_in, "view_normal", view_direction);
}
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "geom", BM_FACE, BM_ELEM_TAG, false);
Array<int> groups_array(bm->totface);
int (*group_index)[2];
const int group_num = BM_mesh_calc_face_groups(
bm, groups_array.data(), &group_index, nullptr, nullptr, nullptr, BM_ELEM_TAG, BM_EDGE);
BM_mesh_elem_table_ensure(bm, BM_FACE);
for (const int g : IndexRange(group_num)) {
const int start = group_index[g][0];
const int length = group_index[g][1];
Vector<BMFace *> faces;
faces.reserve(length);
for (const int i : IndexRange(start, length)) {
faces.append(BM_face_at_index(bm, groups_array[i]));
}
Vector<BMVert *> verts = collect_verts_from_faces(faces);
float3 center;
float3 normal(0.0f);
switch (method) {
case FLATTEN_BEST_FIT:
BM_verts_calc_normal_from_cloud_ex(
verts.data(), int(verts.size()), normal, center, nullptr);
break;
case FLATTEN_NORMAL:
normal = compute_average_face_normal(faces);
center = compute_centroid(verts);
break;
case FLATTEN_VIEW:
normal = view_direction;
center = compute_centroid(verts);
break;
}
BLI_assert(!math::is_zero(normal));
for (BMVert *v : verts) {
float3 co(v->co);
float3 projected = co - math::dot(co - center, normal) * normal;
if (lock_x) {
projected.x = co.x;
}
if (lock_y) {
projected.y = co.y;
}
if (lock_z) {
projected.z = co.z;
}
float3 co_final = math::interpolate(co, projected, factor);
copy_v3_v3(v->co, co_final);
}
}
MEM_delete(group_index);
}
} // namespace blender

View File

@@ -0,0 +1,606 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Create a convex hull using bullet physics library.
*/
#ifdef WITH_BULLET
# include "MEM_guardedalloc.h"
# include "BLI_listbase.h"
# include "BLI_math_geom.h"
# include "BLI_vector.hh"
# include "RBI_hull_api.h"
/* XXX: using 128 for totelem and `pchunk` of `mempool`, no idea what good
* values would be though */
# include "bmesh.hh"
# include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/* Internal operator flags */
enum {
HULL_FLAG_INPUT = (1 << 0),
HULL_FLAG_INTERIOR_ELE = (1 << 1),
HULL_FLAG_OUTPUT_GEOM = (1 << 2),
HULL_FLAG_DEL = (1 << 3),
HULL_FLAG_HOLE = (1 << 4),
};
/* Store hull triangles separate from BMesh faces until the end; this
* way we don't have to worry about cleaning up extraneous edges or
* incorrectly deleting existing geometry. */
struct HullTriangle {
BMVert *v[3];
float no[3];
int skip;
};
/*************************** Hull Triangles ***************************/
static void hull_add_triangle(
BMesh *bm, BLI_mempool *hull_triangles, BMVert *v1, BMVert *v2, BMVert *v3)
{
HullTriangle *t;
int i;
t = static_cast<HullTriangle *>(BLI_mempool_calloc(hull_triangles));
t->v[0] = v1;
t->v[1] = v2;
t->v[2] = v3;
/* Mark triangles vertices as not interior */
for (i = 0; i < 3; i++) {
BMO_vert_flag_disable(bm, t->v[i], HULL_FLAG_INTERIOR_ELE);
}
normal_tri_v3(t->no, v1->co, v2->co, v3->co);
}
static BMFace *hull_find_example_face(BMesh *bm, BMEdge *e)
{
BMIter iter;
BMFace *f;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_INPUT) ||
BMO_face_flag_test(bm, f, HULL_FLAG_OUTPUT_GEOM) == false)
{
return f;
}
}
return nullptr;
}
static void hull_output_triangles(BMesh *bm, BLI_mempool *hull_triangles)
{
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
int i;
if (!t->skip) {
BMEdge *edges[3] = {
BM_edge_create(bm, t->v[0], t->v[1], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, t->v[1], t->v[2], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, t->v[2], t->v[0], nullptr, BM_CREATE_NO_DOUBLE),
};
BMFace *f, *example = nullptr;
f = BM_face_exists(t->v, 3);
if (f != nullptr) {
/* If the operator is run with "use_existing_faces"
* disabled, but an output face in the hull is the
* same as a face in the existing mesh, it should not
* be marked as unused or interior. */
BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
}
else {
/* Look for an adjacent face that existed before the hull */
for (i = 0; i < 3; i++) {
if (!example) {
example = hull_find_example_face(bm, edges[i]);
}
}
/* Create new hull face */
f = BM_face_create_verts(bm, t->v, 3, example, BM_CREATE_NO_DOUBLE, true);
BM_face_copy_shared(bm, f, nullptr, nullptr);
}
/* Mark face for 'geom.out' slot and select */
BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
BM_face_select_set(bm, f, true);
/* Mark edges for 'geom.out' slot */
for (i = 0; i < 3; i++) {
BMO_edge_flag_enable(bm, edges[i], HULL_FLAG_OUTPUT_GEOM);
}
}
else {
/* Mark input edges for 'geom.out' slot */
for (i = 0; i < 3; i++) {
const int next = (i == 2 ? 0 : i + 1);
BMEdge *e = BM_edge_exists(t->v[i], t->v[next]);
if (e && BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT) &&
!BMO_edge_flag_test(bm, e, HULL_FLAG_HOLE))
{
BMO_edge_flag_enable(bm, e, HULL_FLAG_OUTPUT_GEOM);
}
}
}
/* Mark verts for 'geom.out' slot */
for (i = 0; i < 3; i++) {
BMO_vert_flag_enable(bm, t->v[i], HULL_FLAG_OUTPUT_GEOM);
}
}
}
/***************************** Final Edges ****************************/
struct HullFinalEdges {
Map<BMVert *, ListBaseT<LinkData> *> *edges;
BLI_mempool *base_pool, *link_pool;
};
static LinkData *final_edges_find_link(ListBaseT<LinkData> *adj, BMVert *v)
{
for (LinkData &link : *adj) {
if (link.data == v) {
return &link;
}
}
return nullptr;
}
static int hull_final_edges_lookup(HullFinalEdges *final_edges, BMVert *v1, BMVert *v2)
{
ListBaseT<LinkData> *adj;
/* Use lower vertex pointer for hash key */
if (v1 > v2) {
std::swap(v1, v2);
}
adj = final_edges->edges->lookup_default(v1, nullptr);
if (!adj) {
return false;
}
return !!final_edges_find_link(adj, v2);
}
/* Used for checking whether a pre-existing edge lies on the hull */
static HullFinalEdges *hull_final_edges(BLI_mempool *hull_triangles)
{
HullFinalEdges *final_edges;
final_edges = MEM_new_zeroed<HullFinalEdges>("HullFinalEdges");
final_edges->edges = MEM_new<Map<BMVert *, ListBaseT<LinkData> *>>("final edges map");
final_edges->base_pool = BLI_mempool_create(
sizeof(ListBaseT<LinkData>), 0, 128, BLI_MEMPOOL_NOP);
final_edges->link_pool = BLI_mempool_create(sizeof(LinkData), 0, 128, BLI_MEMPOOL_NOP);
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
LinkData *link;
int i;
for (i = 0; i < 3; i++) {
BMVert *v1 = t->v[i];
BMVert *v2 = t->v[(i + 1) % 3];
/* Use lower vertex pointer for hash key */
if (v1 > v2) {
std::swap(v1, v2);
}
ListBaseT<LinkData> *adj = final_edges->edges->lookup_or_add_cb(v1, [&]() {
return static_cast<ListBaseT<LinkData> *>(BLI_mempool_calloc(final_edges->base_pool));
});
if (!final_edges_find_link(adj, v2)) {
link = static_cast<LinkData *>(BLI_mempool_calloc(final_edges->link_pool));
link->data = v2;
BLI_addtail(adj, link);
}
}
}
return final_edges;
}
static void hull_final_edges_free(HullFinalEdges *final_edges)
{
MEM_delete(final_edges->edges);
BLI_mempool_destroy(final_edges->base_pool);
BLI_mempool_destroy(final_edges->link_pool);
MEM_delete(final_edges);
}
/**************************** Final Output ****************************/
static void hull_remove_overlapping(BMesh *bm,
BLI_mempool *hull_triangles,
HullFinalEdges *final_edges)
{
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
BMIter bm_iter1, bm_iter2;
BMFace *f;
bool f_on_hull;
BM_ITER_ELEM (f, &bm_iter1, t->v[0], BM_FACES_OF_VERT) {
BMEdge *e;
/* Check that all the face's edges are on the hull,
* otherwise can't reuse it */
f_on_hull = true;
BM_ITER_ELEM (e, &bm_iter2, f, BM_EDGES_OF_FACE) {
if (!hull_final_edges_lookup(final_edges, e->v1, e->v2)) {
f_on_hull = false;
break;
}
}
/* NOTE: can't change ghash while iterating, so mark
* with 'skip' flag rather than deleting triangles */
if (BM_vert_in_face(t->v[1], f) && BM_vert_in_face(t->v[2], f) && f_on_hull) {
t->skip = true;
BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
BMO_face_flag_enable(bm, f, HULL_FLAG_HOLE);
}
}
}
}
static void hull_mark_interior_elements(BMesh *bm, BMOperator *op, HullFinalEdges *final_edges)
{
BMEdge *e;
BMFace *f;
BMOIter oiter;
/* Check for interior edges too */
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
if (!hull_final_edges_lookup(final_edges, e->v1, e->v2)) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_INTERIOR_ELE);
}
}
/* Mark all input faces as interior, some may be unmarked in
* hull_remove_overlapping() */
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
BMO_face_flag_enable(bm, f, HULL_FLAG_INTERIOR_ELE);
}
}
static void hull_tag_unused(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter oiter;
BMVert *v;
BMEdge *e;
BMFace *f;
/* Mark vertices, edges, and faces that are already marked
* interior (i.e. were already part of the input, but not part of
* the hull), but that aren't also used by elements outside the
* input set */
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
if (BMO_vert_flag_test(bm, v, HULL_FLAG_INTERIOR_ELE)) {
bool del = true;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
if (del) {
BMO_vert_flag_enable(bm, v, HULL_FLAG_DEL);
}
}
}
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
if (BMO_edge_flag_test(bm, e, HULL_FLAG_INTERIOR_ELE)) {
bool del = true;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
if (del) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_DEL);
}
}
}
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_INTERIOR_ELE)) {
BMO_face_flag_enable(bm, f, HULL_FLAG_DEL);
}
}
}
static void hull_tag_holes(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter oiter;
BMFace *f;
BMEdge *e;
/* Unmark any hole faces if they are isolated or part of a
* border */
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) {
if (BM_edge_is_boundary(e)) {
BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
break;
}
}
}
}
/* Mark edges too if all adjacent faces are holes and the edge is
* not already isolated */
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
bool hole = true;
bool any_faces = false;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
any_faces = true;
if (!BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
hole = false;
break;
}
}
if (hole && any_faces) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_HOLE);
}
}
}
static int hull_input_vert_count(BMOperator *op)
{
BMOIter oiter;
BMVert *v;
int count = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
count++;
}
return count;
}
static BMVert **hull_input_verts_copy(BMOperator *op, const int num_input_verts)
{
BMOIter oiter;
BMVert *v;
BMVert **input_verts = MEM_new_array_zeroed<BMVert *>(num_input_verts, AT);
int i = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
input_verts[i++] = v;
}
return input_verts;
}
static float (*hull_verts_for_bullet(BMVert **input_verts, const int num_input_verts))[3]
{
float (*coords)[3] = MEM_new_array_zeroed<float[3]>(num_input_verts, __func__);
int i;
for (i = 0; i < num_input_verts; i++) {
copy_v3_v3(coords[i], input_verts[i]->co);
}
return coords;
}
static BMVert **hull_verts_from_bullet(plConvexHull hull,
BMVert **input_verts,
const int num_input_verts)
{
const int num_verts = plConvexHullNumVertices(hull);
BMVert **hull_verts = MEM_new_array_uninitialized<BMVert *>(num_verts, AT);
int i;
for (i = 0; i < num_verts; i++) {
float co[3];
int original_index;
plConvexHullGetVertex(hull, i, co, &original_index);
if (original_index >= 0 && original_index < num_input_verts) {
hull_verts[i] = input_verts[original_index];
}
else {
BLI_assert_msg(0, "Unexpected new vertex in hull output");
}
}
return hull_verts;
}
static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triangles)
{
BMVert **input_verts;
float (*coords)[3];
BMVert **hull_verts;
plConvexHull hull;
int i, count = 0;
const int num_input_verts = hull_input_vert_count(op);
input_verts = hull_input_verts_copy(op, num_input_verts);
coords = hull_verts_for_bullet(input_verts, num_input_verts);
hull = plConvexHullCompute(coords, num_input_verts);
hull_verts = hull_verts_from_bullet(hull, input_verts, num_input_verts);
count = plConvexHullNumFaces(hull);
Vector<int> fvi;
for (i = 0; i < count; i++) {
const int len = plConvexHullGetFaceSize(hull, i);
if (len > 2) {
BMVert *fv[3];
int j;
/* Get face vertex indices */
fvi.reinitialize(len);
plConvexHullGetFaceVertices(hull, i, fvi.data());
/* NOTE: here we throw away any NGons from Bullet and turn
* them into triangle fans. Would be nice to use these
* directly, but will have to wait until HullTriangle goes
* away (TODO) */
fv[0] = hull_verts[fvi[0]];
for (j = 2; j < len; j++) {
fv[1] = hull_verts[fvi[j - 1]];
fv[2] = hull_verts[fvi[j]];
hull_add_triangle(bm, hull_triangles, fv[0], fv[1], fv[2]);
}
}
}
plConvexHullDelete(hull);
MEM_delete(hull_verts);
MEM_delete(coords);
MEM_delete(input_verts);
}
/* Check that there are at least three vertices in the input */
static bool hull_num_input_verts_is_ok(BMOperator *op)
{
BMOIter oiter;
BMVert *v;
int partial_num_verts = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
partial_num_verts++;
if (partial_num_verts >= 3) {
break;
}
}
return (partial_num_verts >= 3);
}
void bmo_convex_hull_exec(BMesh *bm, BMOperator *op)
{
HullFinalEdges *final_edges;
BLI_mempool *hull_triangles;
BMElemF *ele;
BMOIter oiter;
/* Verify that at least three verts in the input */
if (!hull_num_input_verts_is_ok(op)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Requires at least three vertices");
return;
}
/* Tag input elements */
BMO_ITER (ele, &oiter, op->slots_in, "input", BM_ALL) {
/* Mark all vertices as interior to begin with */
if (ele->head.htype == BM_VERT) {
BMO_vert_flag_enable(bm, (BMVert *)ele, HULL_FLAG_INPUT | HULL_FLAG_INTERIOR_ELE);
}
else if (ele->head.htype == BM_EDGE) {
BMO_edge_flag_enable(bm, (BMEdge *)ele, HULL_FLAG_INPUT);
}
else {
BMO_face_flag_enable(bm, (BMFace *)ele, HULL_FLAG_INPUT);
}
}
hull_triangles = BLI_mempool_create(sizeof(HullTriangle), 0, 128, BLI_MEMPOOL_ALLOW_ITER);
hull_from_bullet(bm, op, hull_triangles);
final_edges = hull_final_edges(hull_triangles);
hull_mark_interior_elements(bm, op, final_edges);
/* Remove hull triangles covered by an existing face */
if (BMO_slot_bool_get(op->slots_in, "use_existing_faces")) {
hull_remove_overlapping(bm, hull_triangles, final_edges);
hull_tag_holes(bm, op);
}
/* Done with edges */
hull_final_edges_free(final_edges);
/* Convert hull triangles to BMesh faces */
hull_output_triangles(bm, hull_triangles);
BLI_mempool_destroy(hull_triangles);
hull_tag_unused(bm, op);
/* Output slot of input elements that ended up inside the hull
* rather than part of it */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_interior.out", BM_ALL_NOLOOP, HULL_FLAG_INTERIOR_ELE);
/* Output slot of input elements that ended up inside the hull and
* are unused by other geometry. */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_unused.out", BM_ALL_NOLOOP, HULL_FLAG_DEL);
/* Output slot of faces and edges that were in the input and on
* the hull (useful for cases like bridging where you want to
* delete some input geometry) */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_holes.out", BM_ALL_NOLOOP, HULL_FLAG_HOLE);
/* Output slot of all hull vertices, faces, and edges */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, HULL_FLAG_OUTPUT_GEOM);
}
} // namespace blender
#endif /* WITH_BULLET */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains functions
* for converting a Mesh
* into a Bmesh, and back again.
*/
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh"
#include "BKE_global.hh"
namespace blender {
void bmo_mesh_to_bmesh_exec(BMesh *bm, BMOperator *op)
{
Object *ob = static_cast<Object *>(BMO_slot_ptr_get(op->slots_in, "object"));
Mesh *mesh = static_cast<Mesh *>(BMO_slot_ptr_get(op->slots_in, "mesh"));
bool set_key = BMO_slot_bool_get(op->slots_in, "use_shapekey");
BMeshFromMeshParams params{};
params.use_shapekey = set_key;
params.active_shapekey = ob->shapenr;
BM_mesh_bm_from_me(bm, mesh, &params);
if (mesh->key && ob->shapenr > mesh->key->totkey) {
ob->shapenr = mesh->key->totkey - 1;
}
}
void bmo_object_load_bmesh_exec(BMesh *bm, BMOperator *op)
{
Object *ob = static_cast<Object *>(BMO_slot_ptr_get(op->slots_in, "object"));
// Scene *scene = BMO_slot_ptr_get(op, "scene");
Mesh *mesh = id_cast<Mesh *>(ob->data);
BMO_op_callf(bm, op->flag, "bmesh_to_mesh mesh=%p object=%p", mesh, ob);
}
void bmo_bmesh_to_mesh_exec(BMesh *bm, BMOperator *op)
{
Mesh *mesh = static_cast<Mesh *>(BMO_slot_ptr_get(op->slots_in, "mesh"));
// Object *ob = BMO_slot_ptr_get(op, "object");
BMeshToMeshParams params{};
params.calc_object_remap = true;
BM_mesh_bm_to_me(G.main, bm, mesh, &params);
}
} // namespace blender

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Basic mirror, optionally with UVs's.
*/
#include "BLI_math_base.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
void bmo_mirror_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupeop, weldop;
BMOIter siter;
BMVert *v;
float scale[3] = {1.0f, 1.0f, 1.0f};
float dist = BMO_slot_float_get(op->slots_in, "merge_dist");
int i;
int axis = BMO_slot_int_get(op->slots_in, "axis");
bool mirror_u = BMO_slot_bool_get(op->slots_in, "mirror_u");
bool mirror_v = BMO_slot_bool_get(op->slots_in, "mirror_v");
bool mirror_udim = BMO_slot_bool_get(op->slots_in, "mirror_udim");
BMOpSlot *slot_targetmap;
BMOpSlot *slot_vertmap;
BMO_op_initf(bm, &dupeop, op->flag, "duplicate geom=%s", op, "geom");
BMO_op_exec(bm, &dupeop);
BMO_slot_buffer_flag_enable(bm, dupeop.slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW);
/* feed old data to transform bmo */
scale[axis] = -1.0f;
BMO_op_callf(bm,
op->flag,
"scale verts=%fv vec=%v space=%s use_shapekey=%s",
ELE_NEW,
scale,
op,
"matrix",
op,
"use_shapekey");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
slot_vertmap = BMO_slot_get(dupeop.slots_out, "vert_map.out");
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
if (fabsf(v->co[axis]) <= dist) {
BMVert *v_new = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_vertmap, v));
BLI_assert(v_new != nullptr);
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_new, v);
}
}
if (mirror_u || mirror_v) {
BMFace *f;
BMLoop *l;
float *luv;
const int totlayer = CustomData_number_of_layers(&bm->ldata, CD_PROP_FLOAT2);
BMIter liter;
BMO_ITER (f, &siter, dupeop.slots_out, "geom.out", BM_FACE) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
for (i = 0; i < totlayer; i++) {
luv = static_cast<float *>(
CustomData_bmesh_get_n(&bm->ldata, l->head.data, CD_PROP_FLOAT2, i));
if (mirror_u) {
float uv_u = luv[0];
if (mirror_udim) {
luv[0] = ceilf(uv_u) - fmodf(uv_u, 1.0f);
}
else {
luv[0] = 1.0f - uv_u;
}
}
if (mirror_v) {
float uv_v = luv[1];
if (mirror_udim) {
luv[1] = ceilf(uv_v) - fmodf(uv_v, 1.0f);
}
else {
luv[1] = 1.0f - uv_v;
}
}
}
}
}
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
BMO_op_finish(bm, &dupeop);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,303 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Functionality for flipping faces to make normals consistent.
*/
#include "MEM_guardedalloc.h"
#include "BLI_linklist_stack.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/********* Right-hand faces implementation ****** */
#define FACE_FLAG (1 << 0)
#define FACE_FLIP (1 << 1)
#define FACE_TEMP (1 << 2)
static bool bmo_recalc_normal_loop_filter_cb(const BMLoop *l, void * /*user_data*/)
{
return BM_edge_is_manifold(l->e);
}
/**
* This uses a more comprehensive test to see if the furthest face from the center
* is pointing towards the center or not.
*
* A simple test could just check the dot product
* of the faces-normal and the direction from the center,
* however this can fail for faces which make a sharp spike. eg:
*
* <pre>
* +
* |\ <- face
* + +
* \ \
* \ \
* \ +--------------+
* \ |
* \ center -> + |
* \ |
* +------------+
* </pre>
*
* In the example above, the \a face can point towards the \a center
* which would end up flipping the normals inwards.
*
* To take these spikes into account, find the furthest face-loop-vertex.
*/
/**
* \return a face index in \a faces and set \a r_is_flip
* if the face is flipped away from the center.
*/
static int recalc_face_normals_find_index(BMesh *bm,
BMFace **faces,
const int faces_len,
bool *r_is_flip)
{
const float eps = FLT_EPSILON;
float cent_area_accum = 0.0f;
float cent[3];
const float cent_fac = 1.0f / float(faces_len);
bool is_flip = false;
int f_start_index;
int i;
/** Search for the best loop. Members are compared in-order defined here. */
struct {
/**
* Squared distance from the center to the loops vertex 'l->v'.
* The normalized direction between the center and this vertex
* is also used for the dot-products below.
*/
float dist_sq;
/**
* Signed dot product using the normalized edge vector,
* (best of 'l->prev->v' or 'l->next->v').
*/
float edge_dot;
/**
* Unsigned dot product using the loop-normal
* (sign is used to check if we need to flip).
*/
float loop_dot;
} best, test;
UNUSED_VARS_NDEBUG(bm);
zero_v3(cent);
/* first calculate the center */
for (i = 0; i < faces_len; i++) {
float f_cent[3];
const float f_area = BM_face_calc_area(faces[i]);
BM_face_calc_center_median_weighted(faces[i], f_cent);
madd_v3_v3fl(cent, f_cent, cent_fac * f_area);
cent_area_accum += f_area;
BLI_assert(BMO_face_flag_test(bm, faces[i], FACE_TEMP) == 0);
BLI_assert(BM_face_is_normal_valid(faces[i]));
}
if (cent_area_accum != 0.0f) {
mul_v3_fl(cent, 1.0f / cent_area_accum);
}
/* Distances must start above zero,
* or we can't do meaningful calculations based on the direction to the center */
best.dist_sq = eps;
best.edge_dot = best.loop_dot = -FLT_MAX;
/* used in degenerate cases only */
f_start_index = 0;
/**
* Find the outer-most vertex, comparing distance to the center,
* then the outer-most loop attached to that vertex.
*
* Important this is correctly detected,
* where casting a ray from the center won't hit any loops past this one.
* Otherwise the result may be incorrect.
*/
for (i = 0; i < faces_len; i++) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(faces[i]);
do {
bool is_best_dist_sq;
float dir[3];
sub_v3_v3v3(dir, l_iter->v->co, cent);
test.dist_sq = len_squared_v3(dir);
is_best_dist_sq = (test.dist_sq > best.dist_sq);
if (is_best_dist_sq || (test.dist_sq == best.dist_sq)) {
float edge_dir_pair[2][3];
mul_v3_fl(dir, 1.0f / sqrtf(test.dist_sq));
sub_v3_v3v3(edge_dir_pair[0], l_iter->next->v->co, l_iter->v->co);
sub_v3_v3v3(edge_dir_pair[1], l_iter->prev->v->co, l_iter->v->co);
if ((normalize_v3(edge_dir_pair[0]) > eps) && (normalize_v3(edge_dir_pair[1]) > eps)) {
bool is_best_edge_dot;
test.edge_dot = max_ff(dot_v3v3(dir, edge_dir_pair[0]), dot_v3v3(dir, edge_dir_pair[1]));
is_best_edge_dot = (test.edge_dot > best.edge_dot);
if (is_best_dist_sq || is_best_edge_dot || (test.edge_dot == best.edge_dot)) {
float loop_dir[3];
cross_v3_v3v3(loop_dir, edge_dir_pair[0], edge_dir_pair[1]);
if (normalize_v3(loop_dir) > eps) {
float loop_dir_dot;
/* Highly unlikely the furthest loop is also the concave part of an ngon,
* but it can be contrived with _very_ non-planar faces - so better check. */
if (UNLIKELY(dot_v3v3(loop_dir, l_iter->f->no) < 0.0f)) {
negate_v3(loop_dir);
}
loop_dir_dot = dot_v3v3(dir, loop_dir);
test.loop_dot = fabsf(loop_dir_dot);
if (is_best_dist_sq || is_best_edge_dot || (test.loop_dot > best.loop_dot)) {
best = test;
f_start_index = i;
is_flip = (loop_dir_dot < 0.0f);
}
}
}
}
}
} while ((l_iter = l_iter->next) != l_first);
}
*r_is_flip = is_flip;
return f_start_index;
}
/**
* Given an array of faces, recalculate their normals.
* this functions assumes all faces in the array are connected by edges.
*
* \param bm:
* \param faces: Array of connected faces.
* \param faces_len: Length of \a faces
* \param oflag: Flag to check before doing the actual face flipping.
*/
static void bmo_recalc_face_normals_array(BMesh *bm,
BMFace **faces,
const int faces_len,
const short oflag)
{
int i, f_start_index;
const short oflag_flip = oflag | FACE_FLIP;
bool is_flip;
BMFace *f;
BLI_LINKSTACK_DECLARE(fstack, BMFace *);
f_start_index = recalc_face_normals_find_index(bm, faces, faces_len, &is_flip);
if (is_flip) {
BMO_face_flag_enable(bm, faces[f_start_index], FACE_FLIP);
}
/* now that we've found our starting face, make all connected faces
* have the same winding. this is done recursively, using a manual
* stack (if we use simple function recursion, we'd end up overloading
* the stack on large meshes). */
BLI_LINKSTACK_INIT(fstack);
BLI_LINKSTACK_PUSH(fstack, faces[f_start_index]);
BMO_face_flag_enable(bm, faces[f_start_index], FACE_TEMP);
while ((f = BLI_LINKSTACK_POP(fstack))) {
const bool flip_state = BMO_face_flag_test_bool(bm, f, FACE_FLIP);
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_other = l_iter->radial_next;
if ((l_other != l_iter) && bmo_recalc_normal_loop_filter_cb(l_iter, nullptr)) {
if (!BMO_face_flag_test(bm, l_other->f, FACE_TEMP)) {
BMO_face_flag_enable(bm, l_other->f, FACE_TEMP);
BMO_face_flag_set(bm, l_other->f, FACE_FLIP, (l_other->v == l_iter->v) != flip_state);
BLI_LINKSTACK_PUSH(fstack, l_other->f);
}
}
} while ((l_iter = l_iter->next) != l_first);
}
BLI_LINKSTACK_FREE(fstack);
/* apply flipping to oflag'd faces */
for (i = 0; i < faces_len; i++) {
if (BMO_face_flag_test(bm, faces[i], oflag_flip) == oflag_flip) {
BM_face_normal_flip(bm, faces[i]);
}
BMO_face_flag_disable(bm, faces[i], FACE_TEMP);
}
}
/**
* Put normal to the outside, and set the first direction flags in edges
*
* then check the object, and set directions / direction-flags:
* but only for edges with 1 or 2 faces this is in fact the 'select connected'
*
* in case all faces were not done: start over with 'find the ultimate ...'.
*/
void bmo_recalc_face_normals_exec(BMesh *bm, BMOperator *op)
{
int *groups_array = MEM_new_array_uninitialized<int>(bm->totface, __func__);
BMFace **faces_grp = MEM_new_array_uninitialized<BMFace *>(bm->totface, __func__);
int (*group_index)[2];
const int group_tot = BM_mesh_calc_face_groups(bm,
groups_array,
&group_index,
bmo_recalc_normal_loop_filter_cb,
nullptr,
nullptr,
0,
BM_EDGE);
int i;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_FLAG);
BM_mesh_elem_table_ensure(bm, BM_FACE);
for (i = 0; i < group_tot; i++) {
const int fg_sta = group_index[i][0];
const int fg_len = group_index[i][1];
int j;
bool is_calc = false;
for (j = 0; j < fg_len; j++) {
faces_grp[j] = BM_face_at_index(bm, groups_array[fg_sta + j]);
if (is_calc == false) {
is_calc = BMO_face_flag_test_bool(bm, faces_grp[j], FACE_FLAG);
}
}
if (is_calc) {
bmo_recalc_face_normals_array(bm, faces_grp, fg_len, FACE_FLAG);
}
}
MEM_delete(faces_grp);
MEM_delete(groups_array);
MEM_delete(group_index);
}
} // namespace blender

View File

@@ -0,0 +1,278 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Simple edge offset functionality.
*
* \note Actual offset is done by edge-slide.
* (this only changes topology)
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define USE_CAP_OPTION
#define ELE_NEW (1 << 0)
#ifdef USE_CAP_OPTION
# define ELE_VERT_ENDPOINT (1 << 1)
#endif
/* set for debugging */
#define OFFSET 0.0f
static BMFace *bm_face_split_walk_back(BMesh *bm, BMLoop *l_src, BMLoop **r_l)
{
float (*cos)[3];
BMLoop *l_dst;
BMFace *f;
int num, i;
for (l_dst = l_src->prev, num = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, num++)
{
/* pass */
}
BLI_assert(num != 0);
Array<float3, BM_DEFAULT_NGON_STACK_SIZE> cos_buf(num);
cos = reinterpret_cast<float (*)[3]>(cos_buf.data());
for (l_dst = l_src->prev, i = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, i++)
{
copy_v3_v3(cos[num - (i + 1)], l_dst->v->co);
}
f = BM_face_split_n(bm, l_src->f, l_dst->prev, l_src->next, cos, num, r_l, nullptr);
return f;
}
void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op)
{
const int edges_num = BMO_slot_buffer_len(op->slots_in, "edges");
BMVert **verts;
STACK_DECLARE(verts);
int i;
#ifdef USE_CAP_OPTION
bool use_cap_endpoint = BMO_slot_bool_get(op->slots_in, "use_cap_endpoint");
int v_edges_max = 0;
#endif
BMOIter oiter;
/* only so we can detect new verts (index == -1) */
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
/* over alloc */
verts = MEM_new_array_uninitialized<BMVert *>((edges_num * 2), __func__);
STACK_INIT(verts, (edges_num * 2));
{
BMEdge *e;
BMO_ITER (e, &oiter, op->slots_in, "edges", BM_EDGE) {
int j;
BM_elem_flag_enable(e, BM_ELEM_TAG);
for (j = 0; j < 2; j++) {
BMVert *v_edge = *(&(e->v1) + j);
if (!BM_elem_flag_test(v_edge, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_edge, BM_ELEM_TAG);
STACK_PUSH(verts, v_edge);
}
}
}
}
/* -------------------------------------------------------------------- */
/* Remove verts only used by tagged edges */
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
int flag = 0;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
flag |= BM_elem_flag_test(e, BM_ELEM_TAG) ? 1 : 2;
if (flag == (1 | 2)) {
break;
}
}
/* only boundary verts are interesting */
if (flag != (1 | 2)) {
STACK_REMOVE(verts, i);
}
}
/* possible but unlikely we have no mixed vertices */
if (UNLIKELY(STACK_SIZE(verts) == 0)) {
MEM_delete(verts);
return;
}
/* main loop */
for (i = 0; i < STACK_SIZE(verts); i++) {
int v_edges_num = 0;
int v_edges_num_untag = 0;
BMVert *v = verts[i];
BMIter iter;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
BMVert *v_other;
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) {
BM_elem_flag_enable(l->f, BM_ELEM_TAG);
}
v_other = BM_edge_other_vert(e, v);
BM_edge_split(bm, e, v_other, nullptr, 1.0f - OFFSET);
}
else {
v_edges_num_untag += 1;
}
v_edges_num += 1;
}
#ifdef USE_CAP_OPTION
if (v_edges_num_untag == 1) {
BMO_vert_flag_enable(bm, v, ELE_VERT_ENDPOINT);
}
CLAMP_MIN(v_edges_max, v_edges_num);
#endif
}
for (i = 0; i < STACK_SIZE(verts); i++) {
BMVert *v = verts[i];
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (BM_elem_flag_test(l->f, BM_ELEM_TAG) && (l->f->len != 3)) {
BMFace *f_cmp = l->f;
if ((BM_elem_index_get(l->next->v) == -1) && (BM_elem_index_get(l->prev->v) == -1)) {
#ifdef USE_CAP_OPTION
if (use_cap_endpoint || (BMO_vert_flag_test(bm, v, ELE_VERT_ENDPOINT) == 0))
#endif
{
BMLoop *l_new;
if (!BM_face_split_check_double_face(l->prev, l->next, 3) &&
(BM_face_split(bm, l->f, l->prev, l->next, &l_new, nullptr, true) != nullptr))
{
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
UNUSED_VARS_NDEBUG(f_cmp);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
}
}
}
else if (l->f->len > 4) {
if (BM_elem_flag_test(l->e, BM_ELEM_TAG) != BM_elem_flag_test(l->prev->e, BM_ELEM_TAG)) {
if (BM_elem_index_get(l->next->v) == -1) {
if (BM_elem_index_get(l->prev->prev->v) == -1) {
BMLoop *l_new;
if (!BM_face_split_check_double_face(l->prev->prev, l->next, 4) &&
(BM_face_split(bm, l->f, l->prev->prev, l->next, &l_new, nullptr, true) !=
nullptr))
{
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
}
else {
/* walk backwards */
BMLoop *l_new;
bm_face_split_walk_back(bm, l, &l_new);
do {
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
l_new = l_new->next;
} while (BM_vert_is_edge_pair(l_new->v));
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
}
/* NOTE: instead of duplicate code in alternate direction,
* we can be sure to hit the other vertex, so the code above runs. */
#if 0
else if (BM_elem_index_get(l->prev->v) == -1) {
if (BM_elem_index_get(l->next->next->v) == -1) {
/* pass */
}
}
#endif
}
}
}
}
}
#ifdef USE_CAP_OPTION
if (use_cap_endpoint == false) {
Array<BMVert *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> varr_buf(v_edges_max);
BMVert **varr = varr_buf.data();
STACK_DECLARE(varr);
BMVert *v;
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
BMEdge *e;
v = verts[i];
STACK_INIT(varr, v_edges_max);
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
BMVert *v_other;
v_other = BM_edge_other_vert(e, v);
if (BM_elem_index_get(v_other) == -1) {
if (BM_vert_is_edge_pair(v_other)) {
/* defer bmesh_kernel_join_edge_kill_vert to avoid looping over data we're removing */
v_other->e = e;
STACK_PUSH(varr, v_other);
}
}
}
while ((v = STACK_POP(varr))) {
bmesh_kernel_join_edge_kill_vert(bm, v->e, v, true, false, false, true);
}
}
}
#endif
MEM_delete(verts);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Iteratively flatten 4+ sided faces.
*/
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_VERT_ADJUST (1 << 0)
#define ELE_FACE_ADJUST (1 << 1)
struct VertAccum {
float co[3] = {};
int co_tot = 0;
};
void bmo_planar_faces_exec(BMesh *bm, BMOperator *op)
{
const float fac = BMO_slot_float_get(op->slots_in, "factor");
const int iterations = BMO_slot_int_get(op->slots_in, "iterations");
const int faces_num = BMO_slot_buffer_len(op->slots_in, "faces");
const float eps = 0.00001f;
const float eps_sq = square_f(eps);
BMOIter oiter;
BMFace *f;
float (*faces_center)[3];
int i, iter_step, shared_vert_num;
faces_center = MEM_new_array_uninitialized<float[3]>(faces_num, __func__);
shared_vert_num = 0;
BMO_ITER_INDEX (f, &oiter, op->slots_in, "faces", BM_FACE, i) {
BMLoop *l_iter, *l_first;
if (f->len == 3) {
continue;
}
BM_face_calc_center_median_weighted(f, faces_center[i]);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (!BMO_vert_flag_test(bm, l_iter->v, ELE_VERT_ADJUST)) {
BMO_vert_flag_enable(bm, l_iter->v, ELE_VERT_ADJUST);
shared_vert_num += 1;
}
} while ((l_iter = l_iter->next) != l_first);
BMO_face_flag_enable(bm, f, ELE_FACE_ADJUST);
}
Map<BMVert *, VertAccum> vaccum_map;
vaccum_map.reserve(shared_vert_num);
for (iter_step = 0; iter_step < iterations; iter_step++) {
bool changed = false;
BMO_ITER_INDEX (f, &oiter, op->slots_in, "faces", BM_FACE, i) {
BMLoop *l_iter, *l_first;
float plane[4];
if (!BMO_face_flag_test(bm, f, ELE_FACE_ADJUST)) {
continue;
}
BMO_face_flag_disable(bm, f, ELE_FACE_ADJUST);
BLI_assert(f->len != 3);
/* keep original face data (else we 'move' the face) */
#if 0
BM_face_normal_update(f);
BM_face_calc_center_median_weighted(f, f_center);
#endif
plane_from_point_normal_v3(plane, faces_center[i], f->no);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
VertAccum &va = vaccum_map.lookup_or_add_default(l_iter->v);
float co[3];
closest_to_plane_normalized_v3(co, plane, l_iter->v->co);
va.co_tot += 1;
interp_v3_v3v3(va.co, va.co, co, 1.0f / float(va.co_tot));
} while ((l_iter = l_iter->next) != l_first);
}
for (const auto &item : vaccum_map.items()) {
BMVert *v = item.key;
const VertAccum &va = item.value;
BMIter iter;
if (len_squared_v3v3(v->co, va.co) > eps_sq) {
BMO_vert_flag_enable(bm, v, ELE_VERT_ADJUST);
interp_v3_v3v3(v->co, v->co, va.co, fac);
changed = true;
}
/* tag for re-calculation */
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (f->len != 3) {
BMO_face_flag_enable(bm, f, ELE_FACE_ADJUST);
}
}
}
/* if nothing changed, break out early */
if (changed == false) {
break;
}
vaccum_map.clear();
}
MEM_delete(faces_center);
}
} // namespace blender

View File

@@ -0,0 +1,135 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Pokes a face.
*
* Splits a face into a triangle fan.
*/
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
namespace blender {
#define ELE_NEW 1
/**
* Pokes a face
*
* Splits a face into a triangle fan.
* Iterate over all selected faces, create a new center vertex and
* create triangles between original face edges and new center vertex.
*/
void bmo_poke_exec(BMesh *bm, BMOperator *op)
{
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
BMOIter oiter;
BMFace *f;
const float offset = BMO_slot_float_get(op->slots_in, "offset");
const bool use_relative_offset = BMO_slot_bool_get(op->slots_in, "use_relative_offset");
const int center_mode = BMO_slot_int_get(op->slots_in, "center_mode");
void (*bm_face_calc_center_fn)(const BMFace *f, float r_cent[3]);
switch (center_mode) {
case BMOP_POKE_MEDIAN_WEIGHTED:
bm_face_calc_center_fn = BM_face_calc_center_median_weighted;
break;
case BMOP_POKE_BOUNDS:
bm_face_calc_center_fn = BM_face_calc_center_bounds;
break;
case BMOP_POKE_MEDIAN:
bm_face_calc_center_fn = BM_face_calc_center_median;
break;
default:
BLI_assert(0);
return;
}
BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
float f_center[3];
BMVert *v_center = nullptr;
BMLoop *l_iter, *l_first;
/* only interpolate the central loop from the face once,
* then copy to all others in the fan */
BMLoop *l_center_example;
/* 1.0 or the average length from the center to the face verts */
float offset_fac;
int i;
bm_face_calc_center_fn(f, f_center);
v_center = BM_vert_create(bm, f_center, nullptr, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, v_center, ELE_NEW);
/* handled by BM_loop_interp_from_face */
// BM_vert_interp_from_face(bm, v_center, f);
if (use_relative_offset) {
offset_fac = 0.0f;
}
else {
offset_fac = 1.0f;
}
i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_new;
BMFace *f_new = BM_face_create_quad_tri(
bm, l_iter->v, l_iter->next->v, v_center, nullptr, f, BM_CREATE_NOP);
l_new = BM_FACE_FIRST_LOOP(f_new);
if (i == 0) {
l_center_example = l_new->prev;
BM_loop_interp_from_face(bm, l_center_example, f, true, false);
}
else {
BM_elem_attrs_copy(bm, l_center_example, l_new->prev);
}
/* Copy Loop Data */
BM_elem_attrs_copy(bm, l_iter, l_new);
BM_elem_attrs_copy(bm, l_iter->next, l_new->next);
BMO_face_flag_enable(bm, f_new, ELE_NEW);
if (cd_loop_mdisp_offset != -1) {
float f_new_center[3];
BM_face_calc_center_median(f_new, f_new_center);
BM_face_interp_multires_ex(bm, f_new, f, f_new_center, f_center, cd_loop_mdisp_offset);
}
if (use_relative_offset) {
offset_fac += len_v3v3(f_center, l_iter->v->co);
}
} while ((void)i++, (l_iter = l_iter->next) != l_first);
if (use_relative_offset) {
offset_fac /= float(f->len);
}
/* else remain at 1.0 */
copy_v3_v3(v_center->no, f->no);
madd_v3_v3fl(v_center->co, v_center->no, offset * offset_fac);
/* Kill Face */
BM_face_kill(bm, f);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "verts.out", BM_VERT, ELE_NEW);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_NEW);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,930 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Welding and merging functionality.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_kdtree.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_base.hh"
#include "BLI_math_vector.h"
#include "BLI_multi_value_map.hh"
#include "BLI_stack.h"
#include "BLI_stack.hh"
#include "BLI_utildefines_stack.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
static void remdoubles_splitface(BMFace *f, BMesh *bm, BMOperator *op, BMOpSlot *slot_targetmap)
{
BMIter liter;
BMLoop *l, *l_tar, *l_double;
bool split = false;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BMVert *v_tar = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, l->v));
/* Ok: if `v_tar` is nullptr (e.g. not in the map) then it's
* a target vert, otherwise it's a double. */
if (v_tar) {
l_tar = BM_face_vert_share_loop(f, v_tar);
if (l_tar && (l_tar != l) && !BM_loop_is_adjacent(l_tar, l)) {
l_double = l;
split = true;
break;
}
}
}
if (split) {
BMLoop *l_new;
BMFace *f_new;
f_new = BM_face_split(bm, f, l_double, l_tar, &l_new, nullptr, false);
remdoubles_splitface(f, bm, op, slot_targetmap);
remdoubles_splitface(f_new, bm, op, slot_targetmap);
}
}
#define ELE_DEL 1
#define EDGE_COL 2
#define VERT_IN_FACE 4
/**
* Helper function for #bmo_weld_verts_exec so we can use stack memory.
*/
static BMFace *remdoubles_createface(BMesh *bm,
BMFace *f,
BMOpSlot *slot_targetmap,
bool *r_created)
{
BMEdge *e_new;
/* New ordered edges. */
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_buf(f->len);
BMEdge **edges = edges_buf.data();
/* New ordered verts. */
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts_buf(f->len);
BMVert **verts = verts_buf.data();
/* Original ordered loops to copy attributes into the new face. */
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops_buf(f->len);
BMLoop **loops = loops_buf.data();
STACK_DECLARE(edges);
STACK_DECLARE(loops);
STACK_DECLARE(verts);
STACK_INIT(edges, f->len);
STACK_INIT(loops, f->len);
STACK_INIT(verts, f->len);
*r_created = false;
{
#define LOOP_MAP_VERT_INIT(l_init, v_map, is_del) \
v_map = l_init->v; \
is_del = BMO_vert_flag_test_bool(bm, v_map, ELE_DEL); \
if (is_del) { \
v_map = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v_map)); \
} \
((void)0)
BMLoop *l_first, *l_curr, *l_next;
BMVert *v_curr;
bool is_del_v_curr;
l_curr = l_first = BM_FACE_FIRST_LOOP(f);
LOOP_MAP_VERT_INIT(l_curr, v_curr, is_del_v_curr);
do {
BMVert *v_next;
bool is_del_v_next;
l_next = l_curr->next;
LOOP_MAP_VERT_INIT(l_next, v_next, is_del_v_next);
/* Only search for a new edge if one of the verts is mapped. */
if ((is_del_v_curr || is_del_v_next) == 0) {
e_new = l_curr->e;
}
else if (v_curr == v_next) {
e_new = nullptr; /* Skip. */
}
else {
e_new = BM_edge_exists(v_curr, v_next);
BLI_assert(e_new); /* Never fails. */
}
if (e_new) {
if (UNLIKELY(BMO_vert_flag_test(bm, v_curr, VERT_IN_FACE))) {
/* We can't make the face, bail out. */
STACK_CLEAR(edges);
goto finally;
}
BMO_vert_flag_enable(bm, v_curr, VERT_IN_FACE);
STACK_PUSH(edges, e_new);
STACK_PUSH(loops, l_curr);
STACK_PUSH(verts, v_curr);
}
v_curr = v_next;
is_del_v_curr = is_del_v_next;
} while ((l_curr = l_next) != l_first);
#undef LOOP_MAP_VERT_INIT
}
finally: {
uint i;
for (i = 0; i < STACK_SIZE(verts); i++) {
BMO_vert_flag_disable(bm, verts[i], VERT_IN_FACE);
}
}
if (STACK_SIZE(edges) >= 3) {
BMFace *f_new = BM_face_exists(verts, STACK_SIZE(verts));
if (f_new) {
return f_new;
}
f_new = BM_face_create(bm, verts, edges, STACK_SIZE(edges), f, BM_CREATE_NOP);
BLI_assert(f_new != f);
if (f_new) {
uint i = 0;
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
do {
BM_elem_attrs_copy(bm, loops[i], l_iter);
} while ((void)i++, (l_iter = l_iter->next) != l_first);
*r_created = true;
return f_new;
}
}
return nullptr;
}
/**
* \note with 'targetmap', multiple 'keys' are currently supported,
* though no callers should be using.
* (because slot maps currently use GHash without the GHASH_FLAG_ALLOW_DUPES flag set)
*/
void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
{
BMIter iter, liter;
BMVert *v;
BMEdge *e;
BMLoop *l;
BMFace *f;
BMOpSlot *slot_targetmap = BMO_slot_get(op->slots_in, "targetmap");
const bool use_centroid = BMO_slot_bool_get(op->slots_in, "use_centroid");
const bool average_vert_data = BMO_slot_bool_get(op->slots_in, "average_vert_data") ||
use_centroid;
/* Maintain selection history. */
const bool has_selected = !bm->selected.is_empty();
const bool use_targetmap_all = has_selected;
Map<void *, void *> targetmap_all;
/* Used when use_centroid or average_vert_data is true. */
MultiValueMap<BMVert *, BMVert *> clusters;
/* Mark merge verts for deletion. */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BMVert *v_dst = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v));
if (v_dst == nullptr) {
continue;
}
BMO_vert_flag_enable(bm, v, ELE_DEL);
/* Merge the vertex flags, else we get randomly selected/unselected verts. */
BM_elem_flag_merge_ex(v, v_dst, BM_ELEM_HIDDEN);
if (use_targetmap_all) {
BLI_assert(v != v_dst);
targetmap_all.add(v, v_dst);
}
/* Group vertices by their survivor. */
if (average_vert_data && LIKELY(v_dst != v)) {
clusters.add(v_dst, v);
}
}
if (use_centroid) {
/* Compute centroid for each survivor. */
for (const auto &item : clusters.items()) {
BMVert *v_dst = item.key;
Span<BMVert *> cluster = item.value;
float centroid[3];
copy_v3_v3(centroid, v_dst->co);
int count = 1; /* Include `v_dst`. */
for (BMVert *v_duplicate : cluster) {
add_v3_v3(centroid, v_duplicate->co);
count++;
}
mul_v3_fl(centroid, 1.0f / float(count));
copy_v3_v3(v_dst->co, centroid);
}
}
if (average_vert_data) {
for (const auto &item : clusters.items()) {
BMVert *v_dst = item.key;
Span<BMVert *> merged_verts = item.value;
Array<const void *> src_blocks(merged_verts.size() + 1);
src_blocks[0] = v_dst->head.data;
for (const int i : merged_verts.index_range()) {
src_blocks[i + 1] = merged_verts[i]->head.data;
}
CustomData_bmesh_interp(
&bm->vdata, src_blocks.data(), nullptr, src_blocks.size(), v_dst->head.data);
}
}
/* Check if any faces are getting their own corners merged
* together, split face if so. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
remdoubles_splitface(f, bm, op, slot_targetmap);
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BMVert *v1, *v2;
const bool is_del_v1 = BMO_vert_flag_test_bool(bm, (v1 = e->v1), ELE_DEL);
const bool is_del_v2 = BMO_vert_flag_test_bool(bm, (v2 = e->v2), ELE_DEL);
if (is_del_v1 || is_del_v2) {
if (is_del_v1) {
v1 = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v1));
}
if (is_del_v2) {
v2 = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v2));
}
if (v1 == v2) {
BMO_edge_flag_enable(bm, e, EDGE_COL);
}
else {
/* Always merge flags, even for edges we already created. */
BMEdge *e_new = BM_edge_exists(v1, v2);
if (e_new == nullptr) {
e_new = BM_edge_create(bm, v1, v2, e, BM_CREATE_NOP);
}
BM_elem_flag_merge_ex(e_new, e, BM_ELEM_HIDDEN);
if (use_targetmap_all) {
BLI_assert(e != e_new);
targetmap_all.add(e, e_new);
}
}
BMO_edge_flag_enable(bm, e, ELE_DEL);
}
}
/* Faces get "modified" by creating new faces here, then at the
* end the old faces are deleted. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
bool vert_delete = false;
int edge_collapse = 0;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_vert_flag_test(bm, l->v, ELE_DEL)) {
vert_delete = true;
}
if (BMO_edge_flag_test(bm, l->e, EDGE_COL)) {
edge_collapse++;
}
}
if (vert_delete) {
bool use_in_place = false;
BMFace *f_new = nullptr;
BMO_face_flag_enable(bm, f, ELE_DEL);
if (f->len - edge_collapse >= 3) {
bool created;
f_new = remdoubles_createface(bm, f, slot_targetmap, &created);
/* Do this so we don't need to return a list of created faces. */
if (f_new) {
if (created) {
bmesh_face_swap_data(f_new, f);
if (bm->use_toolflags) {
std::swap((reinterpret_cast<BMFace_OFlag *>(f))->oflags,
(reinterpret_cast<BMFace_OFlag *>(f_new))->oflags);
}
BMO_face_flag_disable(bm, f, ELE_DEL);
BM_face_kill(bm, f_new);
use_in_place = true;
}
else {
BM_elem_flag_merge_ex(f_new, f, BM_ELEM_HIDDEN);
}
}
}
if ((use_in_place == false) && (f_new != nullptr)) {
BLI_assert(f != f_new);
if (use_targetmap_all) {
targetmap_all.add(f, f_new);
}
if (bm->act_face && (f == bm->act_face)) {
bm->act_face = f_new;
}
}
}
}
if (has_selected) {
BM_select_history_merge_from_targetmap(
bm, &targetmap_all, &targetmap_all, &targetmap_all, true);
}
BMO_mesh_delete_oflag_context(bm, ELE_DEL, DEL_ONLYTAGGED, nullptr);
}
#define VERT_KEEP 8
#define EDGE_MARK 1
void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v, *vert_target;
BMLoop *l, *l_first = nullptr;
float fac;
int i, tot;
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
tot = BM_vert_face_count(vert_target);
if (!tot) {
return;
}
fac = 1.0f / tot;
BM_ITER_ELEM (l, &iter, vert_target, BM_LOOPS_OF_VERT) {
if (l_first == nullptr) {
l_first = l;
}
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i)) {
const int type = bm->ldata.layers[i].type;
const int offset = bm->ldata.layers[i].offset;
void *e1, *e2;
e1 = BM_ELEM_CD_GET_VOID_P(l_first, offset);
e2 = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_multiply(eCustomDataType(type), e2, fac);
if (l != l_first) {
CustomData_data_add(eCustomDataType(type), e1, e2);
}
}
}
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
if (l == l_first) {
continue;
}
CustomData_bmesh_copy_block(bm->ldata, l_first->head.data, &l->head.data);
}
}
}
void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v;
BMLoop *l;
CDBlockBytes min, max;
int i;
for (i = 0; i < bm->ldata.totlayer; i++) {
const int type = bm->ldata.layers[i].type;
const int offset = bm->ldata.layers[i].offset;
if (!CustomData_layer_has_math(&bm->ldata, i)) {
continue;
}
CustomData_data_initminmax(eCustomDataType(type), &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
void *block = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_dominmax(eCustomDataType(type), block, &min, &max);
}
}
CustomData_data_multiply(eCustomDataType(type), &min, 0.5f);
CustomData_data_multiply(eCustomDataType(type), &max, 0.5f);
CustomData_data_add(eCustomDataType(type), &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
void *block = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_copy_value(eCustomDataType(type), &min, block);
}
}
}
}
void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOIter siter;
BMVert *v, *vert_target = nullptr;
float vec[3];
BMOpSlot *slot_targetmap;
BMO_slot_vec_get(op->slots_in, "merge_co", vec);
// BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
const bool is_explicit_snap = vert_target != nullptr;
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (!vert_target) {
vert_target = v;
copy_v3_v3(vert_target->co, vec);
}
else if (v != vert_target) {
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_target);
}
}
if (!is_explicit_snap) {
BMO_slot_bool_set(weldop.slots_in, "average_vert_data", true);
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_collapse_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMWalker walker;
BMIter iter;
BMEdge *e;
BLI_Stack *edge_stack;
BMOpSlot *slot_targetmap;
if (BMO_slot_bool_get(op->slots_in, "uvs")) {
BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
}
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
BMW_init(&walker,
bm,
BMW_VERT_SHELL,
BMW_MASK_NOP,
EDGE_MARK,
BMW_MASK_NOP,
BMW_FLAG_NOP, /* No need to use #BMW_FLAG_TEST_HIDDEN, already marked data. */
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
float center[3];
int count = 0;
BMVert *v_tar;
zero_v3(center);
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
BLI_assert(BLI_stack_is_empty(edge_stack));
for (e = static_cast<BMEdge *>(BMW_begin(&walker, e->v1)); e;
e = static_cast<BMEdge *>(BMW_step(&walker)))
{
BLI_stack_push(edge_stack, &e);
add_v3_v3(center, e->v1->co);
add_v3_v3(center, e->v2->co);
count += 2;
/* Prevent adding to `slot_targetmap` multiple times. */
BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
}
if (!BLI_stack_is_empty(edge_stack)) {
mul_v3_fl(center, 1.0f / count);
/* Snap edges to a point. for initial testing purposes anyway. */
e = *static_cast<BMEdge **>(BLI_stack_peek(edge_stack));
v_tar = e->v1;
while (!BLI_stack_is_empty(edge_stack)) {
uint j;
BLI_stack_pop(edge_stack, &e);
for (j = 0; j < 2; j++) {
BMVert *v_src = *((&e->v1) + j);
copy_v3_v3(v_src->co, center);
if ((v_src != v_tar) && !BM_elem_flag_test(v_src, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_src, BM_ELEM_TAG);
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_src, v_tar);
}
}
}
}
}
BLI_stack_free(edge_stack);
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
BMW_end(&walker);
}
/** UV collapse function. */
static void bmo_collapsecon_do_layer(BMesh *bm, const int layer, const short oflag)
{
const int type = bm->ldata.layers[layer].type;
const int offset = bm->ldata.layers[layer].offset;
BMIter iter, liter;
BMFace *f;
BMLoop *l, *l2;
BMWalker walker;
BLI_Stack *block_stack;
CDBlockBytes min, max;
BMW_init(&walker,
bm,
BMW_LOOPDATA_ISLAND,
BMW_MASK_NOP,
oflag,
BMW_MASK_NOP,
BMW_FLAG_NOP, /* No need to use #BMW_FLAG_TEST_HIDDEN, already marked data. */
layer,
BMW_DELIMIT_NONE);
block_stack = BLI_stack_new(sizeof(void *), __func__);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_edge_flag_test(bm, l->e, oflag)) {
/* Walk. */
BLI_assert(BLI_stack_is_empty(block_stack));
CustomData_data_initminmax(eCustomDataType(type), &min, &max);
for (l2 = static_cast<BMLoop *>(BMW_begin(&walker, l)); l2;
l2 = static_cast<BMLoop *>(BMW_step(&walker)))
{
void *block = BM_ELEM_CD_GET_VOID_P(l2, offset);
CustomData_data_dominmax(eCustomDataType(type), block, &min, &max);
BLI_stack_push(block_stack, &block);
}
if (!BLI_stack_is_empty(block_stack)) {
CustomData_data_multiply(eCustomDataType(type), &min, 0.5f);
CustomData_data_multiply(eCustomDataType(type), &max, 0.5f);
CustomData_data_add(eCustomDataType(type), &min, &max);
/* Snap custom-data (UV, vertex-colors) points to their centroid. */
while (!BLI_stack_is_empty(block_stack)) {
void *block;
BLI_stack_pop(block_stack, &block);
CustomData_data_copy_value(eCustomDataType(type), &min, block);
}
}
}
}
}
BLI_stack_free(block_stack);
BMW_end(&walker);
}
void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op)
{
const short oflag = EDGE_MARK;
int i;
/* Check flags don't change once set. */
#ifndef NDEBUG
int tot_test;
#endif
if (!CustomData_has_math(&bm->ldata)) {
return;
}
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, oflag);
#ifndef NDEBUG
tot_test = BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, oflag, true);
#endif
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i)) {
bmo_collapsecon_do_layer(bm, i, oflag);
}
}
#ifndef NDEBUG
BLI_assert(tot_test == BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, EDGE_MARK, true));
#endif
}
/**
* \return a `verts_len` aligned array of indices.
* Index values:
* - `-1`: Not a duplicate, others may use as a target.
* - `<itself>`: Not a duplicate (marked to be kept), others may use as a target.
* - `0..verts_len`: The target double.
*/
static int *bmesh_find_doubles_by_distance_impl(BMesh *bm,
BMVert *const *verts,
const int verts_len,
const float dist,
const bool has_keep_vert)
{
int *duplicates = MEM_new_array_uninitialized<int>(verts_len, __func__);
bool found_duplicates = false;
bool has_self_index = false;
KDTree<float3> *tree = kdtree_new<float3>(verts_len);
for (int i = 0; i < verts_len; i++) {
kdtree_insert<float3>(tree, i, verts[i]->co);
if (has_keep_vert && BMO_vert_flag_test(bm, verts[i], VERT_KEEP)) {
duplicates[i] = i;
has_self_index = true;
}
else {
duplicates[i] = -1;
}
}
kdtree_balance<float3>(tree);
/* Given a cluster of duplicates, pick the index to keep. */
auto deduplicate_target_calc_fn = [&verts](const int *cluster, const int cluster_num) -> int {
if (cluster_num == 2) {
/* Special case, no use in calculating centroid.
* Use the lowest index for stability. */
return (cluster[0] < cluster[1]) ? 0 : 1;
}
BLI_assert(cluster_num > 2);
float3 centroid{0.0f};
for (int i = 0; i < cluster_num; i++) {
centroid += float3(verts[cluster[i]]->co);
}
centroid /= float(cluster_num);
/* Now pick the most "central" index (with lowest index as a tie breaker). */
const int cluster_end = cluster_num - 1;
/* Assign `i_best` from the last index as this is the index where the search originated
* so it's most likely to be the best. */
int i_best = cluster_end;
float dist_sq_best = len_squared_v3v3(centroid, verts[cluster[i_best]]->co);
for (int i = 0; i < cluster_end; i++) {
const float dist_sq_test = len_squared_v3v3(centroid, verts[cluster[i]]->co);
if (dist_sq_test > dist_sq_best) {
continue;
}
if (dist_sq_test == dist_sq_best) {
if (cluster[i] > cluster[i_best]) {
continue;
}
}
i_best = i;
dist_sq_best = dist_sq_test;
}
return i_best;
};
found_duplicates = kdtree_calc_duplicates_cb<float3>(
tree, dist, duplicates, has_self_index, deduplicate_target_calc_fn) != 0;
kdtree_free<float3>(tree);
if (!found_duplicates) {
MEM_delete(duplicates);
duplicates = nullptr;
}
return duplicates;
}
/** \copydoc #bmesh_find_doubles_by_distance_connected_impl. */
static int *bmesh_find_doubles_by_distance_connected_impl(BMesh *bm,
BMVert *const *verts,
const int verts_len,
const float dist,
const bool has_keep_vert)
{
int *duplicates = MEM_new_array_uninitialized<int>(verts_len, __func__);
bool found_duplicates = false;
Stack<int> vert_stack;
Map<BMVert *, int> vert_to_index_map;
for (int i = 0; i < verts_len; i++) {
if (has_keep_vert && BMO_vert_flag_test(bm, verts[i], VERT_KEEP)) {
duplicates[i] = i;
}
else {
duplicates[i] = -1;
}
vert_to_index_map.add(verts[i], i);
}
const float dist_sq = math::square(dist);
for (int i = 0; i < verts_len; i++) {
if (!ELEM(duplicates[i], -1, i)) {
continue;
}
const float *co_check = verts[i]->co;
BLI_assert(vert_stack.is_empty());
int i_check = i;
do {
BMVert *v_check = verts[i_check];
if (v_check->e) {
BMEdge *e_iter, *e_first;
e_first = e_iter = v_check->e;
do {
/* Edge stepping. */
BMVert *v_other = BM_edge_other_vert(e_iter, v_check);
if (len_squared_v3v3(v_other->co, co_check) < dist_sq) {
const int i_other = vert_to_index_map.lookup_default(v_other, -1);
if ((i_other != -1) && (duplicates[i_other] == -1)) {
duplicates[i_other] = i;
vert_stack.push(i_other);
found_duplicates = true;
}
}
/* Face stepping. */
if (e_iter->l) {
BMLoop *l_radial_iter;
l_radial_iter = e_iter->l;
do {
if (l_radial_iter->v != v_check) {
/* This face will be met from another edge. */
continue;
}
if (l_radial_iter->f->len <= 3) {
/* Edge iteration handles triangles. */
continue;
}
/* Loop over all vertices not connected to edges attached to `v_check`.
* For a 4 sided face, this will only check 1 vertex. */
BMLoop *l_iter = l_radial_iter->next->next;
BMLoop *l_end = l_radial_iter->prev;
do {
BMVert *v_other = l_iter->v;
if (len_squared_v3v3(v_other->co, co_check) < dist_sq) {
const int i_other = vert_to_index_map.lookup_default(v_other, -1);
if ((i_other != -1) && (duplicates[i_other] == -1)) {
duplicates[i_other] = i;
vert_stack.push(i_other);
found_duplicates = true;
}
}
} while ((l_iter = l_iter->next) != l_end);
} while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l);
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_check)) != e_first);
}
} while ((i_check = vert_stack.is_empty() ? -1 : vert_stack.pop()) != -1);
}
if (!found_duplicates) {
MEM_delete(duplicates);
duplicates = nullptr;
}
return duplicates;
}
static void bmesh_find_doubles_common(BMesh *bm,
BMOperator *op,
BMOperator *optarget,
BMOpSlot *optarget_slot)
{
const bool use_connected = BMO_slot_bool_get(op->slots_in, "use_connected");
const BMOpSlot *slot_verts = BMO_slot_get(op->slots_in, "verts");
BMVert *const *verts = reinterpret_cast<BMVert **>(slot_verts->data.buf);
const int verts_len = slot_verts->len;
bool has_keep_vert = false;
const float dist = BMO_slot_float_get(op->slots_in, "dist");
/* Test whether keep_verts arg exists and is non-empty. */
if (BMO_slot_exists(op->slots_in, "keep_verts")) {
BMOIter oiter;
has_keep_vert = BMO_iter_new(&oiter, op->slots_in, "keep_verts", BM_VERT) != nullptr;
}
/* Flag keep_verts. */
if (has_keep_vert) {
BMO_slot_buffer_flag_enable(bm, op->slots_in, "keep_verts", BM_VERT, VERT_KEEP);
}
int *duplicates = nullptr; /* `verts_len` aligned index array. */
if (use_connected) {
duplicates = bmesh_find_doubles_by_distance_connected_impl(
bm, verts, verts_len, dist, has_keep_vert);
}
else {
duplicates = bmesh_find_doubles_by_distance_impl(bm, verts, verts_len, dist, has_keep_vert);
}
/* Null when no duplicates were found. */
if (duplicates) {
for (int i = 0; i < verts_len; i++) {
BMVert *v_check = verts[i];
if (duplicates[i] == -1) {
/* NOP (others can use as target). */
}
else if (duplicates[i] == i) {
/* Keep (others can use as target). */
}
else {
BMVert *v_other = verts[duplicates[i]];
BLI_assert(ELEM(duplicates[duplicates[i]], -1, duplicates[i]));
BMO_slot_map_elem_insert(optarget, optarget_slot, v_check, v_other);
}
}
MEM_delete(duplicates);
}
}
void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
bmesh_find_doubles_common(bm, op, &weldop, slot_targetmap);
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_find_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *slot_targetmap_out;
slot_targetmap_out = BMO_slot_get(op->slots_out, "targetmap.out");
bmesh_find_doubles_common(bm, op, op, slot_targetmap_out);
}
} // namespace blender

View File

@@ -0,0 +1,271 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Rotate edges topology that share two faces.
*/
#include <cfloat>
#include "MEM_guardedalloc.h"
#include "BLI_heap.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT 1
#define FACE_MARK 1
/**
* Rotate edges where every edge has its own faces (we can rotate in any order).
*/
static void bm_rotate_edges_simple(BMesh *bm,
BMOperator *op,
const short check_flag,
const bool use_ccw)
{
BMOIter siter;
BMEdge *e;
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
/* This ends up being called twice, could add option to not to call check in
* #BM_edge_rotate to get some extra speed. */
if (BM_edge_rotate_check(e)) {
BMEdge *e_rotate = BM_edge_rotate(bm, e, use_ccw, check_flag);
if (e_rotate != nullptr) {
BMO_edge_flag_enable(bm, e_rotate, EDGE_OUT);
}
}
}
}
/**
* Edge length is just a way of ordering that's independent of order in the edges argument,
* we could use some other method since ideally all edges will be rotated,
* this just happens to be simple to calculate.
*/
static float bm_edge_calc_rotate_cost(const BMEdge *e)
{
return -BM_edge_calc_length_squared(e);
}
/**
* Check if this edge is a boundary: Are more than one of the connected faces edges rotating too?
*/
static bool bm_edge_rotate_is_boundary(const BMEdge *e)
{
/* Number of adjacent shared faces. */
int count = 0;
BMLoop *l_radial_iter = e->l;
do {
/* Skip this edge. */
BMLoop *l_iter = l_radial_iter->next;
do {
BMEdge *e_iter = l_iter->e;
const int e_iter_index = BM_elem_index_get(e_iter);
if (e_iter_index != -1) {
if (count == 1) {
return false;
}
count += 1;
break;
}
} while ((l_iter = l_iter->next) != l_radial_iter);
} while ((l_radial_iter = l_radial_iter->radial_next) != e->l);
return true;
}
/**
* Rotate edges where edges share faces,
* edges which could not rotate need to be re-considered after neighbors are rotated.
*/
static void bm_rotate_edges_shared(
BMesh *bm, BMOperator *op, short check_flag, const bool use_ccw, const int edges_len)
{
Heap *heap = BLI_heap_new_ex(edges_len);
HeapNode **eheap_table = MEM_new_array_uninitialized<HeapNode *>(edges_len, __func__);
BMEdge **edges = reinterpret_cast<BMEdge **>(
BMO_SLOT_AS_BUFFER(BMO_slot_get(op->slots_in, "edges")));
int edges_len_rotate = 0;
/* Never read edges with this value in the `eheap_table` since they have been freed. */
HeapNode *edge_free_id = reinterpret_cast<HeapNode *>(uintptr_t(-1));
{
BMIter iter;
BMEdge *e;
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_index_set(e, -1); /* set_dirty! */
}
bm->elem_index_dirty |= BM_EDGE;
}
for (int i = 0; i < edges_len; i++) {
BMEdge *e = edges[i];
BM_elem_index_set(e, BM_edge_is_manifold(e) ? i : -1); /* set_dirty! */
eheap_table[i] = nullptr;
}
/* First operate on boundary edges, this is often all that's needed,
* regions that have no boundaries are handles after. */
enum {
PASS_TYPE_BOUNDARY = 0,
PASS_TYPE_ALL = 1,
PASS_TYPE_DONE = 2,
};
uint pass_type = PASS_TYPE_BOUNDARY;
while ((pass_type != PASS_TYPE_DONE) && (edges_len_rotate != edges_len)) {
BLI_assert(BLI_heap_is_empty(heap));
{
for (int i = 0; i < edges_len; i++) {
if (eheap_table[i] == edge_free_id) {
/* `e` is freed. */
continue;
}
BMEdge *e = edges[i];
BLI_assert(eheap_table[i] == nullptr);
bool ok = (BM_elem_index_get(e) != -1) && BM_edge_rotate_check(e);
if (ok) {
if (pass_type == PASS_TYPE_BOUNDARY) {
ok = bm_edge_rotate_is_boundary(e);
}
}
if (ok) {
float cost = bm_edge_calc_rotate_cost(e);
if (pass_type == PASS_TYPE_BOUNDARY) {
/* Trick to ensure once started,
* non boundaries are handled before other boundary edges.
* This means the first longest boundary defines the starting point which is rotated
* until all its connected edges are exhausted
* and the next boundary is popped off the heap.
*
* Without this we may rotate from different starting points and meet in the middle
* with obviously uneven topology.
*
* Move from negative to positive value,
* inverting so large values are still handled first.
*/
cost = cost != 0.0f ? -1.0f / cost : FLT_MAX;
}
eheap_table[i] = BLI_heap_insert(heap, cost, e);
}
}
}
if (BLI_heap_is_empty(heap)) {
pass_type += 1;
continue;
}
const int edges_len_rotate_prev = edges_len_rotate;
while (!BLI_heap_is_empty(heap)) {
BMEdge *e_best = static_cast<BMEdge *>(BLI_heap_pop_min(heap));
const int e_best_index = BM_elem_index_get(e_best);
eheap_table[e_best_index] = nullptr;
/* No problem if this fails, re-evaluate if faces connected to this edge are touched. */
if (BM_edge_rotate_check(e_best)) {
BMEdge *e_rotate = BM_edge_rotate(bm, e_best, use_ccw, check_flag);
if (e_rotate != nullptr) {
BMO_edge_flag_enable(bm, e_rotate, EDGE_OUT);
/* invalidate so we don't try touch this again. */
BM_elem_index_set(e_rotate, -1); /* set_dirty! */
/* If rotate succeeds, the edge has been freed. */
eheap_table[e_best_index] = edge_free_id;
edges_len_rotate += 1;
/* NOTE: we could validate all edges which have not been rotated
* (not just previously degenerate edges).
* However there is no real need -
* they can be left until they're popped off the queue. */
/* We don't know the exact topology after rotating the edge,
* so loop over all faces attached to the new edge,
* typically this will only be two faces. */
BMLoop *l_radial_iter = e_rotate->l;
do {
/* Skip this edge. */
BMLoop *l_iter = l_radial_iter->next;
do {
BMEdge *e_iter = l_iter->e;
const int e_iter_index = BM_elem_index_get(e_iter);
if ((e_iter_index != -1) && (eheap_table[e_iter_index] == nullptr)) {
/* Once freed, they cannot be accessed via connected geometry. */
BLI_assert(eheap_table[e_iter_index] != edge_free_id);
if (BM_edge_rotate_check(e_iter)) {
/* Previously degenerate, now valid. */
float cost = bm_edge_calc_rotate_cost(e_iter);
eheap_table[e_iter_index] = BLI_heap_insert(heap, cost, e_iter);
}
}
} while ((l_iter = l_iter->next) != l_radial_iter);
} while ((l_radial_iter = l_radial_iter->radial_next) != e_rotate->l);
}
}
}
/* If no actions were taken, move onto the next pass. */
if (edges_len_rotate == edges_len_rotate_prev) {
pass_type += 1;
continue;
}
}
BLI_heap_free(heap, nullptr);
MEM_delete(eheap_table);
}
void bmo_rotate_edges_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMEdge *e;
const int edges_len = BMO_slot_buffer_len(op->slots_in, "edges");
const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
const bool is_single = (edges_len == 1);
short check_flag = is_single ? BM_EDGEROT_CHECK_EXISTS :
BM_EDGEROT_CHECK_EXISTS | BM_EDGEROT_CHECK_DEGENERATE;
bool is_simple = true;
if (is_single == false) {
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
BMFace *f_pair[2];
if (BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
for (uint i = 0; i < ARRAY_SIZE(f_pair); i += 1) {
if (BMO_face_flag_test(bm, f_pair[i], FACE_MARK)) {
is_simple = false;
break;
}
BMO_face_flag_enable(bm, f_pair[i], FACE_MARK);
}
if (is_simple == false) {
break;
}
}
}
}
if (is_simple) {
bm_rotate_edges_simple(bm, op, check_flag, use_ccw);
}
else {
bm_rotate_edges_shared(bm, op, check_flag, use_ccw, edges_len);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
}
} // namespace blender

View File

@@ -0,0 +1,500 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Advanced smoothing.
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "eigen_capi.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
// #define SMOOTH_LAPLACIAN_AREA_FACTOR 4.0f /* UNUSED */
// #define SMOOTH_LAPLACIAN_EDGE_FACTOR 2.0f /* UNUSED */
#define SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE 1.8f
#define SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE 0.15f
struct BLaplacianSystem {
float *eweights; /* Length weights per Edge. */
float (*fweights)[3]; /* Cotangent weights per loop. */
float *ring_areas; /* Total area per ring. */
float *vlengths; /* Total sum of lengths(edges) per vertex. */
float *vweights; /* Total sum of weights per vertex. */
int numEdges; /* Number of edges. */
int numLoops; /* Number of loops. */
int numVerts; /* Number of verts. */
bool *zerola; /* Is zero area or length. */
/* Pointers to data. */
BMesh *bm;
BMOperator *op;
LinearSolver *context;
/* Data. */
float min_area;
};
using LaplacianSystem = BLaplacianSystem;
static bool vert_is_boundary(BMVert *v);
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts);
static void init_laplacian_matrix(LaplacianSystem *sys);
static void delete_laplacian_system(LaplacianSystem *sys);
static void delete_void_pointer(void *data);
static void fill_laplacian_matrix(LaplacianSystem *sys);
static void memset_laplacian_system(LaplacianSystem *sys, int val);
static void validate_solution(
LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume);
static void volume_preservation(
BMOperator *op, float vini, float vend, int usex, int usey, int usez);
static void delete_void_pointer(void *data)
{
if (data) {
MEM_delete_void(data);
}
}
static void delete_laplacian_system(LaplacianSystem *sys)
{
delete_void_pointer(sys->eweights);
delete_void_pointer(sys->fweights);
delete_void_pointer(sys->ring_areas);
delete_void_pointer(sys->vlengths);
delete_void_pointer(sys->vweights);
delete_void_pointer(sys->zerola);
if (sys->context) {
EIG_linear_solver_delete(sys->context);
}
sys->bm = nullptr;
sys->op = nullptr;
MEM_delete(sys);
}
static void memset_laplacian_system(LaplacianSystem *sys, int val)
{
memset(sys->eweights, val, sizeof(float) * sys->numEdges);
memset(sys->fweights, val, sizeof(float[3]) * sys->numLoops);
memset(sys->ring_areas, val, sizeof(float) * sys->numVerts);
memset(sys->vlengths, val, sizeof(float) * sys->numVerts);
memset(sys->vweights, val, sizeof(float) * sys->numVerts);
memset(sys->zerola, val, sizeof(bool) * sys->numVerts);
}
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts)
{
LaplacianSystem *sys;
sys = MEM_new_zeroed<LaplacianSystem>("ModLaplSmoothSystem");
sys->numEdges = a_numEdges;
sys->numLoops = a_numLoops;
sys->numVerts = a_numVerts;
sys->eweights = MEM_new_array_zeroed<float>(sys->numEdges, "ModLaplSmoothEWeight");
if (!sys->eweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->fweights = MEM_new_array_zeroed<float[3]>(sys->numLoops, "ModLaplSmoothFWeight");
if (!sys->fweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->ring_areas = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothRingAreas");
if (!sys->ring_areas) {
delete_laplacian_system(sys);
return nullptr;
}
sys->vlengths = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothVlengths");
if (!sys->vlengths) {
delete_laplacian_system(sys);
return nullptr;
}
sys->vweights = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothVweights");
if (!sys->vweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->zerola = MEM_new_array_zeroed<bool>(sys->numVerts, "ModLaplSmoothZeloa");
if (!sys->zerola) {
delete_laplacian_system(sys);
return nullptr;
}
return sys;
}
/**
* Compute weight between vertex v_i and all your neighbors
* weight between v_i and v_neighbor
* <pre>
* Wij = cot(alpha) + cot(beta) / (4.0 * total area of all faces * sum all weight)
*
* v_i *
* / | \
* / | \
* v_beta* | * v_alpha
* \ | /
* \ | /
* * v_neighbor
* </pre>
*/
static void init_laplacian_matrix(LaplacianSystem *sys)
{
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
uint i;
BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) || !BM_edge_is_boundary(e)) {
continue;
}
const float *v1 = e->v1->co;
const float *v2 = e->v2->co;
const int idv1 = BM_elem_index_get(e->v1);
const int idv2 = BM_elem_index_get(e->v2);
float w1 = len_v3v3(v1, v2);
if (w1 > sys->min_area) {
w1 = 1.0f / w1;
sys->eweights[i] = w1;
sys->vlengths[idv1] += w1;
sys->vlengths[idv2] += w1;
}
else {
sys->zerola[idv1] = true;
sys->zerola[idv2] = true;
}
}
uint l_curr_index = 0;
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
l_curr_index += f->len;
continue;
}
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
const int vi_prev = BM_elem_index_get(l_iter->prev->v);
const int vi_curr = BM_elem_index_get(l_iter->v);
const int vi_next = BM_elem_index_get(l_iter->next->v);
const float *co_prev = l_iter->prev->v->co;
const float *co_curr = l_iter->v->co;
const float *co_next = l_iter->next->v->co;
const float areaf = area_tri_v3(co_prev, co_curr, co_next);
if (areaf < sys->min_area) {
sys->zerola[vi_curr] = true;
}
sys->ring_areas[vi_prev] += areaf;
sys->ring_areas[vi_curr] += areaf;
sys->ring_areas[vi_next] += areaf;
const float w1 = cotangent_tri_weight_v3(co_curr, co_next, co_prev) / 2.0f;
const float w2 = cotangent_tri_weight_v3(co_next, co_prev, co_curr) / 2.0f;
const float w3 = cotangent_tri_weight_v3(co_prev, co_curr, co_next) / 2.0f;
sys->fweights[l_curr_index][0] += w1;
sys->fweights[l_curr_index][1] += w2;
sys->fweights[l_curr_index][2] += w3;
sys->vweights[vi_prev] += w1 + w2;
sys->vweights[vi_curr] += w2 + w3;
sys->vweights[vi_next] += w1 + w3;
} while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
}
}
static void fill_laplacian_matrix(LaplacianSystem *sys)
{
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
int i;
uint l_curr_index = 0;
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
l_curr_index += f->len;
continue;
}
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
int vi_prev = BM_elem_index_get(l_iter->prev->v);
int vi_curr = BM_elem_index_get(l_iter->v);
bool ok_prev = (sys->zerola[vi_prev] == false) && !vert_is_boundary(l_iter->prev->v);
bool ok_curr = (sys->zerola[vi_curr] == false) && !vert_is_boundary(l_iter->v);
do {
const int vi_next = BM_elem_index_get(l_iter->next->v);
const bool ok_next = (sys->zerola[vi_next] == false) && !vert_is_boundary(l_iter->next->v);
if (ok_prev) {
EIG_linear_solver_matrix_add(sys->context,
vi_prev,
vi_curr,
sys->fweights[l_curr_index][1] * sys->vweights[vi_prev]);
EIG_linear_solver_matrix_add(sys->context,
vi_prev,
vi_next,
sys->fweights[l_curr_index][0] * sys->vweights[vi_prev]);
}
if (ok_curr) {
EIG_linear_solver_matrix_add(sys->context,
vi_curr,
vi_next,
sys->fweights[l_curr_index][2] * sys->vweights[vi_curr]);
EIG_linear_solver_matrix_add(sys->context,
vi_curr,
vi_prev,
sys->fweights[l_curr_index][1] * sys->vweights[vi_curr]);
}
if (ok_next) {
EIG_linear_solver_matrix_add(sys->context,
vi_next,
vi_curr,
sys->fweights[l_curr_index][2] * sys->vweights[vi_next]);
EIG_linear_solver_matrix_add(sys->context,
vi_next,
vi_prev,
sys->fweights[l_curr_index][0] * sys->vweights[vi_next]);
}
vi_prev = vi_curr;
vi_curr = vi_next;
ok_prev = ok_curr;
ok_curr = ok_next;
} while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
}
BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) || !BM_edge_is_boundary(e)) {
continue;
}
const uint idv1 = BM_elem_index_get(e->v1);
const uint idv2 = BM_elem_index_get(e->v2);
if (sys->zerola[idv1] == false && sys->zerola[idv2] == false) {
EIG_linear_solver_matrix_add(
sys->context, idv1, idv2, sys->eweights[i] * sys->vlengths[idv1]);
EIG_linear_solver_matrix_add(
sys->context, idv2, idv1, sys->eweights[i] * sys->vlengths[idv2]);
}
}
}
static bool vert_is_boundary(BMVert *v)
{
BMEdge *ed;
BMFace *f;
BMIter ei;
BMIter fi;
BM_ITER_ELEM (ed, &ei, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(ed)) {
return true;
}
}
BM_ITER_ELEM (f, &fi, v, BM_FACES_OF_VERT) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
return true;
}
}
return false;
}
static void volume_preservation(
BMOperator *op, float vini, float vend, int usex, int usey, int usez)
{
float beta;
BMOIter siter;
BMVert *v;
if (vend != 0.0f) {
beta = pow(vini / vend, 1.0f / 3.0f);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (usex) {
v->co[0] *= beta;
}
if (usey) {
v->co[1] *= beta;
}
if (usez) {
v->co[2] *= beta;
}
}
}
}
static void validate_solution(
LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume)
{
int m_vertex_id;
float leni, lene;
float vini, vend;
float *vi1, *vi2, ve1[3], ve2[3];
uint idv1, idv2;
BMOIter siter;
BMVert *v;
BMEdge *e;
BMIter eiter;
BM_ITER_MESH (e, &eiter, sys->bm, BM_EDGES_OF_MESH) {
idv1 = BM_elem_index_get(e->v1);
idv2 = BM_elem_index_get(e->v2);
vi1 = e->v1->co;
vi2 = e->v2->co;
ve1[0] = EIG_linear_solver_variable_get(sys->context, 0, idv1);
ve1[1] = EIG_linear_solver_variable_get(sys->context, 1, idv1);
ve1[2] = EIG_linear_solver_variable_get(sys->context, 2, idv1);
ve2[0] = EIG_linear_solver_variable_get(sys->context, 0, idv2);
ve2[1] = EIG_linear_solver_variable_get(sys->context, 1, idv2);
ve2[2] = EIG_linear_solver_variable_get(sys->context, 2, idv2);
leni = len_v3v3(vi1, vi2);
lene = len_v3v3(ve1, ve2);
if (lene > leni * SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE ||
lene < leni * SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE)
{
sys->zerola[idv1] = true;
sys->zerola[idv2] = true;
}
}
if (preserve_volume) {
vini = BM_mesh_calc_volume(sys->bm, false);
}
BMO_ITER (v, &siter, sys->op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
if (sys->zerola[m_vertex_id] == false) {
if (usex) {
v->co[0] = EIG_linear_solver_variable_get(sys->context, 0, m_vertex_id);
}
if (usey) {
v->co[1] = EIG_linear_solver_variable_get(sys->context, 1, m_vertex_id);
}
if (usez) {
v->co[2] = EIG_linear_solver_variable_get(sys->context, 2, m_vertex_id);
}
}
}
if (preserve_volume) {
vend = BM_mesh_calc_volume(sys->bm, false);
volume_preservation(sys->op, vini, vend, usex, usey, usez);
}
}
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op)
{
int i;
int m_vertex_id;
bool usex, usey, usez, preserve_volume;
float lambda_factor, lambda_border;
float w;
BMOIter siter;
BMVert *v;
LaplacianSystem *sys;
if (bm->totface == 0) {
return;
}
sys = init_laplacian_system(bm->totedge, bm->totloop, bm->totvert);
if (!sys) {
return;
}
sys->bm = bm;
sys->op = op;
memset_laplacian_system(sys, 0);
BM_mesh_elem_index_ensure(bm, BM_VERT);
lambda_factor = BMO_slot_float_get(op->slots_in, "lambda_factor");
lambda_border = BMO_slot_float_get(op->slots_in, "lambda_border");
sys->min_area = 0.00001f;
usex = BMO_slot_bool_get(op->slots_in, "use_x");
usey = BMO_slot_bool_get(op->slots_in, "use_y");
usez = BMO_slot_bool_get(op->slots_in, "use_z");
preserve_volume = BMO_slot_bool_get(op->slots_in, "preserve_volume");
sys->context = EIG_linear_least_squares_solver_new(bm->totvert, bm->totvert, 3);
for (i = 0; i < bm->totvert; i++) {
EIG_linear_solver_variable_lock(sys->context, i);
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
EIG_linear_solver_variable_unlock(sys->context, m_vertex_id);
EIG_linear_solver_variable_set(sys->context, 0, m_vertex_id, v->co[0]);
EIG_linear_solver_variable_set(sys->context, 1, m_vertex_id, v->co[1]);
EIG_linear_solver_variable_set(sys->context, 2, m_vertex_id, v->co[2]);
}
init_laplacian_matrix(sys);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
EIG_linear_solver_right_hand_side_add(sys->context, 0, m_vertex_id, v->co[0]);
EIG_linear_solver_right_hand_side_add(sys->context, 1, m_vertex_id, v->co[1]);
EIG_linear_solver_right_hand_side_add(sys->context, 2, m_vertex_id, v->co[2]);
i = m_vertex_id;
if ((sys->zerola[i] == false) &&
/* Non zero check is to account for vertices that aren't connected to a selected face.
* Without this wire edges become `nan`, see #89214. */
(sys->ring_areas[i] != 0.0f))
{
w = sys->vweights[i] * sys->ring_areas[i];
sys->vweights[i] = (w == 0.0f) ? 0.0f : -lambda_factor / (4.0f * w);
w = sys->vlengths[i];
sys->vlengths[i] = (w == 0.0f) ? 0.0f : -lambda_border * 2.0f / w;
if (!vert_is_boundary(v)) {
EIG_linear_solver_matrix_add(
sys->context, i, i, 1.0f + lambda_factor / (4.0f * sys->ring_areas[i]));
}
else {
EIG_linear_solver_matrix_add(sys->context, i, i, 1.0f + lambda_border * 2.0f);
}
}
else {
EIG_linear_solver_matrix_add(sys->context, i, i, 1.0f);
}
}
fill_laplacian_matrix(sys);
if (EIG_linear_solver_solve(sys->context)) {
validate_solution(sys, usex, usey, usez, preserve_volume);
}
delete_laplacian_system(sys);
}
} // namespace blender

View File

@@ -0,0 +1,415 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Distributes vertices evenly along one or more edge loops.
* Endpoints of edge loops are not modified unless the loop is
* cyclic.
*
* Vertices along the edge loop are redistributed to uniform spacing
* based on the cumulative length of the edge loop and are interpolated
* either smoothly via a natural cubic spline or linearly.
*/
#include <optional>
#include "BLI_math_vector.hh"
#include "BLI_binary_search.hh"
#include "BLI_length_parameterize.hh"
#include "BLI_math_geom.h"
#include "BLI_math_solvers.h"
#include "BLI_set.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/** Used as a threshold to decide if all vertices are at the same position. */
constexpr float DUPLICATE_POSITION_THRESHOLD = 1e-6f;
/** Epsilon to prevent zero division. */
constexpr float SPACE_EPSILON = 1e-8f;
/**
* A chain of vertices collected from a walk along connected edges.
*/
struct SpaceChainData {
/** Ordered vertices from one end of the chain to the other. */
Vector<BMVert *> verts;
/** True if the path forms a closed ring. */
bool is_closed = false;
};
/**
* Stores measured and target distances for a vertex chain.
*/
struct SpaceMeasurements {
/** 3D coordinates of the vertices. */
Array<float3> positions;
/**
* Cumulative vertex distances along the chain.
* For cyclic chains, this array has a length of "positions.size() + 1" to store
* the total chain distance at the end, so values don't need to be wrapped.
*/
Array<float> knot_distances;
};
/**
* Coefficients for the cubic spline curve equation, calculated per coordinate axis.
*/
struct SplineCoeffs {
/** Value at the start of the segment. */
float a;
/** First-order coefficient. */
float b;
/** Second-order coefficient. */
float c;
/** Third-order coefficient. */
float d;
/** Parameter value at the start of the segment. */
float x;
};
/**
* Return the next tagged edge to walk from `v`, or null.
* A null return will occur:
* - When no other edge can be found.
* - When there are 3+ connected edges (a logical "junction").
*/
static BMEdge *vert_next_walk_edge(BMVert *v, const Set<BMEdge *> &visited)
{
BMEdge *e_next = nullptr;
int tagged_count = 0;
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
continue;
}
tagged_count++;
if (tagged_count >= 3) {
return nullptr;
}
if (!e_next && !visited.contains(e)) {
e_next = e;
}
}
return e_next;
}
/**
* Walk from start_edge in both directions and return the resulting vertex chain.
* Returns std::nullopt when all vertices are at the same position.
*/
static std::optional<SpaceChainData> walk_edges(BMEdge *start_edge, Set<BMEdge *> &r_visited)
{
SpaceChainData chain_data;
Set<BMVert *> visited_verts;
chain_data.verts.append(start_edge->v1);
chain_data.verts.append(start_edge->v2);
visited_verts.add(start_edge->v1);
visited_verts.add(start_edge->v2);
r_visited.add(start_edge);
auto walk_fn = [&](BMVert *v_curr, Vector<BMVert *> &result) {
while (true) {
BMEdge *e_next = vert_next_walk_edge(v_curr, r_visited);
if (!e_next) {
break;
}
BMVert *v_next = BM_edge_other_vert(e_next, v_curr);
if (visited_verts.contains(v_next)) {
break;
}
v_curr = v_next;
visited_verts.add(v_curr);
result.append(v_curr);
r_visited.add(e_next);
}
};
/* The initial edge direction (v1 -> v2) is arbitrary.
* We walk from v2 to extend this sequence. */
walk_fn(start_edge->v2, chain_data.verts);
Vector<BMVert *> pre_chain;
walk_fn(start_edge->v1, pre_chain);
if (!pre_chain.is_empty()) {
std::ranges::reverse(pre_chain);
pre_chain.extend(chain_data.verts);
chain_data.verts = std::move(pre_chain);
}
/* Skip chains where all vertices are at the same location. */
bool all_duplicate = true;
for (const int i : chain_data.verts.index_range().drop_back(1)) {
if (math::distance_squared(float3(chain_data.verts[i]->co),
float3(chain_data.verts[i + 1]->co)) >
math::square(DUPLICATE_POSITION_THRESHOLD))
{
all_duplicate = false;
break;
}
}
if (all_duplicate) {
return std::nullopt;
}
/* Close the ring, ensuring the closing vertex is *not* a junction. */
BMVert *v_first = chain_data.verts.first();
BMVert *v_last = chain_data.verts.last();
BMEdge *closing_edge = BM_edge_exists(v_first, v_last);
if (closing_edge && BM_elem_flag_test(closing_edge, BM_ELEM_TAG) &&
vert_next_walk_edge(v_first, r_visited) == closing_edge &&
vert_next_walk_edge(v_last, r_visited) == closing_edge)
{
r_visited.add(closing_edge);
chain_data.is_closed = true;
}
else {
chain_data.is_closed = false;
}
return chain_data;
}
/**
* Build vertex chains from selected edges.
*/
static void get_space_input_chains(BMesh *bm, Vector<SpaceChainData> &r_chains)
{
Set<BMEdge *> visited;
BMIter iter;
BMEdge *edge;
BM_ITER_MESH (edge, &iter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(edge, BM_ELEM_TAG) || visited.contains(edge)) {
continue;
}
std::optional<SpaceChainData> chain = walk_edges(edge, visited);
if (chain) {
r_chains.append(std::move(*chain));
}
}
}
/**
* Compute cumulative distances along the chain.
*/
static SpaceMeasurements measure_chain(const SpaceChainData &chain)
{
SpaceMeasurements measure;
const int verts_num = chain.verts.size();
measure.positions.reinitialize(verts_num);
for (const int i : IndexRange(verts_num)) {
measure.positions[i] = float3(chain.verts[i]->co);
}
measure.knot_distances.reinitialize(verts_num + (chain.is_closed ? 1 : 0));
measure.knot_distances[0] = 0.0f;
length_parameterize::accumulate_lengths<float3>(
measure.positions, chain.is_closed, measure.knot_distances.as_mutable_span().drop_front(1));
return measure;
}
/**
* Compute cubic spline coefficients for one coordinate axis.
*/
static void calculate_splines_axis(Span<float> distances,
Span<float> coords,
const bool is_closed,
Vector<SplineCoeffs> &r_coeffs)
{
const int verts_num = coords.size();
if (verts_num < 2) {
return;
}
const int num_segments = is_closed ? verts_num : verts_num - 1;
Array<float> segment_length(num_segments);
for (const int i : IndexRange(num_segments)) {
segment_length[i] = distances[i + 1] - distances[i];
if (!(segment_length[i] > 0.0f)) {
segment_length[i] = SPACE_EPSILON;
}
}
/* Stores second derivative coefficients. For a natural cubic spline, the boundary
* condition defines the first and last points as zero. */
Array<float> c_vals(verts_num, 0.0f);
/* The Thomas algorithm used in `BLI_tridiagonal_solve` can't properly solve
* a cyclic tridiagonal system so in this case, we use the Sherman-Morrison formula
* via `BLI_tridiagonal_solve_cyclic`. */
if (is_closed) {
Array<float> lower_diag(verts_num);
Array<float> diag(verts_num);
Array<float> upper_diag(verts_num);
Array<float> rhs(verts_num);
for (const int i : IndexRange(verts_num)) {
const int i_prev = math::mod_periodic(i - 1, verts_num);
const int i_next = math::mod_periodic(i + 1, verts_num);
lower_diag[i] = segment_length[i_prev];
diag[i] = 2.0f * (segment_length[i_prev] + segment_length[i]);
upper_diag[i] = segment_length[i];
rhs[i] = 3.0f * (((coords[i_next] - coords[i]) / segment_length[i]) -
((coords[i] - coords[i_prev]) / segment_length[i_prev]));
}
BLI_tridiagonal_solve_cyclic(
lower_diag.data(), diag.data(), upper_diag.data(), rhs.data(), c_vals.data(), verts_num);
}
else {
/* For a natural cubic spline the curvature at the first and last point
* is 0, so for n given points, we only have n-2 unknown interior points. */
const int interior = verts_num - 2;
Array<float> lower_diag(interior);
Array<float> diag(interior);
Array<float> upper_diag(interior);
Array<float> rhs(interior);
for (const int i_curr : IndexRange(interior)) {
const int i_next = i_curr + 1;
lower_diag[i_curr] = segment_length[i_curr];
diag[i_curr] = 2.0f * (segment_length[i_curr] + segment_length[i_next]);
upper_diag[i_curr] = segment_length[i_next];
rhs[i_curr] = 3.0f * (((coords[i_next + 1] - coords[i_next]) / segment_length[i_next]) -
((coords[i_next] - coords[i_curr]) / segment_length[i_curr]));
}
BLI_tridiagonal_solve(lower_diag.data(),
diag.data(),
upper_diag.data(),
rhs.data(),
c_vals.data() + 1,
interior);
}
/* Build polynomial coefficients for each segment. */
for (const int i : IndexRange(num_segments)) {
const int i_next = is_closed ? math::mod_periodic(i + 1, verts_num) : i + 1;
const float coeff_a = coords[i];
const float coeff_b = ((coords[i_next] - coords[i]) / segment_length[i]) -
(segment_length[i] * (c_vals[i_next] + 2.0f * c_vals[i])) / 3.0f;
const float coeff_c = c_vals[i];
const float coeff_d = (c_vals[i_next] - c_vals[i]) / (3.0f * segment_length[i]);
r_coeffs.append({coeff_a, coeff_b, coeff_c, coeff_d, distances[i]});
}
}
/** Return the index of the spline segment that contains target_distance. */
static int calc_spline_segment(Span<float> knot_distances, const float target_distance)
{
const int segment_index = binary_search::last_if(
knot_distances, [&](const float value) { return value <= target_distance; });
return std::clamp(segment_index, 0, int(knot_distances.size()) - 2);
}
/** Evaluates the cubic spline at target_distance. */
static float3 evaluate_cubic(Span<float> tknots,
Span<SplineCoeffs> coeffs_x,
Span<SplineCoeffs> coeffs_y,
Span<SplineCoeffs> coeffs_z,
const float target_distance)
{
const int segment = calc_spline_segment(tknots, target_distance);
const float dt = target_distance - coeffs_x[segment].x;
const SplineCoeffs &cx = coeffs_x[segment];
const SplineCoeffs &cy = coeffs_y[segment];
const SplineCoeffs &cz = coeffs_z[segment];
return float3(cx.a + dt * (cx.b + dt * (cx.c + dt * cx.d)),
cy.a + dt * (cy.b + dt * (cy.c + dt * cy.d)),
cz.a + dt * (cz.b + dt * (cz.c + dt * cz.d)));
}
void bmo_space_edge_loops_evenly_exec(BMesh *bm, BMOperator *op)
{
const float factor = BMO_slot_float_get(op->slots_in, "factor");
const SpaceInterpolationMethod interpolation = static_cast<SpaceInterpolationMethod>(
BMO_slot_int_get(op->slots_in, "interpolation"));
const bool lock_x = BMO_slot_bool_get(op->slots_in, "lock_x");
const bool lock_y = BMO_slot_bool_get(op->slots_in, "lock_y");
const bool lock_z = BMO_slot_bool_get(op->slots_in, "lock_z");
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "geom", BM_EDGE, BM_ELEM_TAG, false);
Vector<SpaceChainData> chains;
get_space_input_chains(bm, chains);
for (SpaceChainData &chain : chains) {
const int verts_num = chain.verts.size();
SpaceMeasurements measure = measure_chain(chain);
Array<int> sample_indices(verts_num);
Array<float> sample_factors(verts_num);
length_parameterize::sample_uniform(measure.knot_distances.as_span().drop_front(1),
!chain.is_closed,
sample_indices,
sample_factors);
Array<float3> new_positions(verts_num);
if (interpolation == SPACE_EDGE_LOOPS_EVENLY_INTERP_LINEAR) {
length_parameterize::interpolate<float3>(
measure.positions, sample_indices, sample_factors, new_positions);
}
else {
Array<float> coords_x(verts_num);
Array<float> coords_y(verts_num);
Array<float> coords_z(verts_num);
for (const int i : IndexRange(verts_num)) {
coords_x[i] = measure.positions[i].x;
coords_y[i] = measure.positions[i].y;
coords_z[i] = measure.positions[i].z;
}
Vector<SplineCoeffs> coeffs_x;
Vector<SplineCoeffs> coeffs_y;
Vector<SplineCoeffs> coeffs_z;
calculate_splines_axis(measure.knot_distances, coords_x, chain.is_closed, coeffs_x);
calculate_splines_axis(measure.knot_distances, coords_y, chain.is_closed, coeffs_y);
calculate_splines_axis(measure.knot_distances, coords_z, chain.is_closed, coeffs_z);
for (const int i : IndexRange(verts_num)) {
const int seg = sample_indices[i];
const float target_dist = math::interpolate(
measure.knot_distances[seg], measure.knot_distances[seg + 1], sample_factors[i]);
new_positions[i] = evaluate_cubic(
measure.knot_distances, coeffs_x, coeffs_y, coeffs_z, target_dist);
}
}
for (const int i : IndexRange(verts_num)) {
/* The first and last vertices of an open chain are anchor points so they are skipped. */
if (!chain.is_closed && (i == 0 || i == verts_num - 1)) {
continue;
}
float3 new_pos = new_positions[i];
if (lock_x) {
new_pos.x = measure.positions[i].x;
}
if (lock_y) {
new_pos.y = measure.positions[i].y;
}
if (lock_z) {
new_pos.z = measure.positions[i].z;
}
float3 final_pos = math::interpolate(measure.positions[i], new_pos, factor);
copy_v3_v3(chain.verts[i]->co, final_pos);
}
}
}
} // namespace blender

View File

@@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Just a wrapper around #BM_mesh_edgesplit
*/
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_split_edges_exec(BMesh *bm, BMOperator *op)
{
const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts");
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "edges", BM_EDGE, BM_ELEM_TAG, false);
if (use_verts) {
/* this slows down the operation but its ok because the modifier doesn't use */
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "verts", BM_VERT, BM_ELEM_TAG, false);
}
/* this is where everything happens */
BM_mesh_edgesplit(bm, use_verts, true, false);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Makes the mesh symmetrical by splitting along an axis and duplicating the geometry.
*/
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
#define ELE_OUT 1
void bmo_symmetrize_exec(BMesh *bm, BMOperator *op)
{
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const int direction = BMO_slot_int_get(op->slots_in, "direction");
const int axis = direction % 3;
BMOperator op_bisect;
BMOperator op_dupe;
BMOperator op_weld;
BMOpSlot *slot_vertmap;
BMOpSlot *slot_targetmap;
float plane_no[3];
float scale[3];
BMOIter siter;
BMVert *v;
copy_v3_fl(plane_no, 0.0f);
copy_v3_fl(scale, 1.0f);
plane_no[axis] = direction > 2 ? -1.0f : 1.0f;
scale[axis] *= -1.0f;
/* Cut in half */
BMO_op_initf(bm,
&op_bisect,
op->flag,
"bisect_plane geom=%s plane_no=%v dist=%f clear_outer=%b use_snap_center=%b",
op,
"input",
plane_no,
dist,
true,
true);
BMO_op_exec(bm, &op_bisect);
/* Duplicate */
BMO_op_initf(bm, &op_dupe, op->flag, "duplicate geom=%S", &op_bisect, "geom.out");
BMO_op_exec(bm, &op_dupe);
/* Flag for output (some will be merged) */
BMO_slot_buffer_flag_enable(bm, op_bisect.slots_out, "geom.out", BM_ALL_NOLOOP, ELE_OUT);
BMO_slot_buffer_flag_enable(bm, op_dupe.slots_out, "geom.out", BM_ALL_NOLOOP, ELE_OUT);
BMO_op_callf(bm,
op->flag,
"scale verts=%S vec=%v use_shapekey=%s",
&op_dupe,
"geom.out",
scale,
op,
"use_shapekey");
/* important 'flip_multires' is disabled,
* otherwise multi-res data will be reversed, see: #47788 */
BMO_op_callf(bm, op->flag, "reverse_faces faces=%S", &op_dupe, "geom.out");
/* Weld verts */
BMO_op_init(bm, &op_weld, op->flag, "weld_verts");
slot_vertmap = BMO_slot_get(op_dupe.slots_out, "vert_map.out");
slot_targetmap = BMO_slot_get(op_weld.slots_in, "targetmap");
BMO_ITER (v, &siter, op_bisect.slots_out, "geom_cut.out", BM_VERT) {
BMVert *v_dupe = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_vertmap, v));
BMO_slot_map_elem_insert(&op_weld, slot_targetmap, v_dupe, v);
}
BMO_op_exec(bm, &op_weld);
/* Cleanup */
BMO_op_finish(bm, &op_weld);
BMO_op_finish(bm, &op_dupe);
BMO_op_finish(bm, &op_bisect);
/* Create output */
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, ELE_OUT);
}
} // namespace blender

View File

@@ -0,0 +1,295 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Triangulate faces, also defines triangle fill.
*/
#include "MEM_guardedalloc.h"
#include "DNA_listBase.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_scanfill.h"
#include "BLI_sort_utils.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
#define ELE_NEW 1
#define EDGE_MARK 4
void bmo_triangulate_exec(BMesh *bm, BMOperator *op)
{
const int quad_method = BMO_slot_int_get(op->slots_in, "quad_method");
const int ngon_method = BMO_slot_int_get(op->slots_in, "ngon_method");
BMOpSlot *slot_facemap_out = BMO_slot_get(op->slots_out, "face_map.out");
BMOpSlot *slot_facemap_double_out = BMO_slot_get(op->slots_out, "face_map_double.out");
BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "faces", BM_FACE, BM_ELEM_TAG, false);
BM_mesh_triangulate(
bm, quad_method, ngon_method, 4, true, op, slot_facemap_out, slot_facemap_double_out);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
}
struct SortNormal {
float value; /* keep first */
float no[3];
};
void bmo_triangle_fill_exec(BMesh *bm, BMOperator *op)
{
const bool use_beauty = BMO_slot_bool_get(op->slots_in, "use_beauty");
const bool use_dissolve = BMO_slot_bool_get(op->slots_in, "use_dissolve");
BMOIter siter;
BMEdge *e;
ScanFillContext sf_ctx;
// ScanFillEdge *sf_edge; /* UNUSED */
float normal[3];
const int scanfill_flag = BLI_SCANFILL_CALC_HOLES | BLI_SCANFILL_CALC_POLYS |
BLI_SCANFILL_CALC_LOOSE;
uint nors_tot;
bool calc_winding = false;
Map<BMVert *, ScanFillVert *> sf_vert_map;
sf_vert_map.reserve(BMO_slot_buffer_len(op->slots_in, "edges"));
BMO_slot_vec_get(op->slots_in, "normal", normal);
BLI_scanfill_begin(&sf_ctx);
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
ScanFillVert *sf_verts[2];
BMVert **e_verts = &e->v1;
uint i;
BMO_edge_flag_enable(bm, e, EDGE_MARK);
calc_winding = (calc_winding || BM_edge_is_boundary(e));
for (i = 0; i < 2; i++) {
sf_verts[i] = sf_vert_map.lookup_or_add_cb(e_verts[i], [&]() {
ScanFillVert *sf_vert = BLI_scanfill_vert_add(&sf_ctx, e_verts[i]->co);
sf_vert->tmp.p = e_verts[i];
return sf_vert;
});
}
/* sf_edge = */ BLI_scanfill_edge_add(&sf_ctx, UNPACK2(sf_verts));
// sf_edge->tmp.p = e; /* UNUSED */
}
nors_tot = sf_vert_map.size();
if (is_zero_v3(normal)) {
/* calculate the normal from the cross product of vert-edge pairs.
* Since we don't know winding, just accumulate */
ScanFillVert *sf_vert;
SortNormal *nors;
uint i;
bool is_degenerate = true;
nors = MEM_new_array_uninitialized<SortNormal>(nors_tot, __func__);
for (sf_vert = static_cast<ScanFillVert *>(sf_ctx.fillvertbase.first), i = 0; sf_vert;
sf_vert = sf_vert->next, i++)
{
BMVert *v = static_cast<BMVert *>(sf_vert->tmp.p);
BMIter eiter;
BMEdge *e_pair[2];
uint e_index = 0;
nors[i].value = -1.0f;
/* only use if 'is_degenerate' stays true */
add_v3_v3(normal, v->no);
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e, EDGE_MARK)) {
if (e_index == 2) {
e_index = 0;
break;
}
e_pair[e_index++] = e;
}
}
if (e_index == 2) {
float dir_a[3], dir_b[3];
is_degenerate = false;
sub_v3_v3v3(dir_a, v->co, BM_edge_other_vert(e_pair[0], v)->co);
sub_v3_v3v3(dir_b, v->co, BM_edge_other_vert(e_pair[1], v)->co);
cross_v3_v3v3(nors[i].no, dir_a, dir_b);
nors[i].value = len_squared_v3(nors[i].no);
/* only to get deterministic behavior (for initial normal) */
if (len_squared_v3(dir_a) > len_squared_v3(dir_b)) {
negate_v3(nors[i].no);
}
}
}
if (UNLIKELY(is_degenerate)) {
/* no vertices have 2 edges?
* in this case fall back to the average vertex normals */
}
else {
qsort(nors, nors_tot, sizeof(*nors), BLI_sortutil_cmp_float_reverse);
copy_v3_v3(normal, nors[0].no);
for (i = 0; i < nors_tot; i++) {
if (UNLIKELY(nors[i].value == -1.0f)) {
break;
}
if (dot_v3v3(normal, nors[i].no) < 0.0f) {
negate_v3(nors[i].no);
}
add_v3_v3(normal, nors[i].no);
}
normalize_v3(normal);
}
MEM_delete(nors);
}
else {
calc_winding = false;
}
/* in this case we almost certainly have degenerate geometry,
* better set a fallback value as a last resort */
if (UNLIKELY(normalize_v3(normal) == 0.0f)) {
normal[2] = 1.0f;
}
BLI_scanfill_calc_ex(&sf_ctx, scanfill_flag, normal);
/* if we have existing faces, base winding on those */
if (calc_winding) {
int winding_votes = 0;
for (ScanFillFace &sf_tri : sf_ctx.fillfacebase) {
BMVert *v_tri[3] = {static_cast<BMVert *>(sf_tri.v1->tmp.p),
static_cast<BMVert *>(sf_tri.v2->tmp.p),
static_cast<BMVert *>(sf_tri.v3->tmp.p)};
uint i, i_prev;
for (i = 0, i_prev = 2; i < 3; i_prev = i++) {
e = BM_edge_exists(v_tri[i], v_tri[i_prev]);
if (e && BM_edge_is_boundary(e) && BMO_edge_flag_test(bm, e, EDGE_MARK)) {
winding_votes += (e->l->v == v_tri[i]) ? 1 : -1;
}
}
}
if (winding_votes < 0) {
for (ScanFillFace &sf_tri : sf_ctx.fillfacebase) {
std::swap(sf_tri.v2, sf_tri.v3);
}
}
}
for (ScanFillFace &sf_tri : sf_ctx.fillfacebase) {
BMFace *f;
BMLoop *l;
BMIter liter;
f = BM_face_create_quad_tri(bm,
static_cast<BMVert *>(sf_tri.v1->tmp.p),
static_cast<BMVert *>(sf_tri.v2->tmp.p),
static_cast<BMVert *>(sf_tri.v3->tmp.p),
nullptr,
nullptr,
BM_CREATE_NO_DOUBLE);
BMO_face_flag_enable(bm, f, ELE_NEW);
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (!BMO_edge_flag_test(bm, l->e, EDGE_MARK)) {
BMO_edge_flag_enable(bm, l->e, ELE_NEW);
}
}
}
BLI_scanfill_end(&sf_ctx);
if (use_beauty) {
BMOperator bmop;
BMO_op_initf(bm, &bmop, op->flag, "beautify_fill faces=%ff edges=%Fe", ELE_NEW, EDGE_MARK);
BMO_op_exec(bm, &bmop);
BMO_slot_buffer_flag_enable(bm, bmop.slots_out, "geom.out", BM_FACE | BM_EDGE, ELE_NEW);
BMO_op_finish(bm, &bmop);
}
if (use_dissolve) {
BMEdge *e_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, ELE_NEW)) {
/* in rare cases the edges face will have already been removed from the edge */
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e, &l_a, &l_b) &&
/* This ensures we don't delete existing faces attached to the geometry being filled.
* The likely cause of this will have been a duplicate, where the newly created
* face was removed and the existing (un-tagged) face kept.
* Follow the rule of not deleting geometry unrelated to the fill. */
(BMO_face_flag_test(bm, l_a->f, ELE_NEW) && BMO_face_flag_test(bm, l_b->f, ELE_NEW)))
[[likely]]
{
BMFace *f_double;
BMFace *f_new = BM_faces_join_pair(bm, e->l, e->l->radial_next, false, &f_double);
if (f_double) [[unlikely]] {
/* NOTE(@ideasman42): Regarding duplicate faces.
* The common case for filling is to select an empty region and fill it.
* In general filling over and existing filled area isn't likely to work well,
* so anything done here is more to avoid errors - not part of a typical workflow.
*
* - It's important never to finish with duplicate faces (an *invalid* mesh).
* - Remove the "new" face because having a "fill" action
* delete existing geometry is unexpected and could cause problems
* if the caller doesn't know to account for this.
* - This face may be an *intermediate* state - where multiple edges
* would be collapsed to create the final face.
* Unfortunately this isn't currently handled as well as it might be,
* it may be better to fill a temporary mesh, then apply the final result,
* so we only have to deal with final duplicates (not intermediate ones).
*/
if (f_new) {
BM_face_kill(bm, f_new);
}
BM_edge_kill(bm, e);
}
else if (f_new) {
BMO_face_flag_enable(bm, f_new, ELE_NEW);
BM_edge_kill(bm, e);
}
}
else if (e->l == nullptr) {
BM_edge_kill(bm, e);
}
else {
/* Edges with 1 or 3+ faces attached,
* most likely caused by a degenerate mesh. */
}
}
}
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_EDGE | BM_FACE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Pattern based geometry reduction which has the result similar to undoing
* a subdivide operation.
*/
#include "BLI_math_base.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_unsubdivide_exec(BMesh *bm, BMOperator *op)
{
/* - `BMVert.flag & BM_ELEM_TAG`: Shows we touched this vert.
* - `BMVert.index == -1`: Shows we will remove this vert. */
BMVert *v;
BMIter iter;
const int iterations = max_ii(1, BMO_slot_int_get(op->slots_in, "iterations"));
BMOpSlot *vinput = BMO_slot_get(op->slots_in, "verts");
BMVert **vinput_arr = reinterpret_cast<BMVert **>(vinput->data.buf);
int v_index;
/* tag verts */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_disable(v, BM_ELEM_TAG);
}
for (v_index = 0; v_index < vinput->len; v_index++) {
v = vinput_arr[v_index];
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
/* do all the real work here */
BM_mesh_decimate_unsubdivide_ex(bm, iterations, true);
}
} // namespace blender

View File

@@ -0,0 +1,772 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* utility bmesh operators, e.g. transform,
* translate, rotate, scale, etc.
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BLI_array.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BKE_attribute.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Create Vert
* \{ */
#define ELE_NEW 1
void bmo_create_vert_exec(BMesh *bm, BMOperator *op)
{
float vec[3];
BMO_slot_vec_get(op->slots_in, "co", vec);
BMO_vert_flag_enable(bm, BM_vert_create(bm, vec, nullptr, BM_CREATE_NOP), ELE_NEW);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "vert.out", BM_VERT, ELE_NEW);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Transform
* \{ */
void bmo_transform_exec(BMesh *bm, BMOperator *op)
{
BMOIter iter;
BMVert *v;
float mat[4][4], mat_space[4][4], imat_space[4][4];
const uint shape_keys_len = BMO_slot_bool_get(op->slots_in, "use_shapekey") ?
CustomData_number_of_layers(&bm->vdata, CD_SHAPEKEY) :
0;
const uint cd_shape_key_offset = CustomData_get_offset(&bm->vdata, CD_SHAPEKEY);
BMO_slot_mat4_get(op->slots_in, "matrix", mat);
BMO_slot_mat4_get(op->slots_in, "space", mat_space);
if (!is_zero_m4(mat_space)) {
invert_m4_m4(imat_space, mat_space);
mul_m4_series(mat, imat_space, mat, mat_space);
}
BMO_ITER (v, &iter, op->slots_in, "verts", BM_VERT) {
mul_m4_v3(mat, v->co);
if (shape_keys_len != 0) {
float (*co_dst)[3] = static_cast<float (*)[3]>(
BM_ELEM_CD_GET_VOID_P(v, cd_shape_key_offset));
for (int i = 0; i < shape_keys_len; i++, co_dst++) {
mul_m4_v3(mat, *co_dst);
}
}
}
}
void bmo_translate_exec(BMesh *bm, BMOperator *op)
{
float mat[4][4], vec[3];
BMO_slot_vec_get(op->slots_in, "vec", vec);
unit_m4(mat);
copy_v3_v3(mat[3], vec);
BMO_op_callf(bm,
op->flag,
"transform matrix=%m4 space=%s verts=%s use_shapekey=%s",
mat,
op,
"space",
op,
"verts",
op,
"use_shapekey");
}
void bmo_scale_exec(BMesh *bm, BMOperator *op)
{
float mat[3][3], vec[3];
BMO_slot_vec_get(op->slots_in, "vec", vec);
unit_m3(mat);
mat[0][0] = vec[0];
mat[1][1] = vec[1];
mat[2][2] = vec[2];
BMO_op_callf(bm,
op->flag,
"transform matrix=%m3 space=%s verts=%s use_shapekey=%s",
mat,
op,
"space",
op,
"verts",
op,
"use_shapekey");
}
void bmo_rotate_exec(BMesh *bm, BMOperator *op)
{
float center[3];
float mat[4][4];
BMO_slot_vec_get(op->slots_in, "cent", center);
BMO_slot_mat4_get(op->slots_in, "matrix", mat);
transform_pivot_set_m4(mat, center);
BMO_op_callf(bm,
op->flag,
"transform matrix=%m4 space=%s verts=%s use_shapekey=%s",
mat,
op,
"space",
op,
"verts",
op,
"use_shapekey");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reverse Faces
* \{ */
void bmo_reverse_faces_exec(BMesh *bm, BMOperator *op)
{
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
const bool use_loop_mdisp_flip = BMO_slot_bool_get(op->slots_in, "flip_multires");
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
BM_face_normal_flip_ex(bm, f, cd_loop_mdisp_offset, use_loop_mdisp_flip);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Flip Quad Tessellation
* \{ */
void bmo_flip_quad_tessellation_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMFace *f;
bool changed = false;
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len == 4) {
f->l_first = f->l_first->next;
changed = true;
}
}
if (changed) {
bm->elem_index_dirty |= BM_LOOP;
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Region Extend
* \{ */
#define SEL_FLAG 1
#define SEL_ORIG 2
static void bmo_face_flag_set_flush(BMesh *bm, BMFace *f, const short oflag, const bool value)
{
BMLoop *l_iter;
BMLoop *l_first;
BMO_face_flag_set(bm, f, oflag, value);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMO_edge_flag_set(bm, l_iter->e, oflag, value);
BMO_vert_flag_set(bm, l_iter->v, oflag, value);
} while ((l_iter = l_iter->next) != l_first);
}
static void bmo_region_extend_expand(BMesh *bm,
BMOperator *op,
const bool use_faces,
const bool use_faces_step)
{
BMOIter siter;
if (!use_faces) {
BMVert *v;
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
bool found = false;
{
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, SEL_ORIG) && !BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
found = true;
break;
}
}
}
if (found) {
if (!use_faces_step) {
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, SEL_FLAG) && !BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
BMO_edge_flag_enable(bm, e, SEL_FLAG);
BMO_vert_flag_enable(bm, BM_edge_other_vert(e, v), SEL_FLAG);
}
}
}
else {
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, SEL_FLAG) && !BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
bmo_face_flag_set_flush(bm, f, SEL_FLAG, true);
}
}
/* handle wire edges (when stepping over faces) */
{
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_wire(e)) {
if (!BMO_edge_flag_test(bm, e, SEL_FLAG) && !BM_elem_flag_test(e, BM_ELEM_HIDDEN))
{
BMO_edge_flag_enable(bm, e, SEL_FLAG);
BMO_vert_flag_enable(bm, BM_edge_other_vert(e, v), SEL_FLAG);
}
}
}
}
}
}
}
}
else {
BMFace *f;
BMO_ITER (f, &siter, op->slots_in, "geom", BM_FACE) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (!use_faces_step) {
BMIter fiter;
BMFace *f_other;
BM_ITER_ELEM (f_other, &fiter, l->e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f_other, SEL_ORIG | SEL_FLAG) &&
!BM_elem_flag_test(f_other, BM_ELEM_HIDDEN))
{
BMO_face_flag_enable(bm, f_other, SEL_FLAG);
}
}
}
else {
BMIter fiter;
BMFace *f_other;
BM_ITER_ELEM (f_other, &fiter, l->v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f_other, SEL_ORIG | SEL_FLAG) &&
!BM_elem_flag_test(f_other, BM_ELEM_HIDDEN))
{
BMO_face_flag_enable(bm, f_other, SEL_FLAG);
}
}
}
}
}
}
}
static void bmo_region_extend_contract(BMesh *bm,
BMOperator *op,
const bool use_faces,
const bool use_faces_step)
{
BMOIter siter;
if (!use_faces) {
BMVert *v;
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
bool found = false;
if (!use_faces_step) {
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, SEL_ORIG)) {
found = true;
break;
}
}
}
else {
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, SEL_ORIG)) {
found = true;
break;
}
}
/* handle wire edges (when stepping over faces) */
if (!found) {
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_wire(e)) {
if (!BMO_edge_flag_test(bm, e, SEL_ORIG)) {
found = true;
break;
}
}
}
}
}
if (found) {
BMIter eiter;
BMEdge *e;
BMO_vert_flag_enable(bm, v, SEL_FLAG);
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
BMO_edge_flag_enable(bm, e, SEL_FLAG);
}
}
}
}
else {
BMFace *f;
BMO_ITER (f, &siter, op->slots_in, "geom", BM_FACE) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (!use_faces_step) {
BMIter fiter;
BMFace *f_other;
BM_ITER_ELEM (f_other, &fiter, l->e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f_other, SEL_ORIG)) {
BMO_face_flag_enable(bm, f, SEL_FLAG);
break;
}
}
}
else {
BMIter fiter;
BMFace *f_other;
BM_ITER_ELEM (f_other, &fiter, l->v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f_other, SEL_ORIG)) {
BMO_face_flag_enable(bm, f, SEL_FLAG);
break;
}
}
}
}
}
}
}
void bmo_region_extend_exec(BMesh *bm, BMOperator *op)
{
const bool use_faces = BMO_slot_bool_get(op->slots_in, "use_faces");
const bool use_face_step = BMO_slot_bool_get(op->slots_in, "use_face_step");
const bool constrict = BMO_slot_bool_get(op->slots_in, "use_contract");
BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_ALL_NOLOOP, SEL_ORIG);
if (constrict) {
bmo_region_extend_contract(bm, op, use_faces, use_face_step);
}
else {
bmo_region_extend_expand(bm, op, use_faces, use_face_step);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, SEL_FLAG);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Smooth Vert
* \{ */
void bmo_smooth_vert_exec(BMesh * /*bm*/, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v;
BMEdge *e;
float (*cos)[3] = MEM_new_array_uninitialized<float[3]>(
BMO_slot_buffer_len(op->slots_in, "verts"), __func__);
float *co, *co2, clip_dist = BMO_slot_float_get(op->slots_in, "clip_dist");
const float fac = BMO_slot_float_get(op->slots_in, "factor");
int i, j, clipx, clipy, clipz;
int xaxis, yaxis, zaxis;
clipx = BMO_slot_bool_get(op->slots_in, "mirror_clip_x");
clipy = BMO_slot_bool_get(op->slots_in, "mirror_clip_y");
clipz = BMO_slot_bool_get(op->slots_in, "mirror_clip_z");
xaxis = BMO_slot_bool_get(op->slots_in, "use_axis_x");
yaxis = BMO_slot_bool_get(op->slots_in, "use_axis_y");
zaxis = BMO_slot_bool_get(op->slots_in, "use_axis_z");
i = 0;
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
co = cos[i];
zero_v3(co);
j = 0;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
co2 = BM_edge_other_vert(e, v)->co;
add_v3_v3v3(co, co, co2);
j += 1;
}
if (!j) {
copy_v3_v3(co, v->co);
i++;
continue;
}
mul_v3_fl(co, 1.0f / float(j));
interp_v3_v3v3(co, v->co, co, fac);
if (clipx && fabsf(v->co[0]) <= clip_dist) {
co[0] = 0.0f;
}
if (clipy && fabsf(v->co[1]) <= clip_dist) {
co[1] = 0.0f;
}
if (clipz && fabsf(v->co[2]) <= clip_dist) {
co[2] = 0.0f;
}
i++;
}
i = 0;
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (xaxis) {
v->co[0] = cos[i][0];
}
if (yaxis) {
v->co[1] = cos[i][1];
}
if (zaxis) {
v->co[2] = cos[i][2];
}
i++;
}
MEM_delete(cos);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cycle UVs for a Face
* \{ */
void bmo_rotate_uvs_exec(BMesh *bm, BMOperator *op)
{
BMOIter fs_iter; /* selected faces iterator */
BMFace *fs; /* current face */
BMIter l_iter; /* iteration loop */
const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
if (cd_loop_uv_offset != -1) {
BMO_ITER (fs, &fs_iter, op->slots_in, "faces", BM_FACE) {
if (use_ccw == false) { /* same loops direction */
BMLoop *lf; /* current face loops */
float *f_luv; /* first face loop uv */
float p_uv[2]; /* previous uvs */
float t_uv[2]; /* temp uvs */
int n = 0;
BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
/* current loop uv is the previous loop uv */
float *luv = BM_ELEM_CD_GET_FLOAT_P(lf, cd_loop_uv_offset);
if (n == 0) {
f_luv = luv;
copy_v2_v2(p_uv, luv);
}
else {
copy_v2_v2(t_uv, luv);
copy_v2_v2(luv, p_uv);
copy_v2_v2(p_uv, t_uv);
}
n++;
}
copy_v2_v2(f_luv, p_uv);
}
else { /* counter loop direction */
BMLoop *lf; /* current face loops */
float *p_luv; /* previous loop uv */
float *luv;
float t_uv[2]; /* current uvs */
int n = 0;
BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
/* previous loop uv is the current loop uv */
luv = BM_ELEM_CD_GET_FLOAT_P(lf, cd_loop_uv_offset);
if (n == 0) {
p_luv = luv;
copy_v2_v2(t_uv, luv);
}
else {
copy_v2_v2(p_luv, luv);
p_luv = luv;
}
n++;
}
copy_v2_v2(luv, t_uv);
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reverse UVs for a Face
* \{ */
static void bm_face_reverse_uvs(BMFace *f, const int cd_loop_uv_offset)
{
BMIter iter;
BMLoop *l;
int i;
Array<float2, BM_DEFAULT_NGON_STACK_SIZE> uvs(f->len);
BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
copy_v2_v2(uvs[i], luv);
}
/* now that we have the uvs in the array, reverse! */
BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
/* current loop uv is the previous loop uv */
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
copy_v2_v2(luv, uvs[(f->len - i - 1)]);
}
}
void bmo_reverse_uvs_exec(BMesh *bm, BMOperator *op)
{
BMOIter iter;
BMFace *f;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
if (cd_loop_uv_offset != -1) {
BMO_ITER (f, &iter, op->slots_in, "faces", BM_FACE) {
bm_face_reverse_uvs(f, cd_loop_uv_offset);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cycle Colors for a Face
* \{ */
static void bmo_get_loop_color_ref(BMesh *bm,
int index,
int *r_cd_color_offset,
std::optional<eCustomDataType> *r_cd_color_type)
{
int color_index = 0;
for (const CustomDataLayer &layer : Span(bm->ldata.layers, bm->ldata.totlayer)) {
if (CD_TYPE_AS_MASK(eCustomDataType(layer.type)) & CD_MASK_COLOR_ALL) {
if (color_index == index) {
*r_cd_color_offset = layer.offset;
*r_cd_color_type = eCustomDataType(layer.type);
return;
}
color_index++;
}
}
*r_cd_color_offset = -1;
}
void bmo_rotate_colors_exec(BMesh *bm, BMOperator *op)
{
BMOIter fs_iter; /* selected faces iterator */
BMFace *fs; /* current face */
BMIter l_iter; /* iteration loop */
const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
const int color_index = BMO_slot_int_get(op->slots_in, "color_index");
int cd_loop_color_offset;
std::optional<eCustomDataType> cd_loop_color_type;
bmo_get_loop_color_ref(bm, color_index, &cd_loop_color_offset, &cd_loop_color_type);
if (cd_loop_color_offset == -1) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "color_index is invalid");
return;
}
const size_t size = cd_loop_color_type == CD_PROP_COLOR ? sizeof(MPropCol) : sizeof(MLoopCol);
void *p_col; /* previous color */
void *t_col = alloca(size); /* Temp color. */
BMO_ITER (fs, &fs_iter, op->slots_in, "faces", BM_FACE) {
if (use_ccw == false) { /* same loops direction */
BMLoop *lf; /* current face loops */
void *f_lcol; /* first face loop color */
int n = 0;
BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
/* current loop color is the previous loop color */
void *lcol = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_color_offset);
if (n == 0) {
f_lcol = lcol;
p_col = lcol;
}
else {
memcpy(t_col, lcol, size);
memcpy(lcol, p_col, size);
memcpy(p_col, t_col, size);
}
n++;
}
memcpy(f_lcol, p_col, size);
}
else { /* counter loop direction */
BMLoop *lf; /* current face loops */
void *lcol, *p_lcol;
int n = 0;
BM_ITER_ELEM (lf, &l_iter, fs, BM_LOOPS_OF_FACE) {
/* previous loop color is the current loop color */
lcol = BM_ELEM_CD_GET_VOID_P(lf, cd_loop_color_offset);
if (n == 0) {
p_lcol = lcol;
memcpy(t_col, lcol, size);
}
else {
memcpy(p_lcol, lcol, size);
p_lcol = lcol;
}
n++;
}
memcpy(lcol, t_col, size);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reverse Colors for a Face
* \{ */
static void bm_face_reverse_colors(BMFace *f,
const int cd_loop_color_offset,
const eCustomDataType cd_loop_color_type)
{
BMIter iter;
BMLoop *l;
int i;
const size_t size = cd_loop_color_type == CD_PROP_COLOR ? sizeof(MPropCol) : sizeof(MLoopCol);
char *cols = static_cast<char *>(alloca(size * f->len));
char *col = cols;
BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
void *lcol = BM_ELEM_CD_GET_VOID_P(l, cd_loop_color_offset);
memcpy(static_cast<void *>(col), lcol, size);
col += size;
}
/* now that we have the uvs in the array, reverse! */
BM_ITER_ELEM_INDEX (l, &iter, f, BM_LOOPS_OF_FACE, i) {
/* current loop uv is the previous loop color */
void *lcol = BM_ELEM_CD_GET_VOID_P(l, cd_loop_color_offset);
col = cols + (f->len - i - 1) * size;
memcpy(lcol, static_cast<void *>(col), size);
}
}
void bmo_reverse_colors_exec(BMesh *bm, BMOperator *op)
{
BMOIter iter;
BMFace *f;
const int color_index = BMO_slot_int_get(op->slots_in, "color_index");
int cd_loop_color_offset;
std::optional<eCustomDataType> cd_loop_color_type;
bmo_get_loop_color_ref(bm, color_index, &cd_loop_color_offset, &cd_loop_color_type);
if (cd_loop_color_offset == -1) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "color_index is invalid");
return;
}
BMO_ITER (f, &iter, op->slots_in, "faces", BM_FACE) {
bm_face_reverse_colors(f, cd_loop_color_offset, *cd_loop_color_type);
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,55 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Creates a solid wireframe from connected faces.
*/
#include "DNA_material_types.h"
#include "bmesh.hh"
#include "tools/bmesh_wireframe.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_wireframe_exec(BMesh *bm, BMOperator *op)
{
const float offset = BMO_slot_float_get(op->slots_in, "thickness");
const float offset_fac = BMO_slot_float_get(op->slots_in, "offset");
const bool use_replace = BMO_slot_bool_get(op->slots_in, "use_replace");
const bool use_boundary = BMO_slot_bool_get(op->slots_in, "use_boundary");
const bool use_even_offset = BMO_slot_bool_get(op->slots_in, "use_even_offset");
const bool use_relative_offset = BMO_slot_bool_get(op->slots_in, "use_relative_offset");
const bool use_crease = BMO_slot_bool_get(op->slots_in, "use_crease");
const float crease_weight = BMO_slot_float_get(op->slots_in, "crease_weight");
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "faces", BM_FACE, BM_ELEM_TAG, false);
BM_mesh_wireframe(bm,
offset,
offset_fac,
0.0f,
use_replace,
use_boundary,
use_even_offset,
use_relative_offset,
use_crease,
crease_weight,
/* dummy vgroup */
-1,
false,
0,
MAXMAT,
true);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
}
} // namespace blender