Add Chromium-only Blender WebEngine parity work
This commit is contained in:
73
blender-5.2.0/source/blender/bmesh/operators/bmo_beautify.cc
Normal file
73
blender-5.2.0/source/blender/bmesh/operators/bmo_beautify.cc
Normal 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
|
||||
96
blender-5.2.0/source/blender/bmesh/operators/bmo_bevel.cc
Normal file
96
blender-5.2.0/source/blender/bmesh/operators/bmo_bevel.cc
Normal 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
|
||||
@@ -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
|
||||
666
blender-5.2.0/source/blender/bmesh/operators/bmo_bridge.cc
Normal file
666
blender-5.2.0/source/blender/bmesh/operators/bmo_bridge.cc
Normal 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
|
||||
747
blender-5.2.0/source/blender/bmesh/operators/bmo_circularize.cc
Normal file
747
blender-5.2.0/source/blender/bmesh/operators/bmo_circularize.cc
Normal 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 ¢er,
|
||||
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 ¢er,
|
||||
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> <ri = 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 ¢er_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> <ri = 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
|
||||
220
blender-5.2.0/source/blender/bmesh/operators/bmo_connect.cc
Normal file
220
blender-5.2.0/source/blender/bmesh/operators/bmo_connect.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
745
blender-5.2.0/source/blender/bmesh/operators/bmo_connect_pair.cc
Normal file
745
blender-5.2.0/source/blender/bmesh/operators/bmo_connect_pair.cc
Normal 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
|
||||
299
blender-5.2.0/source/blender/bmesh/operators/bmo_create.cc
Normal file
299
blender-5.2.0/source/blender/bmesh/operators/bmo_create.cc
Normal 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
|
||||
969
blender-5.2.0/source/blender/bmesh/operators/bmo_dissolve.cc
Normal file
969
blender-5.2.0/source/blender/bmesh/operators/bmo_dissolve.cc
Normal 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(®walker,
|
||||
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(®walker, f)); f2;
|
||||
f2 = static_cast<BMFace *>(BMW_step(®walker)))
|
||||
{
|
||||
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(®walker);
|
||||
|
||||
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(®walker,
|
||||
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(®walker, f))) &&
|
||||
(faces_init[1] = static_cast<BMFace *>(BMW_step(®walker))))
|
||||
{
|
||||
Vector<BMFace *> faces;
|
||||
faces.append(faces_init[0]);
|
||||
faces.append(faces_init[1]);
|
||||
|
||||
BMFace *f_iter;
|
||||
while ((f_iter = static_cast<BMFace *>(BMW_step(®walker)))) {
|
||||
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(®walker);
|
||||
}
|
||||
|
||||
/* 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
|
||||
747
blender-5.2.0/source/blender/bmesh/operators/bmo_dupe.cc
Normal file
747
blender-5.2.0/source/blender/bmesh/operators/bmo_dupe.cc
Normal 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
|
||||
239
blender-5.2.0/source/blender/bmesh/operators/bmo_edgenet.cc
Normal file
239
blender-5.2.0/source/blender/bmesh/operators/bmo_edgenet.cc
Normal 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
|
||||
866
blender-5.2.0/source/blender/bmesh/operators/bmo_extrude.cc
Normal file
866
blender-5.2.0/source/blender/bmesh/operators/bmo_extrude.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
734
blender-5.2.0/source/blender/bmesh/operators/bmo_fill_grid.cc
Normal file
734
blender-5.2.0/source/blender/bmesh/operators/bmo_fill_grid.cc
Normal 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
|
||||
@@ -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
|
||||
128
blender-5.2.0/source/blender/bmesh/operators/bmo_flatten.cc
Normal file
128
blender-5.2.0/source/blender/bmesh/operators/bmo_flatten.cc
Normal 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
|
||||
606
blender-5.2.0/source/blender/bmesh/operators/bmo_hull.cc
Normal file
606
blender-5.2.0/source/blender/bmesh/operators/bmo_hull.cc
Normal 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 */
|
||||
1377
blender-5.2.0/source/blender/bmesh/operators/bmo_inset.cc
Normal file
1377
blender-5.2.0/source/blender/bmesh/operators/bmo_inset.cc
Normal file
File diff suppressed because it is too large
Load Diff
1149
blender-5.2.0/source/blender/bmesh/operators/bmo_join_triangles.cc
Normal file
1149
blender-5.2.0/source/blender/bmesh/operators/bmo_join_triangles.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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, ¶ms);
|
||||
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
110
blender-5.2.0/source/blender/bmesh/operators/bmo_mirror.cc
Normal file
110
blender-5.2.0/source/blender/bmesh/operators/bmo_mirror.cc
Normal 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
|
||||
303
blender-5.2.0/source/blender/bmesh/operators/bmo_normals.cc
Normal file
303
blender-5.2.0/source/blender/bmesh/operators/bmo_normals.cc
Normal 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
|
||||
@@ -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
|
||||
134
blender-5.2.0/source/blender/bmesh/operators/bmo_planar_faces.cc
Normal file
134
blender-5.2.0/source/blender/bmesh/operators/bmo_planar_faces.cc
Normal 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
|
||||
135
blender-5.2.0/source/blender/bmesh/operators/bmo_poke.cc
Normal file
135
blender-5.2.0/source/blender/bmesh/operators/bmo_poke.cc
Normal 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
|
||||
1736
blender-5.2.0/source/blender/bmesh/operators/bmo_primitive.cc
Normal file
1736
blender-5.2.0/source/blender/bmesh/operators/bmo_primitive.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
271
blender-5.2.0/source/blender/bmesh/operators/bmo_rotate_edges.cc
Normal file
271
blender-5.2.0/source/blender/bmesh/operators/bmo_rotate_edges.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
1447
blender-5.2.0/source/blender/bmesh/operators/bmo_subdivide.cc
Normal file
1447
blender-5.2.0/source/blender/bmesh/operators/bmo_subdivide.cc
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
104
blender-5.2.0/source/blender/bmesh/operators/bmo_symmetrize.cc
Normal file
104
blender-5.2.0/source/blender/bmesh/operators/bmo_symmetrize.cc
Normal 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
|
||||
295
blender-5.2.0/source/blender/bmesh/operators/bmo_triangulate.cc
Normal file
295
blender-5.2.0/source/blender/bmesh/operators/bmo_triangulate.cc
Normal 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
|
||||
@@ -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
|
||||
772
blender-5.2.0/source/blender/bmesh/operators/bmo_utils.cc
Normal file
772
blender-5.2.0/source/blender/bmesh/operators/bmo_utils.cc
Normal 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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user