Blender V5.0
bmesh_decimate_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 "MEM_guardedalloc.h"
12
13#include "BLI_heap.h"
14#include "BLI_math_geom.h"
15#include "BLI_math_rotation.h"
16#include "BLI_math_vector.h"
17
18#include "BKE_customdata.hh"
19
20#include "bmesh.hh"
21#include "bmesh_decimate.hh" /* own include */
22
23/* check that collapsing a vertex between 2 edges doesn't cause a degenerate face. */
24#define USE_DEGENERATE_CHECK
25
26#define COST_INVALID FLT_MAX
27
28namespace {
29
30struct DelimitData {
31 int cd_loop_type;
32 int cd_loop_size;
33 int cd_loop_offset;
34 int cd_loop_offset_end;
35};
36
37} // namespace
38
39static bool bm_edge_is_delimiter(const BMEdge *e,
40 const BMO_Delimit delimit,
41 const DelimitData *delimit_data);
42static bool bm_vert_is_delimiter(const BMVert *v,
43 const BMO_Delimit delimit,
44 const DelimitData *delimit_data);
45
46/* multiply vertex edge angle by face angle
47 * this means we are not left with sharp corners between _almost_ planer faces
48 * convert angles [0-PI/2] -> [0-1], multiply together, then convert back to radians. */
50 const BMO_Delimit delimit,
51 const DelimitData *delimit_data)
52{
53#define UNIT_TO_ANGLE DEG2RADF(90.0f)
54#define ANGLE_TO_UNIT (1.0f / UNIT_TO_ANGLE)
55
56 const float angle = BM_vert_calc_edge_angle(v);
57 /* NOTE: could be either edge, it doesn't matter. */
58 if (v->e && BM_edge_is_manifold(v->e)) {
59 /* Checking delimited is important here,
60 * otherwise, for example, the boundary between two materials
61 * will collapse if the faces on either side of the edge have a small angle.
62 *
63 * This way, delimiting edges are treated like boundary edges,
64 * the detail between two delimiting regions won't over-collapse. */
65 if (!bm_vert_is_delimiter(v, delimit, delimit_data)) {
68 }
69 }
70 return angle;
71
72#undef UNIT_TO_ANGLE
73#undef ANGLE_TO_UNIT
74}
75
76static bool bm_edge_is_contiguous_loop_cd_all(const BMEdge *e, const DelimitData *delimit_data)
77{
78 int cd_loop_offset;
79 for (cd_loop_offset = delimit_data->cd_loop_offset;
80 cd_loop_offset < delimit_data->cd_loop_offset_end;
81 cd_loop_offset += delimit_data->cd_loop_size)
82 {
83 if (BM_edge_is_contiguous_loop_cd(e, delimit_data->cd_loop_type, cd_loop_offset) == false) {
84 return false;
85 }
86 }
87
88 return true;
89}
90
91static bool bm_edge_is_delimiter(const BMEdge *e,
92 const BMO_Delimit delimit,
93 const DelimitData *delimit_data)
94{
95 /* Caller must ensure. */
97
98 if (delimit != 0) {
99 if (delimit & BMO_DELIM_SEAM) {
101 return true;
102 }
103 }
104 if (delimit & BMO_DELIM_SHARP) {
106 return true;
107 }
108 }
109 if (delimit & BMO_DELIM_MATERIAL) {
110 if (e->l->f->mat_nr != e->l->radial_next->f->mat_nr) {
111 return true;
112 }
113 }
114 if (delimit & BMO_DELIM_NORMAL) {
115 if (!BM_edge_is_contiguous(e)) {
116 return true;
117 }
118 }
119 if (delimit & BMO_DELIM_UV) {
120 if (bm_edge_is_contiguous_loop_cd_all(e, delimit_data) == 0) {
121 return true;
122 }
123 }
124 }
125
126 return false;
127}
128
129static bool bm_vert_is_delimiter(const BMVert *v,
130 const BMO_Delimit delimit,
131 const DelimitData *delimit_data)
132{
133 BLI_assert(v->e != nullptr);
134
135 if (delimit != 0) {
136 const BMEdge *e, *e_first;
137 e = e_first = v->e;
138 do {
139 if (BM_edge_is_manifold(e)) {
140 if (bm_edge_is_delimiter(e, delimit, delimit_data)) {
141 return true;
142 }
143 }
144 } while ((e = BM_DISK_EDGE_NEXT(e, v)) != e_first);
145 }
146 return false;
147}
148
150 const BMO_Delimit delimit,
151 const DelimitData *delimit_data)
152{
153 if (BM_edge_is_manifold(e) && !bm_edge_is_delimiter(e, delimit, delimit_data)) {
154 float angle_cos_neg = dot_v3v3(e->l->f->no, e->l->radial_next->f->no);
156 angle_cos_neg *= -1;
157 }
158 return angle_cos_neg;
159 }
160
161 return COST_INVALID;
162}
163
164#ifdef USE_DEGENERATE_CHECK
165
166static void mul_v2_m3v3_center(float r[2],
167 const float m[3][3],
168 const float a[3],
169 const float center[3])
170{
171 BLI_assert(r != a);
172 BLI_assert(r != center);
173
174 float co[3];
175 sub_v3_v3v3(co, a, center);
176
177 r[0] = m[0][0] * co[0] + m[1][0] * co[1] + m[2][0] * co[2];
178 r[1] = m[0][1] * co[0] + m[1][1] * co[1] + m[2][1] * co[2];
179}
180
182{
183 /* Calculate relative to the central vertex for higher precision. */
184 const float *center = l_ear->v->co;
185
186 float tri_2d[3][2];
187 float axis_mat[3][3];
188
189 axis_dominant_v3_to_m3(axis_mat, l_ear->f->no);
190
191 {
192 mul_v2_m3v3_center(tri_2d[0], axis_mat, l_ear->prev->v->co, center);
193# if 0
194 mul_v2_m3v3_center(tri_2d[1], axis_mat, l_ear->v->co, center);
195# else
196 zero_v2(tri_2d[1]);
197# endif
198 mul_v2_m3v3_center(tri_2d[2], axis_mat, l_ear->next->v->co, center);
199 }
200
201 /* check we're not flipping face corners before or after the ear */
202 {
203 float adjacent_2d[2];
204
205 if (!BM_vert_is_edge_pair(l_ear->prev->v)) {
206 mul_v2_m3v3_center(adjacent_2d, axis_mat, l_ear->prev->prev->v->co, center);
207 if (signum_i(cross_tri_v2(adjacent_2d, tri_2d[0], tri_2d[1])) !=
208 signum_i(cross_tri_v2(adjacent_2d, tri_2d[0], tri_2d[2])))
209 {
210 return true;
211 }
212 }
213
214 if (!BM_vert_is_edge_pair(l_ear->next->v)) {
215 mul_v2_m3v3_center(adjacent_2d, axis_mat, l_ear->next->next->v->co, center);
216 if (signum_i(cross_tri_v2(adjacent_2d, tri_2d[2], tri_2d[1])) !=
217 signum_i(cross_tri_v2(adjacent_2d, tri_2d[2], tri_2d[0])))
218 {
219 return true;
220 }
221 }
222 }
223
224 /* check no existing verts are inside the triangle */
225 {
226 /* triangle may be concave, if so - flip so we can use clockwise check */
227 if (cross_tri_v2(UNPACK3(tri_2d)) < 0.0f) {
228 swap_v2_v2(tri_2d[1], tri_2d[2]);
229 }
230
231 /* skip l_ear and adjacent verts */
232 BMLoop *l_iter, *l_first;
233
234 l_iter = l_ear->next->next;
235 l_first = l_ear->prev;
236 do {
237 float co_2d[2];
238 mul_v2_m3v3_center(co_2d, axis_mat, l_iter->v->co, center);
239 if (isect_point_tri_v2_cw(co_2d, tri_2d[0], tri_2d[1], tri_2d[2])) {
240 return true;
241 }
242 } while ((l_iter = l_iter->next) != l_first);
243 }
244
245 return false;
246}
247
249{
250 BMEdge *e_pair[2];
251 BMVert *v_pair[2];
252
253 if (BM_vert_edge_pair(v, &e_pair[0], &e_pair[1])) {
254
255 /* allow wire edges */
256 if (BM_edge_is_wire(e_pair[0]) || BM_edge_is_wire(e_pair[1])) {
257 return false;
258 }
259
260 v_pair[0] = BM_edge_other_vert(e_pair[0], v);
261 v_pair[1] = BM_edge_other_vert(e_pair[1], v);
262
263 if (fabsf(cos_v3v3v3(v_pair[0]->co, v->co, v_pair[1]->co)) < (1.0f - FLT_EPSILON)) {
264 BMLoop *l_iter, *l_first;
265 l_iter = l_first = e_pair[1]->l;
266 do {
267 if (l_iter->f->len > 3) {
268 BMLoop *l_pivot = (l_iter->v == v ? l_iter : l_iter->next);
269 BLI_assert(v == l_pivot->v);
270 if (bm_loop_collapse_is_degenerate(l_pivot)) {
271 return true;
272 }
273 }
274 } while ((l_iter = l_iter->radial_next) != l_first);
275 }
276 return false;
277 }
278 return true;
279}
280#endif /* USE_DEGENERATE_CHECK */
281
283 const float angle_limit,
284 const bool do_dissolve_boundaries,
285 BMO_Delimit delimit,
286 BMVert **vinput_arr,
287 const int vinput_len,
288 BMEdge **einput_arr,
289 const int einput_len,
290 const short oflag_out)
291{
292 const float angle_limit_cos_neg = -cosf(angle_limit);
293 DelimitData delimit_data = {0};
294 const int eheap_table_len = do_dissolve_boundaries ? einput_len : max_ii(einput_len, vinput_len);
295 void *_heap_table = MEM_malloc_arrayN<HeapNode *>(eheap_table_len, __func__);
296
297 int i;
298
299 if (delimit & BMO_DELIM_UV) {
300 const int layer_len = CustomData_number_of_layers(&bm->ldata, CD_PROP_FLOAT2);
301 if (layer_len == 0) {
302 delimit &= ~BMO_DELIM_UV;
303 }
304 else {
305 delimit_data.cd_loop_type = CD_PROP_FLOAT2;
306 delimit_data.cd_loop_size = CustomData_sizeof(eCustomDataType(delimit_data.cd_loop_type));
307 delimit_data.cd_loop_offset = CustomData_get_n_offset(&bm->ldata, CD_PROP_FLOAT2, 0);
308 delimit_data.cd_loop_offset_end = delimit_data.cd_loop_offset +
309 delimit_data.cd_loop_size * layer_len;
310 }
311 }
312
313 /* --- first edges --- */
314 if (true) {
315 BMEdge **earray;
316 Heap *eheap;
317 HeapNode **eheap_table = static_cast<HeapNode **>(_heap_table);
318 HeapNode *enode_top;
319 int *vert_reverse_lookup;
320 BMIter iter;
321 BMEdge *e_iter;
322
323 /* --- setup heap --- */
324 eheap = BLI_heap_new_ex(einput_len);
325
326 /* wire -> tag */
327 BM_ITER_MESH (e_iter, &iter, bm, BM_EDGES_OF_MESH) {
329 BM_elem_index_set(e_iter, -1); /* set dirty */
330 }
331 bm->elem_index_dirty |= BM_EDGE;
332
333 /* build heap */
334 for (i = 0; i < einput_len; i++) {
335 BMEdge *e = einput_arr[i];
336 const float cost = bm_edge_calc_dissolve_error(e, delimit, &delimit_data);
337 eheap_table[i] = BLI_heap_insert(eheap, cost, e);
338 BM_elem_index_set(e, i); /* set dirty */
339 }
340
341 while ((BLI_heap_is_empty(eheap) == false) &&
342 (BLI_heap_node_value(enode_top = BLI_heap_top(eheap)) < angle_limit_cos_neg))
343 {
344 BMFace *f_new = nullptr;
345 BMEdge *e;
346
347 e = static_cast<BMEdge *>(BLI_heap_node_ptr(enode_top));
349
350 if (BM_edge_is_manifold(e)) {
351 /* The `f_new` may be an existing face, see #144383.
352 * In this case it's still flagged as output so the selection
353 * isn't "lost" when dissolving, see: !144653. */
354 f_new = BM_faces_join_pair(bm, e->l, e->l->radial_next, false, nullptr);
355
356 if (f_new) {
357 BMLoop *l_first, *l_iter;
358
359 BLI_heap_remove(eheap, enode_top);
360 eheap_table[i] = nullptr;
361
362 /* update normal */
364 if (oflag_out) {
365 BMO_face_flag_enable(bm, f_new, oflag_out);
366 }
367
368 /* re-calculate costs */
369 l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
370 do {
371 const int j = BM_elem_index_get(l_iter->e);
372 if (j != -1 && eheap_table[j]) {
373 const float cost = bm_edge_calc_dissolve_error(l_iter->e, delimit, &delimit_data);
374 BLI_heap_node_value_update(eheap, eheap_table[j], cost);
375 }
376 } while ((l_iter = l_iter->next) != l_first);
377 }
378 }
379
380 if (UNLIKELY(f_new == nullptr)) {
381 BLI_heap_node_value_update(eheap, enode_top, COST_INVALID);
382 }
383 }
384
385 /* prepare for cleanup */
387 vert_reverse_lookup = MEM_malloc_arrayN<int>(bm->totvert, __func__);
388 copy_vn_i(vert_reverse_lookup, bm->totvert, -1);
389 for (i = 0; i < vinput_len; i++) {
390 BMVert *v = vinput_arr[i];
391 vert_reverse_lookup[BM_elem_index_get(v)] = i;
392 }
393
394 /* --- cleanup --- */
395 earray = MEM_malloc_arrayN<BMEdge *>(bm->totedge, __func__);
396 BM_ITER_MESH_INDEX (e_iter, &iter, bm, BM_EDGES_OF_MESH, i) {
397 earray[i] = e_iter;
398 }
399 /* Remove all edges/verts left behind from dissolving,
400 * nulling the vertex array so we don't re-use. */
401 for (i = bm->totedge - 1; i != -1; i--) {
402 e_iter = earray[i];
403
404 if (BM_edge_is_wire(e_iter) && (BM_elem_flag_test(e_iter, BM_ELEM_TAG) == false)) {
405 /* edge has become wire */
406 int vidx_reverse;
407 BMVert *v1 = e_iter->v1;
408 BMVert *v2 = e_iter->v2;
409 BM_edge_kill(bm, e_iter);
410 if (v1->e == nullptr) {
411 vidx_reverse = vert_reverse_lookup[BM_elem_index_get(v1)];
412 if (vidx_reverse != -1) {
413 vinput_arr[vidx_reverse] = nullptr;
414 }
415 BM_vert_kill(bm, v1);
416 }
417 if (v2->e == nullptr) {
418 vidx_reverse = vert_reverse_lookup[BM_elem_index_get(v2)];
419 if (vidx_reverse != -1) {
420 vinput_arr[vidx_reverse] = nullptr;
421 }
423 }
424 }
425 }
426 MEM_freeN(vert_reverse_lookup);
427 MEM_freeN(earray);
428
429 BLI_heap_free(eheap, nullptr);
430 }
431
432 /* --- second verts --- */
433 if (do_dissolve_boundaries) {
434 /* simple version of the branch below, since we will dissolve _all_ verts that use 2 edges */
435 for (i = 0; i < vinput_len; i++) {
436 BMVert *v = vinput_arr[i];
437 if (LIKELY(v != nullptr) && BM_vert_is_edge_pair(v)) {
438 BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
439 }
440 }
441 }
442 else {
443 Heap *vheap;
444 HeapNode **vheap_table = static_cast<HeapNode **>(_heap_table);
445 HeapNode *vnode_top;
446
447 BMVert *v_iter;
448 BMIter iter;
449
450 BM_ITER_MESH (v_iter, &iter, bm, BM_VERTS_OF_MESH) {
451 BM_elem_index_set(v_iter, -1); /* set dirty */
452 }
453 bm->elem_index_dirty |= BM_VERT;
454
455 vheap = BLI_heap_new_ex(vinput_len);
456
457 for (i = 0; i < vinput_len; i++) {
458 BMVert *v = vinput_arr[i];
459 if (LIKELY(v != nullptr)) {
460 const float cost = bm_vert_edge_face_angle(v, delimit, &delimit_data);
461 vheap_table[i] = BLI_heap_insert(vheap, cost, v);
462 BM_elem_index_set(v, i); /* set dirty */
463 }
464 }
465
466 while ((BLI_heap_is_empty(vheap) == false) &&
467 (BLI_heap_node_value(vnode_top = BLI_heap_top(vheap)) < angle_limit))
468 {
469 BMEdge *e_new = nullptr;
470 BMVert *v;
471
472 v = static_cast<BMVert *>(BLI_heap_node_ptr(vnode_top));
474
475 if (
478#else
480#endif
481 )
482 {
483 e_new = BM_vert_collapse_edge(bm, v->e, v, true, true, true); /* join edges */
484
485 if (e_new) {
486
487 BLI_heap_remove(vheap, vnode_top);
488 vheap_table[i] = nullptr;
489
490 /* update normal */
491 if (e_new->l) {
492 BMLoop *l_first, *l_iter;
493 l_iter = l_first = e_new->l;
494 do {
495 BM_face_normal_update(l_iter->f);
496 } while ((l_iter = l_iter->radial_next) != l_first);
497 }
498
499 /* re-calculate costs */
500 BM_ITER_ELEM (v_iter, &iter, e_new, BM_VERTS_OF_EDGE) {
501 const int j = BM_elem_index_get(v_iter);
502 if (j != -1 && vheap_table[j]) {
503 const float cost = bm_vert_edge_face_angle(v_iter, delimit, &delimit_data);
504 BLI_heap_node_value_update(vheap, vheap_table[j], cost);
505 }
506 }
507
508#ifdef USE_DEGENERATE_CHECK
509 /* dissolving a vertex may mean vertices we previously weren't able to dissolve
510 * can now be re-evaluated. */
511 if (e_new->l) {
512 BMLoop *l_first, *l_iter;
513 l_iter = l_first = e_new->l;
514 do {
515 /* skip vertices part of this edge, evaluated above */
516 BMLoop *l_cycle_first, *l_cycle_iter;
517 l_cycle_iter = l_iter->next->next;
518 l_cycle_first = l_iter->prev;
519 do {
520 const int j = BM_elem_index_get(l_cycle_iter->v);
521 if (j != -1 && vheap_table[j] &&
522 (BLI_heap_node_value(vheap_table[j]) == COST_INVALID))
523 {
524 const float cost = bm_vert_edge_face_angle(
525 l_cycle_iter->v, delimit, &delimit_data);
526 BLI_heap_node_value_update(vheap, vheap_table[j], cost);
527 }
528 } while ((l_cycle_iter = l_cycle_iter->next) != l_cycle_first);
529
530 } while ((l_iter = l_iter->radial_next) != l_first);
531 }
532#endif /* USE_DEGENERATE_CHECK */
533 }
534 }
535
536 if (UNLIKELY(e_new == nullptr)) {
537 BLI_heap_node_value_update(vheap, vnode_top, COST_INVALID);
538 }
539 }
540
541 BLI_heap_free(vheap, nullptr);
542 }
543
544 MEM_freeN(_heap_table);
545}
546
548 const float angle_limit,
549 const bool do_dissolve_boundaries,
550 const BMO_Delimit delimit)
551{
552 int vinput_len;
553 int einput_len;
554
555 BMVert **vinput_arr = static_cast<BMVert **>(
556 BM_iter_as_arrayN(bm, BM_VERTS_OF_MESH, nullptr, &vinput_len, nullptr, 0));
557 BMEdge **einput_arr = static_cast<BMEdge **>(
558 BM_iter_as_arrayN(bm, BM_EDGES_OF_MESH, nullptr, &einput_len, nullptr, 0));
559
561 angle_limit,
562 do_dissolve_boundaries,
563 delimit,
564 vinput_arr,
565 vinput_len,
566 einput_arr,
567 einput_len,
568 0);
569
570 MEM_freeN(vinput_arr);
571 MEM_freeN(einput_arr);
572}
CustomData interface, see also DNA_customdata_types.h.
int CustomData_sizeof(eCustomDataType type)
int CustomData_get_n_offset(const CustomData *data, eCustomDataType type, int n)
int CustomData_number_of_layers(const CustomData *data, eCustomDataType type)
#define BLI_assert(a)
Definition BLI_assert.h:46
A min-heap / priority queue ADT.
HeapNode * BLI_heap_top(const Heap *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition BLI_heap.cc:279
void BLI_heap_free(Heap *heap, HeapFreeFP ptrfreefp) ATTR_NONNULL(1)
Definition BLI_heap.cc:191
void void float BLI_heap_node_value(const HeapNode *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition BLI_heap.cc:347
Heap * BLI_heap_new_ex(unsigned int reserve_num) ATTR_WARN_UNUSED_RESULT
Definition BLI_heap.cc:171
void void bool BLI_heap_is_empty(const Heap *heap) ATTR_NONNULL(1)
Definition BLI_heap.cc:269
void BLI_heap_node_value_update(Heap *heap, HeapNode *node, float value) ATTR_NONNULL(1
void * BLI_heap_node_ptr(const HeapNode *heap) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition BLI_heap.cc:352
HeapNode * BLI_heap_insert(Heap *heap, float value, void *ptr) ATTR_NONNULL(1)
Definition BLI_heap.cc:234
void void BLI_heap_remove(Heap *heap, HeapNode *node) ATTR_NONNULL(1
MINLINE int max_ii(int a, int b)
MINLINE int signum_i(float a)
MINLINE float cross_tri_v2(const float v1[2], const float v2[2], const float v3[2])
bool isect_point_tri_v2_cw(const float pt[2], const float v1[2], const float v2[2], const float v3[2])
void axis_dominant_v3_to_m3(float r_mat[3][3], const float normal[3])
Normal to x,y matrix.
MINLINE void swap_v2_v2(float a[2], float b[2])
float cos_v3v3v3(const float p1[3], const float p2[3], const float p3[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
void copy_vn_i(int *array_tar, int size, int val)
MINLINE void zero_v2(float r[2])
#define UNPACK3(a)
#define UNLIKELY(x)
#define LIKELY(x)
@ CD_PROP_FLOAT2
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_SEAM
@ BM_ELEM_SMOOTH
@ BM_ELEM_TAG
void BM_vert_kill(BMesh *bm, BMVert *v)
void BM_edge_kill(BMesh *bm, BMEdge *e)
#define COST_INVALID
static bool bm_vert_is_delimiter(const BMVert *v, const BMO_Delimit delimit, const DelimitData *delimit_data)
void BM_mesh_decimate_dissolve(BMesh *bm, const float angle_limit, const bool do_dissolve_boundaries, const BMO_Delimit delimit)
static float bm_edge_calc_dissolve_error(const BMEdge *e, const BMO_Delimit delimit, const DelimitData *delimit_data)
#define ANGLE_TO_UNIT
#define UNIT_TO_ANGLE
static bool bm_edge_is_delimiter(const BMEdge *e, const BMO_Delimit delimit, const DelimitData *delimit_data)
static bool bm_loop_collapse_is_degenerate(BMLoop *l_ear)
static void mul_v2_m3v3_center(float r[2], const float m[3][3], const float a[3], const float center[3])
static float bm_vert_edge_face_angle(BMVert *v, const BMO_Delimit delimit, const DelimitData *delimit_data)
static bool bm_vert_collapse_is_degenerate(BMVert *v)
static bool bm_edge_is_contiguous_loop_cd_all(const BMEdge *e, const DelimitData *delimit_data)
void BM_mesh_decimate_dissolve_ex(BMesh *bm, const float angle_limit, const bool do_dissolve_boundaries, BMO_Delimit delimit, BMVert **vinput_arr, const int vinput_len, BMEdge **einput_arr, const int einput_len, const short oflag_out)
#define USE_DEGENERATE_CHECK
#define BM_elem_index_get(ele)
#define BM_elem_flag_set(ele, hflag, val)
#define BM_elem_index_set(ele, index)
#define BM_elem_flag_test(ele, hflag)
void * BM_iter_as_arrayN(BMesh *bm, const char itype, void *data, int *r_len, void **stack_array, int stack_array_size)
Iterator as Array.
#define BM_ITER_ELEM(ele, iter, data, itype)
#define BM_ITER_MESH(ele, iter, bm, itype)
#define BM_ITER_MESH_INDEX(ele, iter, bm, itype, indexvar)
@ BM_EDGES_OF_MESH
@ BM_VERTS_OF_MESH
@ BM_VERTS_OF_EDGE
BMesh * bm
void BM_mesh_elem_index_ensure(BMesh *bm, const char htype)
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.
#define BM_EDGE
#define BM_VERT
#define BMO_face_flag_enable(bm, e, oflag)
@ BMO_DELIM_NORMAL
@ BMO_DELIM_MATERIAL
@ BMO_DELIM_SEAM
@ BMO_DELIM_SHARP
@ BMO_DELIM_UV
void BM_face_normal_update(BMFace *f)
float BM_vert_calc_edge_angle(const BMVert *v)
bool BM_edge_is_contiguous_loop_cd(const BMEdge *e, const int cd_loop_type, const int cd_loop_offset)
float BM_edge_calc_face_angle(const BMEdge *e)
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_contiguous(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
BLI_INLINE bool BM_edge_is_manifold(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 BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define fabsf
#define cosf
BMVert * v1
BMVert * v2
struct BMLoop * l
float no[3]
struct BMVert * v
struct BMEdge * e
struct BMLoop * radial_next
struct BMLoop * prev
struct BMFace * f
struct BMLoop * next
float co[3]
struct BMEdge * e
i
Definition text_draw.cc:230