1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 */
16
17 /** \file
18 * \ingroup bmesh
19 *
20 * Find the region defined by the path(s) between 2 elements.
21 * (path isn't ordered).
22 */
23
24 #include "MEM_guardedalloc.h"
25
26 #include "BLI_alloca.h"
27 #include "BLI_linklist.h"
28 #include "BLI_math.h"
29 #include "BLI_utildefines_stack.h"
30
31 #include "bmesh.h"
32 #include "bmesh_path_region.h" /* own include */
33
34 /**
35 * Special handling of vertices with 2 edges
36 * (act as if the edge-chain is a single edge).
37 *
38 * \note Regarding manifold edge stepping: #BM_vert_is_edge_pair_manifold usage.
39 * Logic to skip a chain of vertices is not applied at boundaries because it gives
40 * strange behavior from a user perspective especially with boundary quads, see: T52701
41 *
42 * Restrict walking over a vertex chain to cases where the edges share the same faces.
43 * This is more typical of what a user would consider a vertex chain.
44 */
45 #define USE_EDGE_CHAIN
46
47 #ifdef USE_EDGE_CHAIN
48 /**
49 * Takes a vertex with 2 edge users and assigns the vertices at each end-point,
50 *
51 * \return Success when \a v_end_pair values are set or false if the edges loop back on themselves.
52 */
bm_vert_pair_ends(BMVert * v_pivot,BMVert * v_end_pair[2])53 static bool bm_vert_pair_ends(BMVert *v_pivot, BMVert *v_end_pair[2])
54 {
55 BMEdge *e = v_pivot->e;
56 int j = 0;
57 do {
58 BMEdge *e_chain = e;
59 BMVert *v_other = BM_edge_other_vert(e_chain, v_pivot);
60 while (BM_vert_is_edge_pair_manifold(v_other)) {
61 BMEdge *e_chain_next = BM_DISK_EDGE_NEXT(e_chain, v_other);
62 BLI_assert(BM_DISK_EDGE_NEXT(e_chain_next, v_other) == e_chain);
63 v_other = BM_edge_other_vert(e_chain_next, v_other);
64 if (v_other == v_pivot) {
65 return false;
66 }
67 e_chain = e_chain_next;
68 }
69 v_end_pair[j++] = v_other;
70 } while ((e = BM_DISK_EDGE_NEXT(e, v_pivot)) != v_pivot->e);
71
72 BLI_assert(j == 2);
73 return true;
74 }
75 #endif /* USE_EDGE_CHAIN */
76
77 /** \name Vertex in Region Checks
78 * \{ */
79
bm_vert_region_test(BMVert * v,int * const depths[2],const int pass)80 static bool bm_vert_region_test(BMVert *v, int *const depths[2], const int pass)
81 {
82 const int index = BM_elem_index_get(v);
83 return (((depths[0][index] != -1) && (depths[1][index] != -1)) &&
84 ((depths[0][index] + depths[1][index]) < pass));
85 }
86
87 #ifdef USE_EDGE_CHAIN
bm_vert_region_test_chain(BMVert * v,int * const depths[2],const int pass)88 static bool bm_vert_region_test_chain(BMVert *v, int *const depths[2], const int pass)
89 {
90 BMVert *v_end_pair[2];
91 if (bm_vert_region_test(v, depths, pass)) {
92 return true;
93 }
94 if (BM_vert_is_edge_pair_manifold(v) && bm_vert_pair_ends(v, v_end_pair) &&
95 bm_vert_region_test(v_end_pair[0], depths, pass) &&
96 bm_vert_region_test(v_end_pair[1], depths, pass)) {
97 return true;
98 }
99
100 return false;
101 }
102 #else
bm_vert_region_test_chain(BMVert * v,int * const depths[2],const int pass)103 static bool bm_vert_region_test_chain(BMVert *v, int *const depths[2], const int pass)
104 {
105 return bm_vert_region_test(v, depths, pass);
106 }
107 #endif
108
109 /** \} */
110
111 /**
112 * Main logic for calculating region between 2 elements.
113 *
114 * This method works walking (breadth first) over all vertices,
115 * keeping track of topological distance from the source.
116 *
117 * This is done in both directions, after that each vertices 'depth' is added to check
118 * if its less than the number of passes needed to complete the search.
119 * When it is, we know the path is one of possible paths
120 * that have the minimum topological distance.
121 *
122 * \note Only verts without BM_ELEM_TAG will be walked over.
123 */
mesh_calc_path_region_elem(BMesh * bm,BMElem * ele_src,BMElem * ele_dst,const char path_htype)124 static LinkNode *mesh_calc_path_region_elem(BMesh *bm,
125 BMElem *ele_src,
126 BMElem *ele_dst,
127 const char path_htype)
128 {
129 int ele_verts_len[2];
130 BMVert **ele_verts[2];
131
132 /* Get vertices from any `ele_src/ele_dst` elements. */
133 for (int side = 0; side < 2; side++) {
134 BMElem *ele = side ? ele_dst : ele_src;
135 int j = 0;
136
137 if (ele->head.htype == BM_FACE) {
138 BMFace *f = (BMFace *)ele;
139 ele_verts[side] = BLI_array_alloca(ele_verts[side], f->len);
140
141 BMLoop *l_first, *l_iter;
142 l_iter = l_first = BM_FACE_FIRST_LOOP(f);
143 do {
144 ele_verts[side][j++] = l_iter->v;
145 } while ((l_iter = l_iter->next) != l_first);
146 }
147 else if (ele->head.htype == BM_EDGE) {
148 BMEdge *e = (BMEdge *)ele;
149 ele_verts[side] = BLI_array_alloca(ele_verts[side], 2);
150
151 ele_verts[side][j++] = e->v1;
152 ele_verts[side][j++] = e->v2;
153 }
154 else if (ele->head.htype == BM_VERT) {
155 BMVert *v = (BMVert *)ele;
156 ele_verts[side] = BLI_array_alloca(ele_verts[side], 1);
157
158 ele_verts[side][j++] = v;
159 }
160 else {
161 BLI_assert(0);
162 }
163 ele_verts_len[side] = j;
164 }
165
166 int *depths[2] = {NULL};
167 int pass = 0;
168
169 BMVert **stack = MEM_mallocN(sizeof(*stack) * bm->totvert, __func__);
170 BMVert **stack_other = MEM_mallocN(sizeof(*stack_other) * bm->totvert, __func__);
171
172 STACK_DECLARE(stack);
173 STACK_INIT(stack, bm->totvert);
174
175 STACK_DECLARE(stack_other);
176 STACK_INIT(stack_other, bm->totvert);
177
178 BM_mesh_elem_index_ensure(bm, BM_VERT);
179
180 /* After exhausting all possible elements, we should have found all elements on the 'side_other'.
181 * otherwise, exit early. */
182 bool found_all = false;
183
184 for (int side = 0; side < 2; side++) {
185 const int side_other = !side;
186
187 /* initialize depths to -1 (un-touched), fill in with the depth as we walk over the edges. */
188 depths[side] = MEM_mallocN(sizeof(*depths[side]) * bm->totvert, __func__);
189 copy_vn_i(depths[side], bm->totvert, -1);
190
191 /* needed for second side */
192 STACK_CLEAR(stack);
193 STACK_CLEAR(stack_other);
194
195 for (int i = 0; i < ele_verts_len[side]; i++) {
196 BMVert *v = ele_verts[side][i];
197 depths[side][BM_elem_index_get(v)] = 0;
198 if (v->e && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
199 STACK_PUSH(stack, v);
200 }
201 }
202
203 #ifdef USE_EDGE_CHAIN
204 /* Expand initial state to end-point vertices when they only have 2x edges,
205 * this prevents odd behavior when source or destination are in the middle
206 * of a long chain of edges. */
207 if (ELEM(path_htype, BM_VERT, BM_EDGE)) {
208 for (int i = 0; i < ele_verts_len[side]; i++) {
209 BMVert *v = ele_verts[side][i];
210 BMVert *v_end_pair[2];
211 if (BM_vert_is_edge_pair_manifold(v) && bm_vert_pair_ends(v, v_end_pair)) {
212 for (int j = 0; j < 2; j++) {
213 const int v_end_index = BM_elem_index_get(v_end_pair[j]);
214 if (depths[side][v_end_index] == -1) {
215 depths[side][v_end_index] = 0;
216 if (!BM_elem_flag_test(v_end_pair[j], BM_ELEM_TAG)) {
217 STACK_PUSH(stack, v_end_pair[j]);
218 }
219 }
220 }
221 }
222 }
223 }
224 #endif /* USE_EDGE_CHAIN */
225
226 /* Keep walking over connected geometry until we find all the vertices in
227 * `ele_verts[side_other]`, or exit the loop when there's no connection. */
228 found_all = false;
229 for (pass = 1; (STACK_SIZE(stack) != 0); pass++) {
230 while (STACK_SIZE(stack) != 0) {
231 BMVert *v_a = STACK_POP(stack);
232 // const int v_a_index = BM_elem_index_get(v_a); /* only for assert */
233 BMEdge *e = v_a->e;
234
235 do {
236 BMVert *v_b = BM_edge_other_vert(e, v_a);
237 int v_b_index = BM_elem_index_get(v_b);
238 if (depths[side][v_b_index] == -1) {
239
240 #ifdef USE_EDGE_CHAIN
241 /* Walk along the chain, fill in values until we reach a vertex with 3+ edges. */
242 {
243 BMEdge *e_chain = e;
244 while (BM_vert_is_edge_pair_manifold(v_b) && ((depths[side][v_b_index] == -1))) {
245 depths[side][v_b_index] = pass;
246
247 BMEdge *e_chain_next = BM_DISK_EDGE_NEXT(e_chain, v_b);
248 BLI_assert(BM_DISK_EDGE_NEXT(e_chain_next, v_b) == e_chain);
249 v_b = BM_edge_other_vert(e_chain_next, v_b);
250 v_b_index = BM_elem_index_get(v_b);
251 e_chain = e_chain_next;
252 }
253 }
254 #endif /* USE_EDGE_CHAIN */
255
256 /* Add the other vertex to the stack, to be traversed in the next pass. */
257 if (depths[side][v_b_index] == -1) {
258 #ifdef USE_EDGE_CHAIN
259 BLI_assert(!BM_vert_is_edge_pair_manifold(v_b));
260 #endif
261 BLI_assert(pass == depths[side][BM_elem_index_get(v_a)] + 1);
262 depths[side][v_b_index] = pass;
263 if (!BM_elem_flag_test(v_b, BM_ELEM_TAG)) {
264 STACK_PUSH(stack_other, v_b);
265 }
266 }
267 }
268 } while ((e = BM_DISK_EDGE_NEXT(e, v_a)) != v_a->e);
269 }
270
271 /* Stop searching once there's none left.
272 * Note that this looks in-efficient, however until the target elements reached,
273 * it will exit immediately.
274 * After that, it takes as many passes as the element has edges to finish off. */
275 found_all = true;
276 for (int i = 0; i < ele_verts_len[side_other]; i++) {
277 if (depths[side][BM_elem_index_get(ele_verts[side_other][i])] == -1) {
278 found_all = false;
279 break;
280 }
281 }
282 if (found_all == true) {
283 pass++;
284 break;
285 }
286
287 STACK_SWAP(stack, stack_other);
288 }
289
290 /* if we have nothing left, and didn't find all elements on the other side,
291 * exit early and don't continue */
292 if (found_all == false) {
293 break;
294 }
295 }
296
297 MEM_freeN(stack);
298 MEM_freeN(stack_other);
299
300 /* Now we have depths recorded from both sides,
301 * select elements that use tagged verts. */
302 LinkNode *path = NULL;
303
304 if (found_all == false) {
305 /* fail! (do nothing) */
306 }
307 else if (path_htype == BM_FACE) {
308 BMIter fiter;
309 BMFace *f;
310
311 BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
312 if (!BM_elem_flag_test(f, BM_ELEM_TAG)) {
313 /* check all verts in face are tagged */
314 BMLoop *l_first, *l_iter;
315 l_iter = l_first = BM_FACE_FIRST_LOOP(f);
316 bool ok = true;
317 #if 0
318 do {
319 if (!bm_vert_region_test_chain(l_iter->v, depths, pass)) {
320 ok = false;
321 break;
322 }
323 } while ((l_iter = l_iter->next) != l_first);
324 #else
325 /* Allowing a single failure on a face gives fewer 'gaps'.
326 * While correct, in practice they're often part of what
327 * a user would consider the 'region'. */
328 int ok_tests = f->len > 3 ? 1 : 0; /* how many times we may fail */
329 do {
330 if (!bm_vert_region_test_chain(l_iter->v, depths, pass)) {
331 if (ok_tests == 0) {
332 ok = false;
333 break;
334 }
335 ok_tests--;
336 }
337 } while ((l_iter = l_iter->next) != l_first);
338 #endif
339
340 if (ok) {
341 BLI_linklist_prepend(&path, f);
342 }
343 }
344 }
345 }
346 else if (path_htype == BM_EDGE) {
347 BMIter eiter;
348 BMEdge *e;
349
350 BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
351 if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
352 /* check all verts in edge are tagged */
353 bool ok = true;
354 for (int j = 0; j < 2; j++) {
355 if (!bm_vert_region_test_chain(*((&e->v1) + j), depths, pass)) {
356 ok = false;
357 break;
358 }
359 }
360
361 if (ok) {
362 BLI_linklist_prepend(&path, e);
363 }
364 }
365 }
366 }
367 else if (path_htype == BM_VERT) {
368 BMIter viter;
369 BMVert *v;
370
371 BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
372 if (bm_vert_region_test_chain(v, depths, pass)) {
373 BLI_linklist_prepend(&path, v);
374 }
375 }
376 }
377
378 for (int side = 0; side < 2; side++) {
379 if (depths[side]) {
380 MEM_freeN(depths[side]);
381 }
382 }
383
384 return path;
385 }
386
387 #undef USE_EDGE_CHAIN
388
389 /** \name Main Functions (exposed externally).
390 * \{ */
391
BM_mesh_calc_path_region_vert(BMesh * bm,BMElem * ele_src,BMElem * ele_dst,bool (* filter_fn)(BMVert *,void * user_data),void * user_data)392 LinkNode *BM_mesh_calc_path_region_vert(BMesh *bm,
393 BMElem *ele_src,
394 BMElem *ele_dst,
395 bool (*filter_fn)(BMVert *, void *user_data),
396 void *user_data)
397 {
398 LinkNode *path = NULL;
399 /* BM_ELEM_TAG flag is used to store visited verts */
400 BMVert *v;
401 BMIter viter;
402 int i;
403
404 BM_ITER_MESH_INDEX (v, &viter, bm, BM_VERTS_OF_MESH, i) {
405 BM_elem_flag_set(v, BM_ELEM_TAG, !filter_fn(v, user_data));
406 BM_elem_index_set(v, i); /* set_inline */
407 }
408 bm->elem_index_dirty &= ~BM_VERT;
409
410 path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_VERT);
411
412 return path;
413 }
414
BM_mesh_calc_path_region_edge(BMesh * bm,BMElem * ele_src,BMElem * ele_dst,bool (* filter_fn)(BMEdge *,void * user_data),void * user_data)415 LinkNode *BM_mesh_calc_path_region_edge(BMesh *bm,
416 BMElem *ele_src,
417 BMElem *ele_dst,
418 bool (*filter_fn)(BMEdge *, void *user_data),
419 void *user_data)
420 {
421 LinkNode *path = NULL;
422 /* BM_ELEM_TAG flag is used to store visited verts */
423 BMEdge *e;
424 BMIter eiter;
425 int i;
426
427 /* flush flag to verts */
428 BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
429
430 BM_ITER_MESH_INDEX (e, &eiter, bm, BM_EDGES_OF_MESH, i) {
431 bool test;
432 BM_elem_flag_set(e, BM_ELEM_TAG, test = !filter_fn(e, user_data));
433
434 /* flush tag to verts */
435 if (test == false) {
436 for (int j = 0; j < 2; j++) {
437 BM_elem_flag_disable(*((&e->v1) + j), BM_ELEM_TAG);
438 }
439 }
440 }
441
442 path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_EDGE);
443
444 return path;
445 }
446
BM_mesh_calc_path_region_face(BMesh * bm,BMElem * ele_src,BMElem * ele_dst,bool (* filter_fn)(BMFace *,void * user_data),void * user_data)447 LinkNode *BM_mesh_calc_path_region_face(BMesh *bm,
448 BMElem *ele_src,
449 BMElem *ele_dst,
450 bool (*filter_fn)(BMFace *, void *user_data),
451 void *user_data)
452 {
453 LinkNode *path = NULL;
454 /* BM_ELEM_TAG flag is used to store visited verts */
455 BMFace *f;
456 BMIter fiter;
457 int i;
458
459 /* flush flag to verts */
460 BM_mesh_elem_hflag_enable_all(bm, BM_VERT, BM_ELEM_TAG, false);
461
462 BM_ITER_MESH_INDEX (f, &fiter, bm, BM_FACES_OF_MESH, i) {
463 bool test;
464 BM_elem_flag_set(f, BM_ELEM_TAG, test = !filter_fn(f, user_data));
465
466 /* flush tag to verts */
467 if (test == false) {
468 BMLoop *l_first, *l_iter;
469 l_iter = l_first = BM_FACE_FIRST_LOOP(f);
470 do {
471 BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
472 } while ((l_iter = l_iter->next) != l_first);
473 }
474 }
475
476 path = mesh_calc_path_region_elem(bm, ele_src, ele_dst, BM_FACE);
477
478 return path;
479 }
480
481 /** \} */
482