diff options
author | Campbell Barton <ideasman42@gmail.com> | 2015-12-12 19:46:36 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2015-12-12 19:46:36 +0300 |
commit | 9df6a539a2c93c2b8fe32d0d5b564c62bbadba9a (patch) | |
tree | 8f356a524f445259c4e0ae2d43d769e1cb582c22 | |
parent | f117f45d86c0c3c028b280c84c64e94ded7664ed (diff) |
Knife: use BM_face_split_edgenet for detecting holes
Knife had its own code for detecting holes which worked quite well,
but would prefer to use generic bmesh API call here.
-rw-r--r-- | source/blender/editors/mesh/editmesh_knife.c | 587 |
1 files changed, 93 insertions, 494 deletions
diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index 818526ab772..5b9af2fa75c 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -75,6 +75,9 @@ #include "mesh_intern.h" /* own include */ +/* detect isolated holes and fill them */ +#define USE_NET_ISLAND_CONNECT + #define KMAXDIST 10 /* max mouse distance from edge before not detecting it */ /* WARNING: knife float precision is fragile: @@ -166,6 +169,11 @@ typedef struct KnifeTool_OpData { MemArena *arena; +#ifdef USE_NET_ISLAND_CONNECT + /* cleared each use */ + MemArena *arena_edgenet; +#endif + GHash *origvertmap; GHash *origedgemap; GHash *kedgefacemap; @@ -2205,330 +2213,6 @@ static int sort_verts_by_dist_cb(void *co_p, const void *cur_a_p, const void *cu else return 0; } -/* The chain so far goes from an instantiated vertex to kfv (some may be reversed). - * If possible, complete the chain to another instantiated vertex and return 1, else return 0. - * The visited hash says which KnifeVert's have already been tried, not including kfv. */ -static bool find_chain_search(KnifeTool_OpData *kcd, KnifeVert *kfv, ListBase *fedges, SmallHash *visited, - ListBase *chain) -{ - Ref *r; - KnifeEdge *kfe; - KnifeVert *kfv_other; - - if (kfv->v) - return true; - - BLI_smallhash_insert(visited, (uintptr_t)kfv, NULL); - /* Try all possible next edges. Could either go through fedges - * (all the KnifeEdges for the face being cut) or could go through - * kve->edges and restrict to cutting face and uninstantiated edges. - * Not clear which is better. Let's do the first. */ - for (r = fedges->first; r; r = r->next) { - kfe = r->ref; - kfv_other = NULL; - if (kfe->v1 == kfv) - kfv_other = kfe->v2; - else if (kfe->v2 == kfv) - kfv_other = kfe->v1; - if (kfv_other && !BLI_smallhash_haskey(visited, (uintptr_t)kfv_other)) { - knife_append_list(kcd, chain, kfe); - if (find_chain_search(kcd, kfv_other, fedges, visited, chain)) - return true; - BLI_remlink(chain, chain->last); - } - } - return false; -} - -static ListBase *find_chain_from_vertex(KnifeTool_OpData *kcd, KnifeEdge *kfe, BMVert *v, ListBase *fedges) -{ - SmallHash visited_, *visited = &visited_; - ListBase *ans; - bool found; - - ans = knife_empty_list(kcd); - knife_append_list(kcd, ans, kfe); - found = false; - BLI_smallhash_init(visited); - if (kfe->v1->v == v) { - BLI_smallhash_insert(visited, (uintptr_t)(kfe->v1), NULL); - found = find_chain_search(kcd, kfe->v2, fedges, visited, ans); - } - else { - BLI_assert(kfe->v2->v == v); - BLI_smallhash_insert(visited, (uintptr_t)(kfe->v2), NULL); - found = find_chain_search(kcd, kfe->v1, fedges, visited, ans); - } - - BLI_smallhash_release(visited); - - if (found) - return ans; - else - return NULL; -} - -/* Find a chain in fedges from one instantiated vertex to another. - * Remove the edges in the chain from fedges and return a separate list of the chain. */ -static ListBase *find_chain(KnifeTool_OpData *kcd, ListBase *fedges) -{ - Ref *r, *ref; - KnifeEdge *kfe; - BMVert *v1, *v2; - ListBase *ans; - - ans = NULL; - - for (r = fedges->first; r; r = r->next) { - kfe = r->ref; - v1 = kfe->v1->v; - v2 = kfe->v2->v; - if (v1 && v2) { - ans = knife_empty_list(kcd); - knife_append_list(kcd, ans, kfe); - break; - } - if (v1) - ans = find_chain_from_vertex(kcd, kfe, v1, fedges); - else if (v2) - ans = find_chain_from_vertex(kcd, kfe, v2, fedges); - if (ans) - break; - } - if (ans) { - BLI_assert(!BLI_listbase_is_empty(ans)); - for (r = ans->first; r; r = r->next) { - ref = find_ref(fedges, r->ref); - BLI_assert(ref != NULL); - BLI_remlink(fedges, ref); - } - } - return ans; -} - -/* The hole so far goes from kfvfirst to kfv (some may be reversed). - * If possible, complete the hole back to kfvfirst and return 1, else return 0. - * The visited hash says which KnifeVert's have already been tried, not including kfv or kfvfirst. */ -static bool find_hole_search(KnifeTool_OpData *kcd, KnifeVert *kfvfirst, KnifeVert *kfv, ListBase *fedges, - SmallHash *visited, ListBase *hole) -{ - Ref *r; - KnifeEdge *kfe, *kfelast; - KnifeVert *kfv_other; - - if (kfv == kfvfirst) - return true; - - BLI_smallhash_insert(visited, (uintptr_t)kfv, NULL); - kfelast = ((Ref *)hole->last)->ref; - for (r = fedges->first; r; r = r->next) { - kfe = r->ref; - if (kfe == kfelast) - continue; - if (kfe->v1->v || kfe->v2->v) - continue; - kfv_other = NULL; - if (kfe->v1 == kfv) - kfv_other = kfe->v2; - else if (kfe->v2 == kfv) - kfv_other = kfe->v1; - if (kfv_other && !BLI_smallhash_haskey(visited, (uintptr_t)kfv_other)) { - knife_append_list(kcd, hole, kfe); - if (find_hole_search(kcd, kfvfirst, kfv_other, fedges, visited, hole)) - return true; - BLI_remlink(hole, hole->last); - } - } - return false; -} - -/* Find a hole (simple cycle with no instantiated vertices). - * Remove the edges in the cycle from fedges and return a separate list of the cycle */ -static ListBase *find_hole(KnifeTool_OpData *kcd, ListBase *fedges) -{ - ListBase *ans; - Ref *r, *ref; - KnifeEdge *kfe; - SmallHash visited_, *visited = &visited_; - bool found; - - ans = NULL; - found = false; - - for (r = fedges->first; r && !found; r = r->next) { - kfe = r->ref; - if (kfe->v1->v || kfe->v2->v || kfe->v1 == kfe->v2) - continue; - - BLI_smallhash_init(visited); - ans = knife_empty_list(kcd); - knife_append_list(kcd, ans, kfe); - - found = find_hole_search(kcd, kfe->v1, kfe->v2, fedges, visited, ans); - - BLI_smallhash_release(visited); - } - - if (found) { - for (r = ans->first; r; r = r->next) { - kfe = r->ref; - ref = find_ref(fedges, r->ref); - if (ref) - BLI_remlink(fedges, ref); - } - return ans; - } - else { - return NULL; - } -} - -/* Try to find "nice" diagonals - short, and far apart from each other. - * If found, return true and make a 'main chain' going across f which uses - * the two diagonals and one part of the hole, and a 'side chain' that - * completes the hole. */ -static bool find_hole_chains(KnifeTool_OpData *kcd, ListBase *hole, BMFace *f, ListBase **mainchain, - ListBase **sidechain) -{ - float (*fco)[2], (*hco)[2]; - BMVert **fv; - KnifeVert **hv; - KnifeEdge **he; - Ref *r; - KnifeVert *kfv, *kfvother; - KnifeEdge *kfe; - ListBase *chain; - BMVert *v; - BMIter iter; - int nh, nf, i, j, k, m, ax, ay, sep = 0 /* Quite warnings */, bestsep; - int besti[2], bestj[2]; - float dist_sq, dist_best_sq; - - nh = BLI_listbase_count(hole); - nf = f->len; - if (nh < 2 || nf < 3) - return false; - - /* Gather 2d projections of hole and face vertex coordinates. - * Use best-axis projection - not completely accurate, maybe revisit */ - axis_dominant_v3(&ax, &ay, f->no); - hco = BLI_memarena_alloc(kcd->arena, nh * sizeof(float[2])); - fco = BLI_memarena_alloc(kcd->arena, nf * sizeof(float[2])); - hv = BLI_memarena_alloc(kcd->arena, nh * sizeof(KnifeVert *)); - fv = BLI_memarena_alloc(kcd->arena, nf * sizeof(BMVert *)); - he = BLI_memarena_alloc(kcd->arena, nh * sizeof(KnifeEdge *)); - - i = 0; - kfv = NULL; - kfvother = NULL; - for (r = hole->first; r; r = r->next) { - kfe = r->ref; - he[i] = kfe; - if (kfvother == NULL) { - kfv = kfe->v1; - } - else { - kfv = kfvother; - BLI_assert(kfv == kfe->v1 || kfv == kfe->v2); - } - hco[i][0] = kfv->co[ax]; - hco[i][1] = kfv->co[ay]; - hv[i] = kfv; - kfvother = (kfe->v1 == kfv) ? kfe->v2 : kfe->v1; - i++; - } - - j = 0; - BM_ITER_ELEM (v, &iter, f, BM_VERTS_OF_FACE) { - fco[j][0] = v->co[ax]; - fco[j][1] = v->co[ay]; - fv[j] = v; - j++; - } - - /* For first diagonal (m == 0), want shortest length. - * For second diagonal (m == 1), want max separation of index of hole - * vertex from the hole vertex used in the first diagonal, and from there - * want the one with shortest length not to the same vertex as the first diagonal. */ - for (m = 0; m < 2; m++) { - besti[m] = -1; - bestj[m] = -1; - dist_best_sq = FLT_MAX; - bestsep = 0; - for (i = 0; i < nh; i++) { - if (m == 1) { - if (i == besti[0]) - continue; - sep = (i + nh - besti[0]) % nh; - sep = MIN2(sep, nh - sep); - if (sep < bestsep) - continue; - dist_best_sq = FLT_MAX; - } - for (j = 0; j < nf; j++) { - bool ok; - - if (m == 1 && j == bestj[0]) - continue; - dist_sq = len_squared_v2v2(hco[i], fco[j]); - if (dist_sq > dist_best_sq) - continue; - - ok = true; - for (k = 0; k < nh && ok; k++) { - if (k == i || (k + 1) % nh == i) - continue; - if (isect_seg_seg_v2(hco[i], fco[j], hco[k], hco[(k + 1) % nh])) - ok = false; - } - if (!ok) - continue; - for (k = 0; k < nf && ok; k++) { - if (k == j || (k + 1) % nf == j) - continue; - if (isect_seg_seg_v2(hco[i], fco[j], fco[k], fco[(k + 1) % nf])) - ok = false; - } - if (ok) { - besti[m] = i; - bestj[m] = j; - if (m == 1) - bestsep = sep; - dist_best_sq = dist_sq; - } - } - } - } - - if (besti[0] != -1 && besti[1] != -1) { - BLI_assert(besti[0] != besti[1] && bestj[0] != bestj[1]); - kfe = new_knife_edge(kcd); - kfe->v1 = get_bm_knife_vert(kcd, fv[bestj[0]]); - kfe->v2 = hv[besti[0]]; - chain = knife_empty_list(kcd); - knife_append_list(kcd, chain, kfe); - for (i = besti[0]; i != besti[1]; i = (i + 1) % nh) { - knife_append_list(kcd, chain, he[i]); - } - kfe = new_knife_edge(kcd); - kfe->v1 = hv[besti[1]]; - kfe->v2 = get_bm_knife_vert(kcd, fv[bestj[1]]); - knife_append_list(kcd, chain, kfe); - *mainchain = chain; - - chain = knife_empty_list(kcd); - for (i = besti[1]; i != besti[0]; i = (i + 1) % nh) { - knife_append_list(kcd, chain, he[i]); - } - *sidechain = chain; - - return true; - } - else { - return false; - } -} - static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f) { bool v1_inside, v2_inside; @@ -2572,201 +2256,110 @@ static bool knife_verts_edge_in_face(KnifeVert *v1, KnifeVert *v2, BMFace *f) return false; } -static bool knife_edge_in_face(KnifeEdge *kfe, BMFace *f) -{ - return knife_verts_edge_in_face(kfe->v1, kfe->v2, f); -} - -/* Split face f with KnifeEdges on chain. f remains as one side, the face formed is put in *newface. - * The new face will be on the left side of the chain as viewed from the normal-out side of f. */ -static void knife_make_chain_cut(KnifeTool_OpData *kcd, BMFace *f, ListBase *chain, BMFace **r_f_new) +static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfedges) { BMesh *bm = kcd->em->bm; - KnifeEdge *kfe, *kfelast; - BMVert *v1, *v2; - BMLoop *l_v1, *l_v2; - BMFace *f_new; + KnifeEdge *kfe; Ref *ref; - KnifeVert *kfv, *kfvprev; - BMLoop *l_new, *l_iter; + int edge_array_len = BLI_listbase_count(kfedges); + bool ok; int i; - int nco = BLI_listbase_count(chain) - 1; - float (*cos)[3] = BLI_array_alloca(cos, nco); - KnifeVert **kverts = BLI_array_alloca(kverts, nco); - - kfe = ((Ref *)chain->first)->ref; - v1 = kfe->v1->v ? kfe->v1->v : kfe->v2->v; - kfelast = ((Ref *)chain->last)->ref; - v2 = kfelast->v2->v ? kfelast->v2->v : kfelast->v1->v; - BLI_assert(v1 != NULL && v2 != NULL); - kfvprev = kfe->v1->v == v1 ? kfe->v1 : kfe->v2; - for (ref = chain->first, i = 0; i < nco && ref != chain->last; ref = ref->next, i++) { + + BMFace **face_arr; + int face_arr_len; + + BMEdge **edge_array = BLI_array_alloca(edge_array, edge_array_len); + + /* point to knife edges we've created edges in, edge_array aligned */ + KnifeEdge **kfe_array = BLI_array_alloca(kfe_array, edge_array_len); + + i = 0; + for (ref = kfedges->first; ref; ref = ref->next) { + bool is_new_edge = false; kfe = ref->ref; - BLI_assert(kfvprev == kfe->v1 || kfvprev == kfe->v2); - kfv = kfe->v1 == kfvprev ? kfe->v2 : kfe->v1; - copy_v3_v3(cos[i], kfv->co); - kverts[i] = kfv; - kfvprev = kfv; - } - BLI_assert(i == nco); - l_new = NULL; - if ((l_v1 = BM_face_vert_share_loop(f, v1)) && - (l_v2 = BM_face_vert_share_loop(f, v2))) - { - if (nco == 0) { - /* Want to prevent creating two-sided polygons */ - if (v1 == v2 || BM_edge_exists(v1, v2)) { - f_new = NULL; + if (kfe->e == NULL) { + if (kfe->v1->v && kfe->v2->v) { + kfe->e = BM_edge_exists(kfe->v1->v, kfe->v2->v); } - else { - f_new = BM_face_split(bm, f, l_v1, l_v2, &l_new, NULL, true); + } + + if (kfe->e) { + if (BM_edge_in_face(kfe->e, f)) { + /* shouldn't happen, but in this case - just ignore */ + continue; } } else { - f_new = BM_face_split_n(bm, f, l_v1, l_v2, cos, nco, &l_new, NULL); - if (f_new) { - /* Now go through lnew chain matching up chain kv's and assign real v's to them */ - for (l_iter = l_new->next, i = 0; i < nco; l_iter = l_iter->next, i++) { - BLI_assert(equals_v3v3(cos[i], l_iter->v->co)); - if (kcd->select_result) { - BM_edge_select_set(bm, l_iter->e, true); - } - kverts[i]->v = l_iter->v; + if (kfe->v1->v == NULL) { + kfe->v1->v = BM_vert_create(bm, kfe->v1->co, NULL, 0); + } + if (kfe->v2->v == NULL) { + kfe->v2->v = BM_vert_create(bm, kfe->v2->co, NULL, 0); + } + BLI_assert(kfe->e == NULL); + kfe->e = BM_edge_create(bm, kfe->v1->v, kfe->v2->v, NULL, 0); + if (kfe->e) { + if (kcd->select_result || BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BM_edge_select_set(bm, kfe->e, true); } + is_new_edge = true; } } - } - else { - f_new = NULL; - } - /* the select chain above doesnt account for the first loop */ - if (kcd->select_result) { - if (l_new) { - BM_edge_select_set(bm, l_new->e, true); - } - } - else if (f_new) { - BM_elem_select_copy(bm, bm, f_new, f); + BLI_assert(kfe->e); + + kfe_array[i] = is_new_edge ? kfe : 0; + edge_array[i] = kfe->e; + i += 1; } - *r_f_new = f_new; -} + if (i) { + const int edge_array_len_orig = i; + edge_array_len = i; -static void knife_make_face_cuts(KnifeTool_OpData *kcd, BMFace *f, ListBase *kfedges) -{ - BMesh *bm = kcd->em->bm; - KnifeEdge *kfe; - BMFace *fnew, *fnew2, *fhole; - ListBase *chain, *hole, *sidechain; - Ref *ref, *refnext; - int count, oldcount; - - oldcount = BLI_listbase_count(kfedges); - while ((chain = find_chain(kcd, kfedges)) != NULL) { - ListBase fnew_kfedges; - knife_make_chain_cut(kcd, f, chain, &fnew); - if (!fnew) { - return; - } - - /* Move kfedges to fnew_kfedges if they are now in fnew. - * The chain edges were removed already */ - BLI_listbase_clear(&fnew_kfedges); - for (ref = kfedges->first; ref; ref = refnext) { - kfe = ref->ref; - refnext = ref->next; - if (knife_edge_in_face(kfe, fnew)) { - BLI_remlink(kfedges, ref); - kfe->basef = fnew; - BLI_addtail(&fnew_kfedges, ref); - } - else if (!knife_edge_in_face(kfe, f)) { - /* Concave ngon's - this edge might not be in either faces, T41730 */ - BLI_remlink(kfedges, ref); +#ifdef USE_NET_ISLAND_CONNECT + unsigned int edge_array_holes_len; + BMEdge **edge_array_holes; + if (BM_face_split_edgenet_connect_islands( + bm, f, + edge_array, edge_array_len, + kcd->arena_edgenet, + &edge_array_holes, &edge_array_holes_len)) + { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + for (i = edge_array_len; i < edge_array_holes_len; i++) { + BM_edge_select_set(bm, edge_array_holes[i], true); + } } - } - if (fnew_kfedges.first) - knife_make_face_cuts(kcd, fnew, &fnew_kfedges); - /* find_chain should always remove edges if it returns true, - * but guard against infinite loop anyway */ - count = BLI_listbase_count(kfedges); - if (count >= oldcount) { - BLI_assert(!"knife find_chain infinite loop"); - return; + edge_array_len = edge_array_holes_len; + edge_array = edge_array_holes; /* owned by the arena */ } - oldcount = count; - } +#endif - while ((hole = find_hole(kcd, kfedges)) != NULL) { - if (find_hole_chains(kcd, hole, f, &chain, &sidechain)) { - ListBase fnew_kfedges, fnew2_kfedges; + ok = BM_face_split_edgenet( + bm, f, edge_array, edge_array_len, + &face_arr, &face_arr_len); - /* chain goes across f and sidechain comes back - * from the second last vertex to the second vertex. - */ - knife_make_chain_cut(kcd, f, chain, &fnew); - if (!fnew) { - BLI_assert(!"knife failed hole cut"); - return; - } - kfe = ((Ref *)sidechain->first)->ref; - if (knife_edge_in_face(kfe, f)) { - knife_make_chain_cut(kcd, f, sidechain, &fnew2); - if (fnew2 == NULL) { - return; - } - fhole = f; - } - else if (knife_edge_in_face(kfe, fnew)) { - knife_make_chain_cut(kcd, fnew, sidechain, &fnew2); - if (fnew2 == NULL) { - return; - } - fhole = fnew2; - } - else { - /* shouldn't happen except in funny edge cases */ - return; - } - BM_face_kill(bm, fhole); - /* Move kfedges to either fnew or fnew2 if appropriate. - * The hole edges were removed already */ - BLI_listbase_clear(&fnew_kfedges); - BLI_listbase_clear(&fnew2_kfedges); - for (ref = kfedges->first; ref; ref = refnext) { - kfe = ref->ref; - refnext = ref->next; - if (knife_edge_in_face(kfe, fnew)) { - BLI_remlink(kfedges, ref); - kfe->basef = fnew; - BLI_addtail(&fnew_kfedges, ref); - } - else if (knife_edge_in_face(kfe, fnew2)) { - BLI_remlink(kfedges, ref); - kfe->basef = fnew2; - BLI_addtail(&fnew2_kfedges, ref); + if (ok) { + MEM_freeN(face_arr); + } + + /* remove dangling edges, not essential - but nice for users */ + for (i = 0; i < edge_array_len_orig; i++) { + if (kfe_array[i]) { + if (BM_edge_is_wire(kfe_array[i]->e)) { + BM_edge_kill(bm, kfe_array[i]->e); + kfe_array[i]->e = NULL; } } - /* We'll skip knife edges that are in the newly formed hole. - * (Maybe we shouldn't have made a hole in the first place?) */ - if (fnew != fhole && fnew_kfedges.first) - knife_make_face_cuts(kcd, fnew, &fnew_kfedges); - if (fnew2 != fhole && fnew2_kfedges.first) - knife_make_face_cuts(kcd, fnew2, &fnew2_kfedges); - if (f == fhole) - break; - /* find_hole should always remove edges if it returns true, - * but guard against infinite loop anyway */ - count = BLI_listbase_count(kfedges); - if (count >= oldcount) { - BLI_assert(!"knife find_hole infinite loop"); - return; - } - oldcount = count; } + + +#ifdef USE_NET_ISLAND_CONNECT + BLI_memarena_clear(kcd->arena_edgenet); +#endif } } @@ -2915,6 +2508,9 @@ static void knifetool_exit_ex(bContext *C, KnifeTool_OpData *kcd) BLI_ghash_free(kcd->facetrimap, NULL, NULL); BLI_memarena_free(kcd->arena); +#ifdef USE_NET_ISLAND_CONNECT + BLI_memarena_free(kcd->arena_edgenet); +#endif /* tag for redraw */ ED_region_tag_redraw(kcd->ar); @@ -3000,6 +2596,9 @@ static void knifetool_init(bContext *C, KnifeTool_OpData *kcd, knifetool_init_bmbvh(kcd); kcd->arena = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), "knife"); +#ifdef USE_NET_ISLAND_CONNECT + kcd->arena_edgenet = BLI_memarena_new(MEM_SIZE_OPTIMAL(1 << 15), __func__); +#endif kcd->vthresh = KMAXDIST - 1; kcd->ethresh = KMAXDIST; |