Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/mesh/editmesh_select.c
| Show First 20 Lines • Show All 195 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| struct EDBMSplitEdge { | struct EDBMSplitEdge { | ||||
| BMVert *v; | BMVert *v; | ||||
| BMEdge *e; | BMEdge *e; | ||||
| float lambda; | float lambda; | ||||
| }; | }; | ||||
| struct EDBMSplitBestFaceData { | |||||
| BMEdge **edgenet; | |||||
| int edgenet_len; | |||||
| float average; | |||||
| BMFace *r_best_face; | |||||
| }; | |||||
| 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), | static bool edbm_vert_pair_share_best_splittable_face_cb(BMFace *f, | ||||
| BMLoop *l_a, | |||||
| BMLoop *l_b, | |||||
| void *userdata) | |||||
| { | |||||
| struct EDBMSplitBestFaceData *data = userdata; | |||||
| float no[3], min = FLT_MAX, max = -FLT_MAX; | |||||
| copy_v3_v3(no, f->no); | |||||
| BMVert *verts[2] = {NULL}; | |||||
| BMEdge **e_iter = &data->edgenet[0]; | |||||
| for (int i = data->edgenet_len; i--; e_iter++) { | |||||
| BMIter iter; | |||||
| BMVert *v; | |||||
| BM_ITER_ELEM (v, &iter, *e_iter, BM_VERTS_OF_EDGE) { | |||||
| if (!ELEM(v, verts[0], verts[1])) { | |||||
| float dot = dot_v3v3(v->co, no); | |||||
| if (dot < min) { | |||||
| min = dot; | |||||
| } | |||||
| if (dot > max) { | |||||
| max = dot; | |||||
| } | |||||
| } | |||||
| } | |||||
| verts[0] = (*e_iter)->v1; | |||||
| verts[1] = (*e_iter)->v2; | |||||
| } | |||||
| float average = max - min; | |||||
| if (average < data->average) { | |||||
| data->average = average; | |||||
| data->r_best_face = f; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /* 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) | ||||
| { | { | ||||
campbellbarton: I think co-planar checks like this are too error prone - at different scales we would want… | |||||
| 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, | |||||
| BMEdge **edgenet_stack[], | |||||
campbellbartonUnsubmitted 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. | |||||
| int *stack_len) | |||||
| { | { | ||||
| BMIter iter; | BMIter iter; | ||||
| 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]; | while (BM_edge_is_wire(e)) { | ||||
| copy_v3_v3(data[0], vert_other->co); | if (*stack_len == edgenet_len) { | ||||
| sub_v3_v3v3(data[1], v_dst->co, data[0]); | *stack_len = (*stack_len + 1) * 2; | ||||
| *edgenet_stack = MEM_reallocN(*edgenet_stack, (*stack_len) * sizeof(**edgenet_stack)); | |||||
| } | |||||
| (*edgenet_stack)[edgenet_len++] = e; | |||||
campbellbartonUnsubmitted 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… | |||||
| 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; | BMLoop *dummy; | ||||
| BMFace *f = BM_vert_pair_shared_face_cb( | BMFace *best_face; | ||||
| vert_other, v_dst, false, edbm_automerge_and_split_check_best_face_cb, data, &l_a, &l_b); | if (edgenet_len == 0) { | ||||
| /* Nothing to do. */ | |||||
| continue; | |||||
| } | |||||
| if (edgenet_len == 1) { | |||||
| float data[2][3]; | |||||
| copy_v3_v3(data[0], v_other->co); | |||||
| sub_v3_v3v3(data[1], v->co, data[0]); | |||||
| best_face = BM_vert_pair_shared_face_cb( | |||||
| v_other, v, true, edbm_vert_pair_share_splittable_face_cb, &data, &dummy, &dummy); | |||||
| } | |||||
Done Inline ActionsShould dynamically allocate. campbellbarton: Should dynamically allocate. | |||||
| else { | |||||
| struct EDBMSplitBestFaceData data = { | |||||
| .edgenet = *edgenet_stack, | |||||
| .edgenet_len = edgenet_len, | |||||
| .average = FLT_MAX, | |||||
| .r_best_face = NULL, | |||||
| }; | |||||
| BM_vert_pair_shared_face_cb( | |||||
| v_other, v, true, edbm_vert_pair_share_best_splittable_face_cb, &data, &dummy, &dummy); | |||||
| if (f) { | best_face = data.r_best_face; | ||||
| BM_face_split(bm, f, l_a, l_b, NULL, NULL, true); | |||||
| return true; | |||||
| } | } | ||||
| if (best_face) { | |||||
| BM_face_split_edgenet(bm, best_face, *edgenet_stack, edgenet_len, NULL, NULL); | |||||
| } | } | ||||
| } | } | ||||
| 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); | ||||
| BMEdge **edgenet_stack = NULL; | |||||
| int edgenet_stack_len = 0; | |||||
| 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, &edgenet_stack, &edgenet_stack_len); | |||||
| } | |||||
| } | |||||
| if (edgenet_stack) { | |||||
| MEM_freeN(edgenet_stack); | |||||
| } | |||||
| 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.