Blender V5.0
bmo_dissolve.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include <cmath>
12
13#include "MEM_guardedalloc.h"
14
15#include "BLI_math_vector.h"
16#include "BLI_stack.h"
17#include "BLI_vector.hh"
18
19#include "bmesh.hh"
20#include "bmesh_tools.hh"
21
23
24using blender::Vector;
25
26/* ***_ISGC: mark for garbage-collection */
27
28#define FACE_MARK 1
29#define FACE_ORIG 2
30#define FACE_NEW 4
31#define FACE_TAG 8
32
33#define EDGE_MARK 1
34#define EDGE_TAG 2
35#define EDGE_ISGC 8
40#define EDGE_CHAIN 16
41
42#define VERT_MARK 1
43#define VERT_MARK_PAIR 4
44#define VERT_TAG 2
45#define VERT_ISGC 8
46#define VERT_MARK_TEAR 16
47
48/* -------------------------------------------------------------------- */
51
53{
54 BMWalker regwalker;
55 BMIter liter2;
56 BMLoop *l2, *l3;
57 BMFace *f2;
58
59 /* Checks if there are any unmarked boundary edges in the face region. */
60
61 BMW_init(&regwalker,
62 bm,
69
70 for (f2 = static_cast<BMFace *>(BMW_begin(&regwalker, f)); f2;
71 f2 = static_cast<BMFace *>(BMW_step(&regwalker)))
72 {
73 BM_ITER_ELEM (l2, &liter2, f2, BM_LOOPS_OF_FACE) {
74 l3 = l2->radial_next;
76 if (!BMO_edge_flag_test(bm, l2->e, EDGE_MARK)) {
77 return false;
78 }
79 }
80 }
81 }
82 BMW_end(&regwalker);
83
84 return true;
85}
86
91{
92 BMEdge *e_pair[2];
93 const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
94
95 BLI_assert(is_edge_pair);
96 UNUSED_VARS_NDEBUG(is_edge_pair);
97
98 /* Compute the angle between the edges. Start with the raw angle. */
99 BMVert *v_a = BM_edge_other_vert(e_pair[0], v);
100 BMVert *v_b = BM_edge_other_vert(e_pair[1], v);
101 float angle = M_PI - angle_v3v3v3(v_a->co, v->co, v_b->co);
102
103 /* There are two ways to measure the angle around a vert with two edges. The first is to
104 * measure the raw angle between the two neighboring edges, the second is to measure the
105 * angle of the edges around the vertex normal vector. When the vert is an edge pair
106 * between two faces, The normal measurement is better in general. In the specific case of
107 * a vert between two faces, but the faces have a *very* sharp angle between them, then the
108 * raw angle is better, because the normal is perpendicular to average of the two faces,
109 * and if the faces are folded almost 180 degrees, the vertex normal becomes more an more
110 * edge-on to the faces, meaning the angle *around the normal* becomes more and more flat,
111 * even if it makes a sharp angle when viewed from the side.
112 *
113 * When the faces become very folded, the `raw_factor` adds some of the "as seen from the side"
114 * angle back into the computation, making the algorithm behave more intuitively.
115 *
116 * The `raw_factor` is computed as follows:
117 * - When not a face pair, part this is skipped, and the raw angle is used.
118 * - When a face pair is co-planar, or has an angle up to 90 degrees, `raw_factor` is 0.0.
119 * - As angle increases from 90 to 180 degrees, `raw_factor` increases from 0.0 to 1.0.
120 */
121 BMFace *f_pair[2];
122 if (BM_edge_face_pair(v->e, &f_pair[0], &f_pair[1])) {
123 /* Due to merges, the normals are not currently trustworthy. Compute them. */
124 float no_a[3], no_b[3];
125 BM_face_calc_normal(f_pair[0], no_a);
126 BM_face_calc_normal(f_pair[1], no_b);
127
128 /* Now determine the raw factor based on how folded the faces are. */
129 const float raw_factor = std::clamp(-dot_v3v3(no_a, no_b), 0.0f, 1.0f);
130
131 /* Blend the two ways of computing the angle. */
132 float normal_angle = M_PI - angle_on_axis_v3v3v3_v3(v_a->co, v->co, v_b->co, v->no);
133 angle = interpf(angle, normal_angle, raw_factor);
134 }
135
136 return angle;
137}
138
143
144{
145 /* Merge the header flags on the two edges that will be merged. */
146 BMEdge *e_pair[2];
147 const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
148
149 BLI_assert(is_edge_pair);
150 UNUSED_VARS_NDEBUG(is_edge_pair);
151
152 BM_elem_flag_merge_ex(e_pair[0], e_pair[1], BM_ELEM_HIDDEN);
153
154 /* Dissolve the vertex. */
155 BMEdge *e_new = BM_vert_collapse_edge(bm, v->e, v, do_del, true, true);
156
157 if (e_new) {
158 /* Ensure the result of dissolving never leaves visible edges connected to hidden vertices.
159 * From a user perspective this is an invalid state which tools should not allow. */
160 if (!BM_elem_flag_test(e_new, BM_ELEM_HIDDEN)) {
161 if (BM_elem_flag_test(e_new->v1, BM_ELEM_HIDDEN) ||
163 {
164 if (BM_elem_flag_test(e_new, BM_ELEM_SELECT)) {
165 BM_edge_select_set_noflush(bm, e_new, false);
166 }
168 }
169 }
170 }
171 return e_new;
172}
173
174static void bm_face_split(BMesh *bm, const short oflag, bool use_edge_delete)
175{
176 BLI_Stack *edge_delete_verts;
177 BMIter iter;
178 BMVert *v;
179
180 if (use_edge_delete) {
181 edge_delete_verts = BLI_stack_new(sizeof(BMVert *), __func__);
182 }
183
184 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
185 if (BMO_vert_flag_test(bm, v, oflag)) {
186 if (BM_vert_is_edge_pair(v) == false) {
187 BMIter liter;
188 BMLoop *l;
189 BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
190 if (l->f->len > 3) {
191 if (BMO_vert_flag_test(bm, l->next->v, oflag) == 0 &&
192 BMO_vert_flag_test(bm, l->prev->v, oflag) == 0)
193 {
194 BM_face_split(bm, l->f, l->next, l->prev, nullptr, nullptr, true);
195 }
196 }
197 }
198
199 if (use_edge_delete) {
200 BLI_stack_push(edge_delete_verts, &v);
201 }
202 }
203 }
204 }
205
206 if (use_edge_delete) {
207 while (!BLI_stack_is_empty(edge_delete_verts)) {
208 /* remove surrounding edges & faces */
209 BLI_stack_pop(edge_delete_verts, &v);
210 while (v->e) {
211 BM_edge_kill(bm, v->e);
212 }
213 }
214 BLI_stack_free(edge_delete_verts);
215 }
216}
217
219
220/* -------------------------------------------------------------------- */
223
225{
226 BMOIter oiter;
227 BMFace *f;
228 BMWalker regwalker;
229
230 const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts");
231
232 if (use_verts) {
233 /* tag verts that start out with only 2 edges,
234 * don't remove these later */
235 BMIter viter;
236 BMVert *v;
237
238 BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
240 }
241 }
242
244
245 /* List of regions which are themselves a list of faces. */
247
248 /* collect region */
249 BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
250 if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
251 continue;
252 }
253
254 BMW_init(&regwalker,
255 bm,
259 FACE_MARK,
260 /* no need to check BMW_FLAG_TEST_HIDDEN, faces are already marked by the bmo. */
263
264 /* Check there are at least two faces before creating the array. */
265 BMFace *faces_init[2];
266 if ((faces_init[0] = static_cast<BMFace *>(BMW_begin(&regwalker, f))) &&
267 (faces_init[1] = static_cast<BMFace *>(BMW_step(&regwalker))))
268 {
270 faces.append(faces_init[0]);
271 faces.append(faces_init[1]);
272
273 BMFace *f_iter;
274 while ((f_iter = static_cast<BMFace *>(BMW_step(&regwalker)))) {
275 faces.append(f_iter);
276 }
277
278 for (BMFace *face : faces) {
281 }
282
283 regions.append_as(std::move(faces));
284 }
285
286 BMW_end(&regwalker);
287 }
288
289 /* track how many faces we should end up with */
290 int totface_target = bm->totface;
291
292 for (Vector<BMFace *> &faces : regions) {
293 const int64_t faces_len = faces.size();
294
295 BMFace *f_double;
296
297 BMFace *f_new = BM_faces_join(bm, faces.data(), faces_len, true, &f_double);
298
299 if (LIKELY(f_new)) {
300
301 /* All the joined faces are gone and the fresh f_new represents their union. */
302 totface_target -= faces_len - 1;
303
304 if (UNLIKELY(f_double)) {
305 /* `BM_faces_join()` succeeded, but there is a double. Keep the pre-existing face
306 * and retain its custom-data. Remove the newly made merge result. */
307 BM_face_kill(bm, f_new);
308 totface_target -= 1;
309 f_new = f_double;
310 }
311
312 /* Un-mark the joined face to ensure it is not garbage collected later. */
314
315 /* Mark the joined face so it can be added to the selection later. */
317 }
318 else {
319 /* `BM_faces_join()` failed. */
320
321 /* NOTE: prior to 3.0 this raised an error: "Could not create merged face".
322 * Change behavior since it's not useful to fail entirely when a single face-group
323 * can't be merged into one face. Continue with other face groups instead.
324 *
325 * This could optionally do a partial merge, where some faces are joined. */
326
327 /* Prevent these faces from being removed. */
328 for (BMFace *face : faces) {
330 }
331 }
332 }
333
334 /* Typically no faces need to be deleted */
335 if (totface_target != bm->totface) {
336 BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", FACE_ORIG, DEL_FACES);
337 }
338
339 if (use_verts) {
340 BMIter viter;
341 BMVert *v, *v_next;
342
343 BM_ITER_MESH_MUTABLE (v, v_next, &viter, bm, BM_VERTS_OF_MESH) {
345 continue;
346 }
347 if (BM_vert_is_edge_pair(v)) {
349 }
350 }
351 }
352
354
356}
357
367static BMVert *bmo_find_end_of_chain(BMesh *bm, BMEdge *e, BMVert *v, const short edge_oflag = 0)
368{
369 BMVert *v_init = v;
370
371 while (BM_vert_is_edge_pair(v)) {
372
373 /* Move one step down the chain. */
376
377 /* If we walk to an edge that has already been processed, there's no need to keep working.
378 * If `edge_oflag` is 0, this test never returns true,
379 * so iteration will truly go to the end. */
380 if (BMO_edge_flag_test(bm, e, edge_oflag)) {
381 return nullptr;
382 }
383
384 /* Optionally mark along the chain.
385 * If `edge_oflag` is 0, `hflag |= 0` is still faster than if + test + jump. */
386 BMO_edge_flag_enable(bm, e, edge_oflag);
387
388 /* While this should never happen in the context this function is called.
389 * Avoid an eternal loop even in the case of degenerate geometry. */
390 BLI_assert(v != v_init);
391 if (UNLIKELY(v == v_init)) {
392 return nullptr;
393 }
394 }
395 return v;
396}
397
403{
404 /* If the vert was already tested and marked, don't test again. */
406 return false;
407 }
408
409 /* Check each face at this vert by checking each loop. */
410 BMIter iter;
411 BMLoop *l_a;
412 BM_ITER_ELEM (l_a, &iter, v, BM_LOOPS_OF_VERT) {
414
415 /* `l_a` and `l_b` are now the two edges of the face that share this vert.
416 * if both are untagged, return true. */
418 return true;
419 }
420 }
421
422 return false;
423}
424
429 BMVert *v,
430 const short edge_oflag,
431 const int max)
432{
433 int retval = 0;
434 BMIter iter;
435 BMEdge *e;
436 BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
437 if (BMO_edge_flag_test(bm, e, edge_oflag)) {
438 retval++;
439 }
440 if (retval == max) {
441 return retval;
442 }
443 }
444 return retval;
445}
446
448{
449 /* Set the default not to limit dissolving at all. */
450 BMO_slot_float_set(op->slots_in, "angle_threshold", M_PI);
451}
452
454{
455 // BMOperator fop;
456 BMOIter eiter;
457 BMIter iter;
458 BMEdge *e, *e_next;
459 BMVert *v, *v_next;
460
461 /* Even when geometry has exact angles like 0 or 90 or 180 deg, `angle_on_axis_v3v3v3_v3`
462 * can return slightly incorrect values due to cos/sin functions, floating point error, etc.
463 * This lets the test ignore that tiny bit of math error so users won't notice. */
464 const float angle_epsilon = RAD2DEGF(0.0001f);
465
466 const float angle_threshold = BMO_slot_float_get(op->slots_in, "angle_threshold");
467
468 /* Use verts when told to... except, do *not* use verts when angle_threshold is 0.0. */
469 const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts") &&
470 (angle_threshold > angle_epsilon);
471
472 /* If angle threshold is 180, don't bother with angle math, just dissolve everything. */
473 const bool dissolve_all = (angle_threshold > M_PI - angle_epsilon);
474
475 const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
476
477 if (use_face_split || use_verts) {
479 }
480
481 /* Tag certain geometry around the selected edges, for later processing. */
482 BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
483
484 /* Connected edge chains have endpoints with edge pairs. The existing behavior was to dissolve
485 * the verts, both in the middle, and at the ends, of any selected edges in chains. Mark these
486 * kind of edges, so we know to skip the angle threshold test later. */
487 if (BM_vert_is_edge_pair(e->v1) || BM_vert_is_edge_pair(e->v2)) {
489 }
490
491 BMFace *f_pair[2];
492 if (BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
493 /* Tag all the edges and verts of the two faces on either side of this edge.
494 * This edge is going to be dissolved, and after that happens, some of those elements of the
495 * surrounding faces might end up as loose geometry, depending on how the dissolve affected
496 * geometry near them. Tag them `*_ISGC`, to be checked later, and cleaned up if loose. */
497 uint j;
498 for (j = 0; j < 2; j++) {
499 BMLoop *l_first, *l_iter;
500 l_iter = l_first = BM_FACE_FIRST_LOOP(f_pair[j]);
501 do {
504 } while ((l_iter = l_iter->next) != l_first);
505 }
506
507 /* If using verts, and this edge is part of a chain that will be dissolved, then extend
508 * `EDGE_TAG` to both ends of the chain. This marks any edges that, even though they might
509 * not be selected, will also be dissolved when the face merge happens. This allows counting
510 * how many edges will remain after the dissolves are done later. */
511 if (use_verts && BMO_edge_flag_test(bm, e, EDGE_CHAIN)) {
514 }
515 }
516 }
517
518 if (use_verts) {
519
520 /* Mark all verts that are candidates to be dissolved. */
521 BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
522
523 /* Edges only dissolve if they are manifold, so if the edge won't be dissolved, then there's
524 * no reason to mark either of its ends for dissolve. */
525 BMFace *f_pair[2];
526 if (!BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
527 continue;
528 }
529
530 /* if `BM_faces_join_pair` will be done, mark the correct two verts at the ends for
531 * dissolve. */
532 for (int i = 0; i < 2; i++) {
533 BMVert *v_edge = *((&e->v1) + i);
534
535 /* An edge between two triangles should dissolve to a quad, akin to un-triangulate.
536 * Prevent dissolving either corner, if doing so would collapse the corner, converting
537 * the quad to a triangle or wire. This happens when two triangles join, and the vert
538 * has two untagged edges, and the _only_ other tagged edge is this edge that's about
539 * to be dissolved. When that case is found, skip it, do not tag it.
540 * The edge count test ensures that if we're dissolving a chain, the crossing loop cuts
541 * will still be dissolved, even if they happen to make an "un-triangulate" case.
542 * This is not done when face split is active, because face split often creates triangle
543 * pairs on edges that touch boundaries, resulting in the boundary vert not dissolving. */
544 if (f_pair[0]->len == 3 && f_pair[1]->len == 3 &&
546 {
547 continue;
548 }
549
550 /* If a chain, follow the chain until the end is found. The whole chain will dissolve, so
551 * the test needs to happen there, at the end of the chain, where it meets other geometry,
552 * not here, at the end of a selected edge that only touches other parts of the chain. */
553 if (BM_vert_is_edge_pair(v_edge)) {
554 v_edge = bmo_find_end_of_chain(bm, e, v_edge, EDGE_CHAIN);
555 }
556
557 /* If the end of the chain was searched for and was not located, take no action. */
558 if (v_edge == nullptr) {
559 continue;
560 }
561
562 /* When the user selected multiple edges that meet at one vert, and there are existing
563 * faces at that vert that are *not* selected, then remove that vert from consideration for
564 * dissolve.
565 *
566 * This logic implements the following:
567 * - When several dissolved edges cross a loop cut, the loop cut vert should be dissolved.
568 * (`bmo_vert_touches_unselected_face()` will be false).
569 * - When dissolve edges *end* at a T on a loop cut, the loop cut vert should be dissolved.
570 * (`bmo_vert_tagged_edges_count_at_most()` will be 1).
571 * - When multiple dissolve edges touch the corner of a quad or triangle, but leave in a
572 * different direction, regard that contact is 'incidental' and the face should stay.
573 * (both tests will be true).
574 */
577 {
578 continue;
579 }
580
581 /* Mark for dissolve. */
583 }
584 }
585 }
586
587 if (use_face_split) {
588 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
589 BMIter itersub;
590 int untag_count = 0;
591 BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
593 untag_count++;
594 }
595 }
596
597 /* check that we have 2 edges remaining after dissolve */
598 if (untag_count <= 2) {
600 }
601 }
602
603 bm_face_split(bm, VERT_TAG, false);
604 }
605
606 /* Merge any face pairs that straddle a selected edge. */
607 BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
608 BMLoop *l_a, *l_b;
609 if (BM_edge_loop_pair(e, &l_a, &l_b)) {
610 BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
611 }
612 }
613
614 /* Cleanup geometry. Remove any edges that are garbage collectible and that have became
615 * irrelevant (no loops) because of face merges. */
616 BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
617 if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
618 BM_edge_kill(bm, e);
619 }
620 }
621
622 /* Cleanup geometry. Remove any verts that are garbage collectible and that have became
623 * isolated verts (no edges) because of edge dissolves. */
624 BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
625 if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
626 BM_vert_kill(bm, v);
627 }
628 }
629
630 /* If dissolving verts, then evaluate each VERT_MARK vert. */
631 if (use_verts) {
632 BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
634 continue;
635 }
636
637 /* If it is not an edge pair, it cannot be merged. */
638 BMEdge *e_pair[2];
639 if (BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]) == false) {
641 continue;
642 }
643
644 /* At an angle threshold of 180, dissolve everything, skip the math of the angle test. */
645 if (dissolve_all) {
646 /* VERT_MARK remains enabled. */
647 continue;
648 }
649
650 /* Verts in edge chains ignore the angle test. This maintains the previous behavior,
651 * where such verts were not subject to the angle threshold.
652 *
653 * When edge chains are selected for dissolve, all edge-pair verts at *both* ends of each
654 * selected edge will be dissolved, combining the selected edges into their neighbors.
655 *
656 * Note that when only *part* of a chain is selected, this *will* alter unselected edges,
657 * because selected edges will merge *into their unselected neighbors*. This too, has been
658 * maintained, for consistency with the previous (but possibly unintentional) behavior. */
659 if (BMO_edge_flag_test(bm, e_pair[0], EDGE_CHAIN) ||
660 BMO_edge_flag_test(bm, e_pair[1], EDGE_CHAIN))
661 {
662 /* VERT_MARK remains enabled. */
663 continue;
664 }
665
666 /* If the angle at the vert is larger than the threshold, it cannot be merged. */
667 if (bmo_vert_calc_edge_angle_blended(v) > angle_threshold - angle_epsilon) {
669 continue;
670 }
671 }
672
673 /* Dissolve all verts that remain tagged. This is done in a separate iteration pass. Otherwise
674 * the early dissolves would alter the angles measured at neighboring verts tested later. */
675 BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
677 continue;
678 }
679
680 /* Even though pairs were checked before, the process of performing edge merges
681 * might change a neighboring vert such that it is no longer an edge pair. */
682 if (!BM_vert_is_edge_pair(v)) {
683 continue;
684 }
685
687 }
688 }
689}
690
692{
693 BMOIter oiter;
694 BMIter iter;
695 BMVert *v, *v_next;
696 BMEdge *e, *e_next;
697
698 const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
699 const bool use_boundary_tear = BMO_slot_bool_get(op->slots_in, "use_boundary_tear");
700
701 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
703 }
704
705 if (use_face_split) {
706 bm_face_split(bm, VERT_MARK, false);
707 }
708
709 if (use_boundary_tear) {
710 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
711 if (!BM_vert_is_edge_pair(v)) {
712 BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
713 if (BM_edge_is_boundary(e)) {
715 break;
716 }
717 }
718 }
719 }
720
722 }
723
724 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
725 BMIter itersub;
726 BMLoop *l_first;
727 BMEdge *e_first = nullptr;
728 BM_ITER_ELEM (l_first, &itersub, v, BM_LOOPS_OF_VERT) {
729 BMLoop *l_iter;
730 l_iter = l_first;
731 do {
734 } while ((l_iter = l_iter->next) != l_first);
735
736 e_first = l_first->e;
737 }
738
739 /* important e_first won't be deleted */
740 if (e_first) {
741 e = e_first;
742 do {
743 e_next = BM_DISK_EDGE_NEXT(e, v);
744 if (BM_edge_is_wire(e)) {
745 BM_edge_kill(bm, e);
746 }
747 } while ((e = e_next) != e_first);
748 }
749 }
750
751 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
752 /* tag here so we avoid feedback loop (checking topology as we edit) */
753 if (BM_vert_is_edge_pair(v)) {
755 }
756 }
757
758 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
759 BMIter itersub;
760
761 /* Merge across every edge that touches `v`. This does a `BM_faces_join_pair()` for each edge.
762 * There may be a possible performance improvement available here, for high valence verts.
763 * Collecting a list of 20 faces and performing a single `BM_faces_join` would almost certainly
764 * more performant than doing 19 separate `BM_faces_join_pair()` of 2 faces each in sequence.
765 * Low valence verts would need benchmarking, to check that such a change isn't harmful. */
767 BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
768 BMLoop *l_a, *l_b;
769 if (BM_edge_loop_pair(e, &l_a, &l_b)) {
770 BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
771 }
772 }
773 }
774 }
775
776 /* Cleanup geometry (#BM_faces_join_pair, but it removes geometry we're looping on)
777 * so do this in a separate pass instead. */
778 BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
779 if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
780 BM_edge_kill(bm, e);
781 }
782 }
783
784 /* final cleanup */
785 BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
786 if (BM_vert_is_edge_pair(v)) {
788 }
789 }
790
791 BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
792 if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
793 BM_vert_kill(bm, v);
794 }
795 }
796 /* done with cleanup */
797}
798
800{
801 BMOpSlot *einput = BMO_slot_get(op->slots_in, "edges");
802 BMOpSlot *vinput = BMO_slot_get(op->slots_in, "verts");
803 const float angle_max = M_PI_2;
804 const float angle_limit = min_ff(angle_max, BMO_slot_float_get(op->slots_in, "angle_limit"));
805 const bool do_dissolve_boundaries = BMO_slot_bool_get(op->slots_in, "use_dissolve_boundaries");
806 const BMO_Delimit delimit = BMO_Delimit(BMO_slot_int_get(op->slots_in, "delimit"));
807
809 angle_limit,
810 do_dissolve_boundaries,
811 delimit,
812 (BMVert **)BMO_SLOT_AS_BUFFER(vinput),
813 vinput->len,
814 (BMEdge **)BMO_SLOT_AS_BUFFER(einput),
815 einput->len,
816 FACE_NEW);
817
819}
820
821#define EDGE_MARK 1
822#define EDGE_COLLAPSE 2
823
824static void bm_mesh_edge_collapse_flagged(BMesh *bm, const int flag, const short oflag)
825{
826 BMO_op_callf(bm, flag, "collapse edges=%fe uvs=%b", oflag, true);
827}
828
830{
831 const float dist = BMO_slot_float_get(op->slots_in, "dist");
832 const float dist_sq = dist * dist;
833
834 bool found;
835 BMIter eiter;
836 BMEdge *e;
837
839
840 /* collapse zero length edges, this accounts for zero area faces too */
841 found = false;
842 BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
844 if (BM_edge_calc_length_squared(e) < dist_sq) {
846 found = true;
847 }
848 }
849
850 /* clear all loop tags (checked later) */
851 if (e->l) {
852 BMLoop *l_iter, *l_first;
853 l_iter = l_first = e->l;
854 do {
856 } while ((l_iter = l_iter->radial_next) != l_first);
857 }
858 }
859
860 if (found) {
862 }
863
864 /* clip degenerate ears from the face */
865 found = false;
866 BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
867 if (e->l && BMO_edge_flag_test(bm, e, EDGE_MARK)) {
868 BMLoop *l_iter, *l_first;
869 l_iter = l_first = e->l;
870 do {
871 if (
872 /* check the loop hasn't already been tested (and flag not to test again) */
873 !BM_elem_flag_test(l_iter, BM_ELEM_TAG) &&
874 ((void)BM_elem_flag_enable(l_iter, BM_ELEM_TAG),
875
876 /* check we're marked to tested (radial edge already tested) */
877 BMO_edge_flag_test(bm, l_iter->prev->e, EDGE_MARK) &&
878
879 /* check edges are not already going to be collapsed */
880 !BMO_edge_flag_test(bm, l_iter->e, EDGE_COLLAPSE) &&
882 {
883 /* test if the faces loop (ear) is degenerate */
884 float dir_prev[3], len_prev;
885 float dir_next[3], len_next;
886
887 sub_v3_v3v3(dir_prev, l_iter->prev->v->co, l_iter->v->co);
888 sub_v3_v3v3(dir_next, l_iter->next->v->co, l_iter->v->co);
889
890 len_prev = normalize_v3(dir_prev);
891 len_next = normalize_v3(dir_next);
892
893 if ((len_v3v3(dir_prev, dir_next) * min_ff(len_prev, len_next)) <= dist) {
894 bool reset = false;
895
896 if (fabsf(len_prev - len_next) <= dist) {
897 /* both edges the same length */
898 if (l_iter->f->len == 3) {
899 /* ideally this would have been discovered with short edge test above */
901 found = true;
902 }
903 else {
904 /* add a joining edge and tag for removal */
905 BMLoop *l_split;
906 if (BM_face_split(
907 bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
908 {
910 found = true;
911 reset = true;
912 }
913 }
914 }
915 else if (len_prev < len_next) {
916 /* split 'l_iter->e', then join the vert with next */
917 BMVert *v_new;
918 BMEdge *e_new;
919 BMLoop *l_split;
920 v_new = BM_edge_split(bm, l_iter->e, l_iter->v, &e_new, len_prev / len_next);
921 BLI_assert(v_new == l_iter->next->v);
922 (void)v_new;
923 if (BM_face_split(
924 bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
925 {
927 found = true;
928 }
929 reset = true;
930 }
931 else if (len_next < len_prev) {
932 /* split 'l_iter->prev->e', then join the vert with next */
933 BMVert *v_new;
934 BMEdge *e_new;
935 BMLoop *l_split;
936 v_new = BM_edge_split(bm, l_iter->prev->e, l_iter->v, &e_new, len_next / len_prev);
937 BLI_assert(v_new == l_iter->prev->v);
938 (void)v_new;
939 if (BM_face_split(
940 bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
941 {
943 found = true;
944 }
945 reset = true;
946 }
947
948 if (reset) {
949 /* we can't easily track where we are on the radial edge, reset! */
950 l_first = l_iter;
951 }
952 }
953 }
954 } while ((l_iter = l_iter->radial_next) != l_first);
955 }
956 }
957
958 if (found) {
960 }
961}
962
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE float min_ff(float a, float b)
MINLINE float interpf(float target, float origin, float t)
#define M_PI_2
#define RAD2DEGF(_rad)
#define M_PI
MINLINE float len_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
float angle_on_axis_v3v3v3_v3(const float v1[3], const float v2[3], const float v3[3], const float axis[3]) ATTR_WARN_UNUSED_RESULT
float angle_v3v3v3(const float a[3], const float b[3], const float c[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v3(float n[3])
void BLI_stack_pop(BLI_Stack *stack, void *dst) ATTR_NONNULL()
Definition stack.cc:138
void BLI_stack_push(BLI_Stack *stack, const void *src) ATTR_NONNULL()
Definition stack.cc:132
bool BLI_stack_is_empty(const BLI_Stack *stack) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition stack.cc:250
void BLI_stack_free(BLI_Stack *stack) ATTR_NONNULL()
Definition stack.cc:96
#define BLI_stack_new(esize, descr)
unsigned int uint
#define UNUSED_FUNCTION(x)
#define UNUSED_VARS_NDEBUG(...)
#define UNLIKELY(x)
#define LIKELY(x)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
Read Guarded memory(de)allocation.
#define BM_DISK_EDGE_NEXT(e, v)
#define BM_FACE_FIRST_LOOP(p)
@ BM_ELEM_HIDDEN
@ BM_ELEM_SELECT
@ BM_ELEM_TAG
BMFace * BM_faces_join(BMesh *bm, BMFace **faces, int totface, const bool do_del, BMFace **r_double)
Join Connected Faces.
void BM_vert_kill(BMesh *bm, BMVert *v)
void BM_face_kill(BMesh *bm, BMFace *f)
void BM_edge_kill(BMesh *bm, BMEdge *e)
void BM_mesh_decimate_dissolve_ex(BMesh *bm, float angle_limit, bool do_dissolve_boundaries, BMO_Delimit delimit, BMVert **vinput_arr, int vinput_len, BMEdge **einput_arr, int einput_len, short oflag_out)
@ BMO_ERROR_FATAL
bool BMO_error_occurred_at_level(BMesh *bm, eBMOpErrorLevel level)
#define BM_elem_flag_merge_ex(ele_a, ele_b, hflag_and)
#define BM_elem_flag_disable(ele, hflag)
#define BM_elem_flag_test(ele, hflag)
#define BM_elem_flag_enable(ele, hflag)
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
@ BM_EDGES_OF_MESH
@ BM_VERTS_OF_MESH
@ BM_LOOPS_OF_VERT
@ BM_EDGES_OF_VERT
@ BM_LOOPS_OF_FACE
#define BM_ITER_MESH_MUTABLE(ele, ele_next, iter, bm, itype)
BMesh * bm
void BM_edge_select_set_noflush(BMesh *bm, BMEdge *e, const bool select)
BMVert * BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
Edge Split.
BMFace * BM_faces_join_pair(BMesh *bm, BMLoop *l_a, BMLoop *l_b, const bool do_del, BMFace **r_double)
Faces Join Pair.
BMEdge * BM_vert_collapse_edge(BMesh *bm, BMEdge *e_kill, BMVert *v_kill, const bool do_del, const bool kill_degenerate_faces, const bool kill_duplicate_faces)
Vert Collapse Faces.
BMFace * BM_face_split(BMesh *bm, BMFace *f, BMLoop *l_a, BMLoop *l_b, BMLoop **r_l, BMEdge *example, const bool no_double)
Face Split.
#define BM_FACE
#define BM_EDGE
#define BM_VERT
void BMO_slot_buffer_flag_enable(BMesh *bm, BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, char htype, short oflag)
BMO_FLAG_BUFFER.
#define BMO_vert_flag_disable(bm, e, oflag)
#define BMO_edge_flag_test(bm, e, oflag)
#define BMO_edge_flag_enable(bm, e, oflag)
float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float f)
#define BMO_vert_flag_enable(bm, e, oflag)
BMOpSlot * BMO_slot_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier)
BMESH OPSTACK GET SLOT.
#define BMO_vert_flag_set(bm, e, oflag, val)
#define BMO_SLOT_AS_BUFFER(slot)
void BMO_slot_buffer_from_enabled_flag(BMesh *bm, BMOperator *op, BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, char htype, short oflag)
#define BMO_face_flag_enable(bm, e, oflag)
#define BMO_ITER(ele, iter, slot_args, slot_name, restrict_flag)
#define BMO_vert_flag_test(bm, e, oflag)
int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
bool BMO_op_callf(BMesh *bm, int flag, const char *fmt,...)
#define BMO_face_flag_disable(bm, e, oflag)
#define BMO_face_flag_test(bm, e, oflag)
bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name)
ATTR_WARN_UNUSED_RESULT const BMFlagLayer const short oflag
float BM_face_calc_normal(const BMFace *f, float r_no[3])
BMESH UPDATE FACE NORMAL.
BMLoop * BM_loop_other_edge_loop(BMLoop *l, BMVert *v)
bool BM_edge_loop_pair(BMEdge *e, BMLoop **r_la, BMLoop **r_lb)
float BM_edge_calc_length_squared(const BMEdge *e)
bool BM_edge_face_pair(BMEdge *e, BMFace **r_fa, BMFace **r_fb)
bool BM_vert_is_edge_pair(const BMVert *v)
bool BM_vert_edge_pair(const BMVert *v, BMEdge **r_e_a, BMEdge **r_e_b)
BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
BLI_INLINE BMVert * BM_edge_other_vert(BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
BLI_INLINE bool BM_edge_is_wire(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMLoop * l_b
ATTR_WARN_UNUSED_RESULT const BMVert * v
void * BMW_begin(BMWalker *walker, void *start)
void BMW_init(BMWalker *walker, BMesh *bm, int type, short mask_vert, short mask_edge, short mask_face, BMWFlag flag, int layer)
Initialize Walker.
void BMW_end(BMWalker *walker)
End Walker.
void * BMW_step(BMWalker *walker)
Step Walker.
#define BMW_NIL_LAY
@ BMW_FLAG_NOP
#define BMW_MASK_NOP
@ BMW_ISLAND_MANIFOLD
@ BMW_ISLAND
#define FACE_MARK
#define EDGE_MARK
Definition bmo_bridge.cc:26
#define FACE_TAG
#define VERT_MARK
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
#define VERT_MARK_TEAR
#define FACE_ORIG
#define FACE_NEW
void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op)
static float bmo_vert_calc_edge_angle_blended(const BMVert *v)
static void bm_mesh_edge_collapse_flagged(BMesh *bm, const int flag, const short oflag)
static BMVert * bmo_find_end_of_chain(BMesh *bm, BMEdge *e, BMVert *v, const short edge_oflag=0)
static bool UNUSED_FUNCTION check_hole_in_region(BMesh *bm, BMFace *f)
void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op)
#define VERT_TAG
#define EDGE_TAG
#define EDGE_ISGC
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op)
#define EDGE_CHAIN
#define VERT_ISGC
static BMEdge * bm_vert_collapse_edge_and_merge(BMesh *bm, BMVert *v, const bool do_del)
static int bmo_vert_tagged_edges_count_at_most(BMesh *bm, BMVert *v, const short edge_oflag, const int max)
void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op)
static void bm_face_split(BMesh *bm, const short oflag, bool use_edge_delete)
static bool bmo_vert_touches_unselected_face(BMesh *bm, BMVert *v)
void bmo_dissolve_edges_init(BMOperator *op)
#define EDGE_COLLAPSE
#define VERT_MARK_PAIR
long long int int64_t
void reset()
clear internal cached data and reset random seed
void append_as(ForwardValue &&...value)
static char faces[256]
#define fabsf
BMVert * v1
BMVert * v2
struct BMVert * v
struct BMEdge * e
struct BMLoop * radial_next
struct BMLoop * prev
struct BMFace * f
struct BMLoop * next
struct BMOpSlot slots_out[BMO_OP_MAX_SLOTS]
struct BMOpSlot slots_in[BMO_OP_MAX_SLOTS]
float co[3]
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
uint8_t flag
Definition wm_window.cc:145