Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/mesh/editmesh_select.c
| Show First 20 Lines • Show All 202 Lines • ▼ Show 20 Lines | |||||
| struct EDBMSplitEdgeData { | struct EDBMSplitEdgeData { | ||||
| BMesh *bm; | BMesh *bm; | ||||
| BMEdge *r_edge; | BMEdge *r_edge; | ||||
| float r_lambda; | float r_lambda; | ||||
| }; | }; | ||||
| static bool edbm_automerge_and_split_check_best_face_cb(BMFace *UNUSED(f), | enum { | ||||
| IS_UNDEFINED = -1, | |||||
| IS_NOT_COPLANAR = 0, | |||||
| IS_COPLANAR = 1, | |||||
| }; | |||||
| static int edbm_edgenet_is_coplanar(BMEdge **edgenet, int edgenet_len, float r_plane_no[3]) | |||||
| { | |||||
| int ret = IS_UNDEFINED; | |||||
| if (edgenet_len >= 2) { | |||||
| float vec[2][3]; | |||||
| sub_v3_v3v3(vec[0], edgenet[1]->v2->co, edgenet[1]->v1->co); | |||||
| sub_v3_v3v3(vec[1], edgenet[0]->v2->co, edgenet[0]->v1->co); | |||||
| cross_v3_v3v3(r_plane_no, vec[0], vec[1]); | |||||
| float len_sq_a = len_squared_v3(r_plane_no); | |||||
| if (len_sq_a) { | |||||
| ret = IS_COPLANAR; | |||||
| } | |||||
| for (int i = 2; ret && i < edgenet_len; i++) { | |||||
| float no[3], dot; | |||||
| sub_v3_v3v3(vec[1], edgenet[i]->v2->co, edgenet[i]->v1->co); | |||||
| cross_v3_v3v3(no, vec[0], vec[1]); | |||||
| if (len_sq_a) { | |||||
| dot = dot_v3v3(no, r_plane_no); | |||||
| if (dot) { | |||||
| mul_v3_fl(no, len_sq_a / dot); | |||||
| if (compare_v3v3(no, r_plane_no, 256 * FLT_EPSILON)) { | |||||
| ret = IS_COPLANAR; | |||||
| } | |||||
| else { | |||||
| ret = IS_NOT_COPLANAR; | |||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| /*Avoid testing zero-length normal. */ | |||||
| float len_sq_b = len_squared_v3(no); | |||||
| if (len_sq_a < len_sq_b) { | |||||
| copy_v3_v3(r_plane_no, no); | |||||
| len_sq_a = len_sq_b; | |||||
| } | |||||
| } | |||||
| copy_v3_v3(vec[0], vec[1]); | |||||
| } | |||||
| if (ret == IS_COPLANAR) { | |||||
| /* Normalize */ | |||||
| BLI_assert(len_sq_a); | |||||
| mul_v3_fl(r_plane_no, 1.0f / sqrtf(len_sq_a)); | |||||
| } | |||||
| } | |||||
| return ret; | |||||
| } | |||||
campbellbarton: I think co-planar checks like this are too error prone - at different scales we would want… | |||||
| static bool edbm_vert_pair_share_face_with_normal_cb(BMFace *f, | |||||
| BMLoop *l_a, | |||||
| BMLoop *l_b, | |||||
| void *userdata) | |||||
| { | |||||
| float *no = userdata; | |||||
| float dot = dot_v3v3(no, f->no); | |||||
| return fabs(dot) > 0.99; | |||||
| } | |||||
| /* find the best splittable face between the two vertices. */ | |||||
| static bool edbm_vert_pair_share_splittable_face_cb(BMFace *f, | |||||
| BMLoop *l_a, | BMLoop *l_a, | ||||
| BMLoop *l_b, | BMLoop *l_b, | ||||
| void *userdata) | void *userdata) | ||||
| { | { | ||||
| float(*data)[3] = userdata; | float(*data)[3] = userdata; | ||||
| float *v_a_co = data[0]; | float *v_a_co = data[0]; | ||||
| float *v_a_b_dir = data[1]; | float *v_a_b_dir = data[1]; | ||||
| float lambda; | float lambda; | ||||
| if (isect_ray_seg_v3(v_a_co, v_a_b_dir, l_a->prev->v->co, l_a->next->v->co, &lambda)) { | if (isect_ray_seg_v3(v_a_co, v_a_b_dir, l_a->prev->v->co, l_a->next->v->co, &lambda)) { | ||||
| if (IN_RANGE(lambda, 0.0f, 1.0f)) { | if (IN_RANGE(lambda, 0.0f, 1.0f)) { | ||||
| if (isect_ray_seg_v3(v_a_co, v_a_b_dir, l_b->prev->v->co, l_b->next->v->co, &lambda)) { | if (isect_ray_seg_v3(v_a_co, v_a_b_dir, l_b->prev->v->co, l_b->next->v->co, &lambda)) { | ||||
| return IN_RANGE(lambda, 0.0f, 1.0f); | return IN_RANGE(lambda, 0.0f, 1.0f); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static bool edbm_automerge_check_and_split_faces(BMesh *bm, BMVert *v_src, BMVert *v_dst) | static void edbm_automerge_weld_linked_wire_edges_into_linked_faces(BMesh *bm, BMVert *v) | ||||
| { | { | ||||
| BMIter iter; | BMIter iter; | ||||
Done Inline ActionsStack is normally used for something add and remove (push/pop), this is just an array of edges. Should call r_edgenet r_edgenet_len since these are return args. campbellbarton: Stack is normally used for something add and remove (push/pop), this is just an array of edges. | |||||
| BMEdge *e_iter; | BMEdge *e; | ||||
| BM_ITER_ELEM (e_iter, &iter, v_src, BM_EDGES_OF_VERT) { | BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) { | ||||
| BMVert *vert_other = BM_edge_other_vert(e_iter, v_src); | int edgenet_len = 0; | ||||
| if (vert_other != v_dst) { | BMVert *v_other = v; | ||||
| float data[2][3]; | BMEdge *edgenet[200]; | ||||
campbellbartonUnsubmitted Done Inline ActionsShould dynamically allocate. campbellbarton: Should dynamically allocate. | |||||
| copy_v3_v3(data[0], vert_other->co); | while (BM_edge_is_wire(e)) { | ||||
| sub_v3_v3v3(data[1], v_dst->co, data[0]); | if (edgenet_len == ARRAY_SIZE(edgenet) - 1) { | ||||
| /* Size limit reached, do nothing with this edgenet. */ | |||||
| edgenet_len = 0; | |||||
Done Inline ActionsDealing with pointers-to-pointers in the code reads a little awkwardly. This could use local edgenet and edgenet_len variables, then assign the return arguments at the end. campbellbarton: Dealing with pointers-to-pointers in the code reads a little awkwardly.
This could use local… | |||||
| break; | |||||
| } | |||||
| edgenet[edgenet_len++] = e; | |||||
| v_other = BM_edge_other_vert(e, v_other); | |||||
| BMEdge *e_next = BM_DISK_EDGE_NEXT(e, v_other); | |||||
| if (e_next == e) { | |||||
| /* Vert is wire_endpoint */ | |||||
| edgenet_len = 0; | |||||
| break; | |||||
| } | |||||
| e = e_next; | |||||
| } | |||||
| BMLoop *l_a, *l_b; | if (edgenet_len) { | ||||
| BMFace *f = BM_vert_pair_shared_face_cb( | float data[2][3]; | ||||
| vert_other, v_dst, false, edbm_automerge_and_split_check_best_face_cb, data, &l_a, &l_b); | bool (*callback)(BMFace *, BMLoop *, BMLoop *, void *userdata); | ||||
| int status = edbm_edgenet_is_coplanar(edgenet, edgenet_len, data[0]); | |||||
| if (status == IS_NOT_COPLANAR) { | |||||
| /* Do not split the face. */ | |||||
| continue; | |||||
| } | |||||
| else if (status == IS_UNDEFINED) { | |||||
| copy_v3_v3(data[0], v_other->co); | |||||
| sub_v3_v3v3(data[1], v->co, data[0]); | |||||
| callback = edbm_vert_pair_share_splittable_face_cb; | |||||
| } | |||||
| else { | |||||
| callback = edbm_vert_pair_share_face_with_normal_cb; | |||||
| } | |||||
| BMLoop *dummy; | |||||
| BMFace *best_face = BM_vert_pair_shared_face_cb( | |||||
| v_other, v, true, callback, data, &dummy, &dummy); | |||||
| if (f) { | if (best_face) { | ||||
| BM_face_split(bm, f, l_a, l_b, NULL, NULL, true); | BM_face_split_edgenet(bm, best_face, edgenet, edgenet_len, NULL, NULL); | ||||
| return true; | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return false; | |||||
| } | } | ||||
| static void ebbm_automerge_and_split_find_duplicate_cb(void *userdata, | static void ebbm_automerge_and_split_find_duplicate_cb(void *userdata, | ||||
| int index, | int index, | ||||
| const float co[3], | const float co[3], | ||||
| BVHTreeNearest *nearest) | BVHTreeNearest *nearest) | ||||
| { | { | ||||
| struct EDBMSplitEdgeData *data = userdata; | struct EDBMSplitEdgeData *data = userdata; | ||||
| Show All 24 Lines | static int edbm_automerge_and_split_sort_cmp_by_keys_cb(const void *index1_v, | ||||
| if (cuts[*index1].lambda > cuts[*index2].lambda) { | if (cuts[*index1].lambda > cuts[*index2].lambda) { | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| else { | else { | ||||
| return -1; | return -1; | ||||
| } | } | ||||
| } | } | ||||
| void EDBM_automerge_and_split( | void EDBM_automerge_and_split(Scene *scene, | ||||
| Scene *scene, Object *obedit, bool split_edges, bool update, const char hflag) | Object *obedit, | ||||
| bool split_edges, | |||||
| bool split_faces, | |||||
| bool update, | |||||
| const char hflag) | |||||
| { | { | ||||
| bool ok = false; | bool ok = false; | ||||
| BMEditMesh *em = BKE_editmesh_from_object(obedit); | BMEditMesh *em = BKE_editmesh_from_object(obedit); | ||||
| BMesh *bm = em->bm; | BMesh *bm = em->bm; | ||||
| float dist = scene->toolsettings->doublimit; | float dist = scene->toolsettings->doublimit; | ||||
| BMOperator findop, weldop; | BMOperator findop, weldop; | ||||
| BMOpSlot *slot_targetmap; | BMOpSlot *slot_targetmap; | ||||
| Show All 33 Lines | GHASH_ITER (gh_iter, ghash_targetmap) { | ||||
| BMVert *v_dst = BLI_ghashIterator_getValue(&gh_iter); | BMVert *v_dst = BLI_ghashIterator_getValue(&gh_iter); | ||||
| if (!BM_elem_flag_test(v, BM_ELEM_TAG)) { | if (!BM_elem_flag_test(v, BM_ELEM_TAG)) { | ||||
| /* Should this happen? */ | /* Should this happen? */ | ||||
| SWAP(BMVert *, v, v_dst); | SWAP(BMVert *, v, v_dst); | ||||
| } | } | ||||
| BLI_assert(BM_elem_flag_test(v, BM_ELEM_TAG)); | BLI_assert(BM_elem_flag_test(v, BM_ELEM_TAG)); | ||||
| BM_elem_flag_disable(v, BM_ELEM_TAG); | BM_elem_flag_disable(v, BM_ELEM_TAG); | ||||
| edbm_automerge_check_and_split_faces(bm, v, v_dst); | |||||
| ok = true; | ok = true; | ||||
| verts_len--; | verts_len--; | ||||
| } | } | ||||
| int totedge = bm->totedge; | int totedge = bm->totedge; | ||||
| if (totedge == 0 || verts_len == 0) { | if (totedge == 0 || verts_len == 0) { | ||||
| split_edges = false; | split_edges = false; | ||||
| } | } | ||||
| Show All 9 Lines | BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { | ||||
| edges_len++; | edges_len++; | ||||
| } | } | ||||
| else { | else { | ||||
| BM_elem_flag_disable(e, BM_ELEM_TAG); | BM_elem_flag_disable(e, BM_ELEM_TAG); | ||||
| } | } | ||||
| } | } | ||||
| if (edges_len) { | if (edges_len) { | ||||
| /* Use `e->head.index` to count intersections. */ | |||||
| bm->elem_index_dirty &= ~BM_EDGE; | |||||
| /* Create a BVHTree of edges with `dist` as epsilon. */ | /* Create a BVHTree of edges with `dist` as epsilon. */ | ||||
| BVHTree *tree_edges = BLI_bvhtree_new(edges_len, dist, 2, 6); | BVHTree *tree_edges = BLI_bvhtree_new(edges_len, dist, 2, 6); | ||||
| int i; | int i; | ||||
| BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { | BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { | ||||
| if (BM_elem_flag_test(e, BM_ELEM_TAG)) { | if (BM_elem_flag_test(e, BM_ELEM_TAG)) { | ||||
| float co[2][3]; | float co[2][3]; | ||||
| copy_v3_v3(co[0], e->v1->co); | copy_v3_v3(co[0], e->v1->co); | ||||
| copy_v3_v3(co[1], e->v2->co); | copy_v3_v3(co[1], e->v2->co); | ||||
| BLI_bvhtree_insert(tree_edges, i, co[0], 2); | BLI_bvhtree_insert(tree_edges, i, co[0], 2); | ||||
| /* Use `e->head.index` to count intersections. */ | |||||
| e->head.index = 0; | e->head.index = 0; | ||||
| } | } | ||||
| } | } | ||||
| BLI_bvhtree_balance(tree_edges); | BLI_bvhtree_balance(tree_edges); | ||||
| struct EDBMSplitEdge *cuts_iter, *cuts; | struct EDBMSplitEdge *cuts_iter, *cuts; | ||||
| /* Store all intersections in this array. */ | /* Store all intersections in this array. */ | ||||
| ▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | if (edges_len) { | ||||
| e_map->as_int[++e->head.index] = i; | e_map->as_int[++e->head.index] = i; | ||||
| } | } | ||||
| } | } | ||||
| /* Split Edges and Faces. */ | /* Split Edges and Faces. */ | ||||
| for (i = 0; i < map_len; | for (i = 0; i < map_len; | ||||
| e_map_iter = (void *)&e_map->as_int[i], i += 1 + e_map_iter->cuts_len) { | e_map_iter = (void *)&e_map->as_int[i], i += 1 + e_map_iter->cuts_len) { | ||||
| /* sort by lambda! */ | /* sort by lambda. */ | ||||
| BLI_qsort_r(e_map_iter->cuts_index, | BLI_qsort_r(e_map_iter->cuts_index, | ||||
| e_map_iter->cuts_len, | e_map_iter->cuts_len, | ||||
| sizeof(*(e_map->cuts_index)), | sizeof(*(e_map->cuts_index)), | ||||
| edbm_automerge_and_split_sort_cmp_by_keys_cb, | edbm_automerge_and_split_sort_cmp_by_keys_cb, | ||||
| cuts); | cuts); | ||||
| float lambda, lambda_prev = 0.0f; | float lambda, lambda_prev = 0.0f; | ||||
| for (int j = 0; j < e_map_iter->cuts_len; j++) { | for (int j = 0; j < e_map_iter->cuts_len; j++) { | ||||
| cuts_iter = &cuts[e_map_iter->cuts_index[j]]; | cuts_iter = &cuts[e_map_iter->cuts_index[j]]; | ||||
| lambda = (cuts_iter->lambda - lambda_prev) / (1.0f - lambda_prev); | lambda = (cuts_iter->lambda - lambda_prev) / (1.0f - lambda_prev); | ||||
| lambda_prev = cuts_iter->lambda; | lambda_prev = cuts_iter->lambda; | ||||
| v = cuts_iter->v; | v = cuts_iter->v; | ||||
| e = cuts_iter->e; | e = cuts_iter->e; | ||||
| BMVert *v_new = BM_edge_split(bm, e, e->v1, NULL, lambda); | BMVert *v_new = BM_edge_split(bm, e, e->v1, NULL, lambda); | ||||
| edbm_automerge_check_and_split_faces(bm, v, v_new); | |||||
| BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_new, v); | BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_new, v); | ||||
| } | } | ||||
| } | } | ||||
| ok = true; | ok = true; | ||||
| MEM_freeN(e_map); | MEM_freeN(e_map); | ||||
| } | } | ||||
| MEM_freeN(cuts); | MEM_freeN(cuts); | ||||
| } | } | ||||
| } | } | ||||
| BMO_op_exec(bm, &weldop); | BMO_op_exec(bm, &weldop); | ||||
| if (split_faces) { | |||||
| GHASH_ITER (gh_iter, ghash_targetmap) { | |||||
| v = BLI_ghashIterator_getValue(&gh_iter); | |||||
| BLI_assert(BM_elem_flag_test(v, hflag) || hflag == BM_ELEM_TAG); | |||||
| edbm_automerge_weld_linked_wire_edges_into_linked_faces(bm, v); | |||||
| } | |||||
| } | |||||
| BMO_op_finish(bm, &findop); | BMO_op_finish(bm, &findop); | ||||
| BMO_op_finish(bm, &weldop); | BMO_op_finish(bm, &weldop); | ||||
| if (LIKELY(ok) && update) { | if (LIKELY(ok) && update) { | ||||
| EDBM_update_generic(em, true, true); | EDBM_update_generic(em, true, true); | ||||
| } | } | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 4,615 Lines • Show Last 20 Lines | |||||
I think co-planar checks like this are too error prone - at different scales we would want different epsilons for subd modeling you may want non-planar faces.
Suggest always splitting, if the users connects a wire edge chain and doesn't want to split the face - they can disable this option.