Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
659 lines
20 KiB
C++
659 lines
20 KiB
C++
/* 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.h"
|
|
|
|
#include "intern/bmesh_operators_private.h" /* own include */
|
|
|
|
/**
|
|
* 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(((BMVert *)el_a->data)->co, ((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)
|
|
{
|
|
ListBase *lb_a = BM_edgeloop_verts_get(el_store_a);
|
|
ListBase *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((BMEdgeLoopStore *)el_store_a);
|
|
el_store_b_len = BM_edgeloop_length_get((BMEdgeLoopStore *)el_store_b);
|
|
|
|
if (el_store_a_len < el_store_b_len) {
|
|
SWAP(int, el_store_a_len, el_store_b_len);
|
|
SWAP(BMEdgeLoopStore *, 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 {
|
|
ListBase *lb_a = BM_edgeloop_verts_get(el_store_a);
|
|
ListBase *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,
|
|
((BMVert *)(((LinkData *)lb_a->first)->data))->co,
|
|
((BMVert *)(((LinkData *)lb_a->last)->data))->co);
|
|
sub_v3_v3v3(dir_b_orig,
|
|
((BMVert *)(((LinkData *)lb_b->first)->data))->co,
|
|
((BMVert *)(((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) {
|
|
LISTBASE_FOREACH (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);
|
|
ListBase *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, bm, l_b, l_iter);
|
|
}
|
|
l_iter = l_iter->next;
|
|
if (l_b_next) {
|
|
BM_elem_attrs_copy(bm, bm, l_b_next, l_iter);
|
|
}
|
|
l_iter = l_iter->next;
|
|
if (l_a_next) {
|
|
BM_elem_attrs_copy(bm, bm, l_a_next, l_iter);
|
|
}
|
|
l_iter = l_iter->next;
|
|
if (l_a) {
|
|
BM_elem_attrs_copy(bm, 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, bm, l_b, l_iter);
|
|
}
|
|
l_iter = l_iter->next;
|
|
if (l_a_next) {
|
|
BM_elem_attrs_copy(bm, bm, l_a_next, l_iter);
|
|
}
|
|
l_iter = l_iter->next;
|
|
if (l_a) {
|
|
BM_elem_attrs_copy(bm, 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, 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++) {
|
|
LISTBASE_FOREACH (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++) {
|
|
LISTBASE_FOREACH (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)
|
|
{
|
|
ListBase 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));
|
|
LISTBASE_FOREACH (LinkData *, el_store, &eloops) {
|
|
if (eloop_len != BM_edgeloop_length_get((BMEdgeLoopStore *)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);
|
|
}
|
|
|
|
LISTBASE_FOREACH (LinkData *, el_store, &eloops) {
|
|
LinkData *el_store_next = el_store->next;
|
|
|
|
if (el_store_next == nullptr) {
|
|
if (use_cyclic && (count > 2)) {
|
|
el_store_next = static_cast<LinkData *>(eloops.first);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
bridge_loop_pair(bm,
|
|
(BMEdgeLoopStore *)el_store,
|
|
(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);
|
|
}
|
|
}
|
|
}
|