#include "helper.h" #include #include #include #include #include #include #include #include #include "MaxFlow.h" #define DENOMINATOR_EPS 1e-6 typedef TinyVector vec3d; void greedy_graph_coloring(const size_t num_vertices, const std::vector>& edges, std::vector>& colored_vertices) { std::vector result(num_vertices, -1); result[0] = 0; std::vector available(num_vertices, false); int max_color = 0; for (size_t u = 1; u < num_vertices; u++) { for (typename std::set::iterator iter = edges[u].begin(); iter != edges[u].end(); iter++) { if (result[*iter] != -1) available[result[*iter]] = true; } int cr; for (cr = 0; cr < (int)num_vertices; cr++) { if (available[cr] == false) break; } result[u] = cr; max_color = std::max(cr, max_color); for (typename std::set::iterator iter = edges[u].begin(); iter != edges[u].end(); iter++) { if (result[*iter] != -1) available[result[*iter]] = false; } } colored_vertices.resize(max_color + 1); for (size_t i = 0; i < num_vertices; i++) { colored_vertices[result[i]].push_back(i); } } void get_graph_component(const std::vector>& edges, std::vector>& components) { //edges id starting from 0 components.clear(); std::vector vert2color(edges.size(), -1); int cid = 0; int startid = -1; for (size_t i = 0; i < edges.size(); i++) { if (!edges[i].empty()) { startid = i; break; } } assert(startid != -1); while (startid != -1) { std::set onecluser; std::queue q; q.push(startid); vert2color[startid] = cid; while (!q.empty()) { size_t front = q.front(); q.pop(); onecluser.insert(front); for (auto v : edges[front]) { if (vert2color[v] == -1) { q.push(v); vert2color[v] = cid; } } } components.push_back(onecluser); cid++; startid = -1; for (size_t i = 0; i < edges.size(); i++) { if (!edges[i].empty() && vert2color[i] == -1) { startid = i; break; } } } } void compute_vert_mean_curvature(const std::vector>& pos, const std::vector>& faces, std::vector& curvature) { using namespace Eigen; using namespace std; MatrixXd V; MatrixXi F; curvature.clear(); curvature.resize(pos.size(), 0.0); //igl::readOFF(TUTORIAL_SHARED_PATH "/bumpy.off",V,F); //igl::readOFF("E:\\code\\PaperCode\\VolumeMeshProcessing\\x64\\Release\\AllModels\\fandisk\\fandisk.off", V, F); //remove dup verts std::vector o2n(pos.size(), (size_t)-1), n2o; size_t count = 0; for (size_t i = 0; i < faces.size(); i++) { for (size_t j = 0; j < 3; j++) { size_t id = faces[i][j]; if (o2n[id] == (size_t)-1) { o2n[id] = count; count = count + 1; n2o.push_back(id); } } } std::vector> vV(n2o.size(), std::vector(3)); std::vector> vF(faces.size(), std::vector(3)); for (size_t i = 0; i < n2o.size(); i++) { //V(3 *i, 1) = pos[i][0]; /*V(3 * i + 1) = pos[i][1]; V(3 * i + 2) = pos[i][2];*/ for (size_t j = 0; j < 3; j++) { vV[i][j] = pos[n2o[i]][j]; } } for (size_t i = 0; i < faces.size(); i++) { /*F(3 * i) = faces[i][0]; F(3 * i + 1) = faces[i][1]; F(3 * i + 2) = faces[i][2];*/ for (size_t j = 0; j < 3; j++) { vF[i][j] = o2n[faces[i][j]]; } } igl::list_to_matrix(vV, V); igl::list_to_matrix(vF, F); std::cout << "V size: " << V.size() << std::endl; std::cout << "F size: " << F.size() << std::endl; std::cout << "pos & faces size: " << pos.size() << " " << faces.size() << std::endl; SparseMatrix M, Minv; MatrixXd HN; SparseMatrix L; igl::cotmatrix(V, F, L); igl::massmatrix(V, F, igl::MASSMATRIX_TYPE_VORONOI, M); igl::invert_diag(M, Minv); HN = -Minv * (L * V); VectorXd H = HN.rowwise().norm(); //up to sign std::cout << "H min max: " << H.minCoeff() << " " << H.maxCoeff() << std::endl; for (size_t i = 0; i < n2o.size(); i++) { curvature[n2o[i]] = H[i]; } } void update_tree_color(const std::vector& new_color, TreeNode* t) { std::set new_keys; for (auto v : t->keys) { new_keys.insert(new_color[v]); } t->keys = new_keys; for (size_t i = 0; i < t->children.size(); i++) { update_tree_color(new_color, t->children[i]); } } bool get_tree_from_convex_graph(const std::vector> &graph , const std::map, int>& flag_fpconvex, bool flag_convex_bool, TreeNode* tn, int layer, std::vector &invalid_cluster) { //set tn int target_convex = 2 - (int)flag_convex_bool; std::set counter_nodes; for (size_t i = 0; i < graph.size(); i++) { for (auto v : graph[i]) { if (v > i) { auto it = flag_fpconvex.find(std::pair(i, v)); assert(it != flag_fpconvex.end()); if (it->second != 0 && it->second != target_convex) { counter_nodes.insert(i); counter_nodes.insert(v); } } } } if (!counter_nodes.empty()) { std::vector> counter_graph(graph.size()); //graph made of counter nodes for (auto cv : counter_nodes) { for (auto cvn : graph[cv]) { if (std::find(counter_nodes.begin(), counter_nodes.end(), cvn) != counter_nodes.end()) { auto it = flag_fpconvex.find(std::pair(std::min(cv, cvn), std::max(cv, cvn))); assert(it != flag_fpconvex.end()); if (it->second != target_convex) //including smooth one counter_graph[cv].insert(cvn); } } } std::vector> counter_clusters; get_graph_component(counter_graph, counter_clusters); //set tn.children //set nodes that are not counter nodes to tn.keys for (size_t i = 0; i < graph.size(); i++) { if (!graph[i].empty() && std::find(counter_nodes.begin(), counter_nodes.end(), i) == counter_nodes.end()) { tn->keys.insert(i); } } //one more criteria: cluster == 1 and no other component if (tn->keys.size() == 0 && counter_clusters.size() == 1) { //assume only one cluster is invalid std::cout << "tree construction failed" << std::endl; //exit(EXIT_FAILURE); invalid_cluster = std::vector(counter_clusters[0].begin(), counter_clusters[0].end()); return false; } for (size_t i = 0; i < counter_clusters.size(); i++) { std::vector> subgraph(graph.size()); for (auto v : counter_clusters[i]) { for (auto vn : graph[v]) { if (std::find(counter_clusters[i].begin(), counter_clusters[i].end(), vn) != counter_clusters[i].end()) { subgraph[v].insert(vn); } } } TreeNode* child = new TreeNode; bool tmp_flag = get_tree_from_convex_graph(subgraph, flag_fpconvex, !flag_convex_bool, child, layer + 1, invalid_cluster); if (!tmp_flag) { return false; } tn->children.push_back(child); } } else { //set nodes that are not counter nodes to tn.keys for (size_t i = 0; i < graph.size(); i++) { if (!graph[i].empty() && std::find(counter_nodes.begin(), counter_nodes.end(), i) == counter_nodes.end()) { tn->keys.insert(i); } } } return true; } using namespace MeshLib; void get_mesh_vert_faces(Mesh3d& mesh, std::vector>& pos, std::vector>& faces) { pos.clear(); faces.clear(); for (size_t i = 0; i < mesh.get_faces_list()->size(); i++) { HE_edge* begin_edge = mesh.get_faces_list()->at(i)->edge; HE_edge* edge = mesh.get_faces_list()->at(i)->edge; std::vector oneface; do { //tri_verts[i][local_id++] = edge->pair->vert->id; oneface.push_back(edge->pair->vert->id); edge = edge->next; } while (edge != begin_edge); //mesh.get_faces_list()->at(fid)->normal; faces.push_back(oneface); } //std::vector> vert_pos; for (size_t i = 0; i < mesh.get_vertices_list()->size(); i++) { //vert_pos.push_back(mesh.get_vertices_list()->at(i)->pos); vec3d one_pt = mesh.get_vertices_list()->at(i)->pos; pos.push_back(std::array({ one_pt[0], one_pt[1], one_pt[2] })); } } int get_mesh_edge_type(Mesh3d& m, int heid) //0: smooth, 1: convex, 2: concave { HE_edge* e1 = m.get_edges_list()->at(heid); HE_edge* e2 = e1->pair; if (e1->face == NULL || e2->face == NULL) { return 0; } double product = e1->face->normal.Dot(e2->next->vert->pos - e1->next->vert->pos); double tmp_cos = e1->face->normal.Dot(e2->face->normal); int res = 0; if (product < 0.0) { if (tmp_cos < th_smooth_cos_value) res = 1; } else { if (tmp_cos < th_smooth_cos_value) { res = 2; } } return res; } void get_all_tri_area(Mesh3d& mesh, std::vector& tri_area) { tri_area.clear(); tri_area.resize(mesh.get_num_of_faces(), 0.0); std::vector> tri_verts(mesh.get_faces_list()->size()); //std::vector> tri_normals; //get tri list for (size_t i = 0; i < mesh.get_faces_list()->size(); i++) { HE_edge* begin_edge = mesh.get_faces_list()->at(i)->edge; HE_edge* edge = mesh.get_faces_list()->at(i)->edge; int local_id = 0; do { tri_verts[i][local_id++] = edge->pair->vert->id; edge = edge->next; } while (edge != begin_edge); //mesh.get_faces_list()->at(fid)->normal; //tri_normals.push_back(mesh.get_faces_list()->at(i)->normal); } std::vector> vert_pos; for (size_t i = 0; i < mesh.get_vertices_list()->size(); i++) { vert_pos.push_back(mesh.get_vertices_list()->at(i)->pos); } for (size_t i = 0; i < tri_area.size(); i++) { //tri_area[i] = std::abs(compute_tri_area(tri_verts[tri_faces[i][0]], tri_verts[tri_faces[i][1]], tri_verts[tri_faces[i][2]])); //total_tri_area += tri_area[i]; tri_area[i] = std::abs(compute_tri_area(vert_pos[tri_verts[i][0]], vert_pos[tri_verts[i][1]], vert_pos[tri_verts[i][2]])); } } bool dijkstra_mesh(Mesh3d* m, size_t start_vert, std::vector& prev_map, std::vector& dist) { prev_map.clear(); dist.clear(); dist.resize(m->get_num_of_vertices(), DBL_MAX); prev_map.resize(m->get_num_of_vertices(), 0); dist[start_vert] = 0.0; std::set*>> vqueue; for (size_t i = 0; i < m->get_num_of_vertices(); i++) { HE_vert* hv = m->get_vertex(i); vqueue.insert(std::pair*>(dist[hv->id], hv)); } while (!vqueue.empty()) { std::set*>>::iterator iter = vqueue.begin(); HE_vert* u = iter->second; vqueue.erase(iter); if (u->edge == NULL) continue; HE_edge* he = u->edge; do { double alt = dist[u->id] + (u->pos - he->vert->pos).Length(); if (alt < dist[he->vert->id]) { vqueue.erase(std::pair*>(dist[he->vert->id], he->vert)); dist[he->vert->id] = alt; prev_map[he->vert->id] = u->id; vqueue.insert(std::pair*>(dist[he->vert->id], he->vert)); } he = he->pair->next; } while (he != u->edge); } return true; } void get_he_list_dijkstra(Mesh3d& m, size_t start_vert, size_t end_vert, const std::vector& prev_map, std::vector& he_list) { //get he list for path start_vert -> end_vert he_list.clear(); size_t cur = end_vert; while (cur != start_vert) { size_t prev = prev_map[cur]; HE_edge* start_edge = m.get_vertex(cur)->edge; HE_edge* edge = m.get_vertex(cur)->edge; do { if (edge->vert->id == prev) { he_list.push_back(edge->id); he_list.push_back(edge->pair->id); break; } edge = edge->pair->next; } while (edge != start_edge); cur = prev; } } void repair_tree_features(Mesh3d& m, const std::vector& face_color, const std::vector& invalid_colors, std::vector>& ungrouped_features) { //get sub mesh firstly std::vector invalid_color_map(face_color.size(), false); for (auto cid : invalid_colors) //invalid_color_set.insert(cid); invalid_color_map[cid] = true; std::vector> all_pts; std::vector> all_faces, invalid_faces; get_mesh_vert_faces(m, all_pts, all_faces); for (size_t i = 0; i < face_color.size(); i++) { if (invalid_color_map[face_color[i] - 1] == true) { invalid_faces.push_back(all_faces[i]); } } Mesh3d sub_mesh; sub_mesh.load_mesh(all_pts, invalid_faces); sub_mesh.write_obj("invalid.obj"); //to change: ungrouped features //step1: load feature, step2: select convex-concavefeature, step3: add shorted path to other corner points or boundary std::vector> feature_v2he(sub_mesh.get_num_of_vertices()); //id of hes ematating from each vertex std::vector> ungrouped_features_new; std::vector flag_he_feature(sub_mesh.get_num_of_edges(), false); for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; //feature_degree_v[id0]++; //feature_degree_v[id1]++; HE_edge* begin_edge = sub_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = sub_mesh.get_vertices_list()->at(id0)->edge; bool flag_found = false; //if (edge != NULL && !sub_mesh.is_on_boundary(edge)) //not boundary if (edge != NULL) { do { if (id1 == edge->vert->id) { feature_v2he[id0].push_back(edge->id); feature_v2he[id1].push_back(edge->pair->id); flag_he_feature[edge->id] = true; flag_he_feature[edge->pair->id] = true; flag_found = true; break; } edge = edge->pair->next; } while (edge != begin_edge); } //assert(flag_found == true); if (flag_found == true) { ungrouped_features_new.push_back(ungrouped_features[i]); } } std::vector feature_vtype(sub_mesh.get_num_of_vertices(), 0); //0: normal case, 1: corner, 2: turn vertex std::vector feature_vcolor(sub_mesh.get_num_of_vertices(), false); for (size_t i = 0; i < feature_v2he.size(); i++) { if (feature_v2he[i].size() == 1) { feature_vtype[i] = 1; } else if (feature_v2he[i].size() >= 3) { //normal or turn int convex_count = 0, concave_count = 0; for (auto heid : feature_v2he[i]) { int hetype = get_mesh_edge_type(sub_mesh, heid); if (hetype == 1) convex_count += 1; if (hetype == 2) concave_count += 1; } if (convex_count > 0 && concave_count > 0) { feature_vtype[i] = 2; //turn //std::cout << "turn vertex id: " << i << std::endl; } else { feature_vtype[i] = 1; } } } std::set ep_cand; //corner or boundary for (size_t i = 0; i < feature_v2he.size(); i++) { if (feature_vtype[i] > 0) ep_cand.insert(i); } /*for (size_t i = 0; i < sub_mesh.get_num_of_vertices(); i++) { if (sub_mesh.is_on_boundary(sub_mesh.get_vertex(i))) { ep_cand.insert(i); } }*/ for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { if (sub_mesh.is_on_boundary(sub_mesh.get_edge(i))) { ep_cand.insert(sub_mesh.get_edge(i)->vert->id); ep_cand.insert(sub_mesh.get_edge(i)->pair->vert->id); } } //add features for each turn vertex //std::vector flag_he_feature_add = flag_he_feature; std::vector flag_he_feature_add(flag_he_feature.size(), false); for (size_t i = 0; i < feature_v2he.size(); i++) { if (feature_vtype[i] == 2 && !feature_vcolor[i]) { std::vector prev_map; std::vector dist; dijkstra_mesh(&sub_mesh, i, prev_map, dist); int target = -1; std::vector> dist_ep; for (auto ep : ep_cand) { if (ep != i) dist_ep.push_back(std::pair(dist[ep], ep)); } std::sort(dist_ep.begin(), dist_ep.end()); for (size_t j = 0; j < dist_ep.size(); j++) { int cur_vert = dist_ep[j].second; bool path_valid = false; //if one of the edge in the path is not feature, set as true std::vector he_list; get_he_list_dijkstra(sub_mesh, i, cur_vert, prev_map, he_list); for (auto he : he_list) { if (flag_he_feature[he] == false) path_valid = true; } if (path_valid) { //update flag_he_feature_add for (auto he : he_list) { flag_he_feature_add[he] = true; } target = cur_vert; break; } } assert(target != -1); feature_vcolor[i] = true; //target also set as true feature_vcolor[target] = true; } } //update ungrouped_features std::set> ungrouped_features_set; for (auto& pp : ungrouped_features) { ungrouped_features_set.insert(std::pair(std::min(pp.first, pp.second), std::max(pp.first, pp.second))); } //ungrouped_features.clear(); for (size_t i = 0; i < flag_he_feature_add.size(); i++) { if (flag_he_feature_add[i]) { //set opposite as true flag_he_feature_add[sub_mesh.get_edge(i)->pair->id] = false; int v1 = sub_mesh.get_edge(i)->vert->id, v2 = sub_mesh.get_edge(i)->pair->vert->id; ungrouped_features_set.insert(std::pair(std::min(v1, v2), std::max(v1, v2))); //ungrouped_features.push_back(std::pair(sub_mesh.get_edge(i)->vert->id, sub_mesh.get_edge(i)->pair->vert->id)); } } //ungrouped_features.clear(); ungrouped_features = std::vector>(ungrouped_features_set.begin(), ungrouped_features_set.end()); } void repair_tree_features_maxflow(Mesh3d& m, const std::vector& face_color, const std::vector& invalid_colors, std::vector>& ungrouped_features) { //bool flag_debug = true; bool flag_debug = false; std::set> ungrouped_features_set; for (auto& pp : ungrouped_features) { ungrouped_features_set.insert(std::pair(std::min(pp.first, pp.second), std::max(pp.first, pp.second))); } std::vector> all_pts; std::vector> all_faces, all_invalid_faces; get_mesh_vert_faces(m, all_pts, all_faces); std::vector invalid_color_map(face_color.size(), false); for (auto cid : invalid_colors) //invalid_color_set.insert(cid); invalid_color_map[cid] = true; for (size_t i = 0; i < face_color.size(); i++) { if (invalid_color_map[face_color[i] - 1]) { all_invalid_faces.push_back(all_faces[i]); } } Mesh3d invalid_mesh; invalid_mesh.load_mesh(all_pts, all_invalid_faces); if (flag_debug) { invalid_mesh.write_obj("invalid_all.obj"); } std::map, int> edgepair2type; for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; HE_edge* begin_edge = invalid_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = invalid_mesh.get_vertices_list()->at(id0)->edge; //if (edge != NULL && !invalid_mesh.is_on_boundary(edge)) //not boundary if (edge != NULL) { do { if (id1 == edge->vert->id) { int edge_type = get_mesh_edge_type(invalid_mesh, edge->id); //if boundary, type is 0 edgepair2type[ungrouped_features[i]] = edge_type; break; } edge = edge->pair->next; } while (edge != begin_edge); } } bool flag_patchbypatch = true; //flip edge std::vector> flipped_edges; std::vector flipped_corner; //repair patch by patch if (flag_patchbypatch) { for (auto cid : invalid_colors) { std::vector> cur_faces; for (size_t i = 0; i < face_color.size(); i++) { if (face_color[i] - 1 == cid) { cur_faces.push_back(all_faces[i]); } } Mesh3d sub_mesh; sub_mesh.load_mesh(all_pts, cur_faces); if (flag_debug) sub_mesh.write_obj("curpatch.obj"); //submesh might need to be adjusted std::vector flag_he_feature(sub_mesh.get_num_of_edges(), false); std::vector flag_he_type(sub_mesh.get_num_of_edges(), 0); for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; HE_edge* begin_edge = sub_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = sub_mesh.get_vertices_list()->at(id0)->edge; bool flag_found = false; //if (edge != NULL && !sub_mesh.is_on_boundary(edge)) //not boundary if (edge != NULL) { do { if (id1 == edge->vert->id) { flag_he_feature[edge->id] = true; flag_he_feature[edge->pair->id] = true; //int edge_type = get_mesh_edge_type(sub_mesh, edge->id); int edge_type = edgepair2type[ungrouped_features[i]]; flag_he_type[edge->id] = edge_type; flag_he_type[edge->pair->id] = edge_type; flag_found = true; break; } edge = edge->pair->next; } while (edge != begin_edge); } } //check mesh validness: a triangle should not have two nb edge with different convexity bool flag_mesh_valid = true; for (size_t i = 0; i < sub_mesh.get_num_of_faces(); i++) { if (sub_mesh.is_on_boundary(sub_mesh.get_face(i))) { int convex_count = 0, concave_count = 0; HE_edge* edge = sub_mesh.get_face(i)->edge; do { if (flag_he_feature[edge->id]) { if (flag_he_type[edge->id] == 1) { convex_count += 1; } else if (flag_he_type[edge->id] == 2) { concave_count += 1; } } edge = edge->next; } while (edge != sub_mesh.get_face(i)->edge); if (convex_count > 0 && concave_count > 0) { flag_mesh_valid = false; //edge flip edge = sub_mesh.get_face(i)->edge; //find no feature curve do { if (!flag_he_feature[edge->id]) { break; } edge = edge->next; } while (edge != sub_mesh.get_face(i)->edge); if (sub_mesh.is_on_boundary(edge)) { std::cout << "all 3 edge of triangle " << i << " are features" << std::endl; } else { int v0 = edge->vert->id, v1 = edge->pair->vert->id; flipped_edges.push_back(std::pair(std::min(v0, v1), std::max(v0, v1))); flipped_corner.push_back(edge->next->vert->id); int oppo_face_id = edge->pair->face->id; std::vector newf1, newf2; //edge flip newf1.push_back(edge->vert->id); newf1.push_back(edge->next->vert->id); newf1.push_back(edge->pair->next->vert->id); edge = edge->pair; newf2.push_back(edge->vert->id); newf2.push_back(edge->next->vert->id); newf2.push_back(edge->pair->next->vert->id); cur_faces[i] = newf1; cur_faces[oppo_face_id] = newf2; } } } } if (!flag_mesh_valid) { std::cout << "input mesh for patch " << cid << " is invalid" << std::endl; /*Mesh3d new_mesh; new_mesh.load_mesh(all_pts, cur_faces); std::swap(sub_mesh, new_mesh);*/ sub_mesh.load_mesh(all_pts, cur_faces); if (flag_debug) sub_mesh.write_obj("invalidpatch_repair.obj"); //submesh might need to be adjusted flag_he_feature.clear(); flag_he_type.clear(); flag_he_feature.resize(sub_mesh.get_num_of_edges(), false); flag_he_type.resize(sub_mesh.get_num_of_edges(), 0); for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; //feature_degree_v[id0]++; //feature_degree_v[id1]++; HE_edge* begin_edge = sub_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = sub_mesh.get_vertices_list()->at(id0)->edge; bool flag_found = false; //if (edge != NULL && !sub_mesh.is_on_boundary(edge)) //not boundary if (edge != NULL) { do { if (id1 == edge->vert->id) { flag_he_feature[edge->id] = true; flag_he_feature[edge->pair->id] = true; //int edge_type = get_mesh_edge_type(sub_mesh, edge->id); int edge_type = edgepair2type[ungrouped_features[i]]; flag_he_type[edge->id] = edge_type; flag_he_type[edge->pair->id] = edge_type; flag_found = true; break; } edge = edge->pair->next; } while (edge != begin_edge); } } } //mesh constructed, build max cut algorithm int n_node = sub_mesh.get_num_of_faces() + 2; std::set convex_node, concave_node; for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { if (!flag_he_feature[i]) continue; if (flag_he_type[i] == 1) { //convex if (sub_mesh.get_edge(i)->face != NULL) { convex_node.insert(sub_mesh.get_edge(i)->face->id); } } else if (flag_he_type[i] == 2) { //concave if (sub_mesh.get_edge(i)->face != NULL) { concave_node.insert(sub_mesh.get_edge(i)->face->id); } } } assert(!convex_node.empty() && !concave_node.empty()); if (!convex_node.empty() && !concave_node.empty()) { int s = sub_mesh.get_num_of_faces(); int t = s + 1; double max_cap = 1.0 * sub_mesh.get_num_of_faces(); std::vector> graph_edges; std::vector graph_edgecap; //s to convex for (auto node : convex_node) { graph_edges.push_back(std::pair(s, node)); graph_edgecap.push_back(max_cap); } //concave to t for (auto node : concave_node) { graph_edges.push_back(std::pair(node, t)); graph_edgecap.push_back(max_cap); } if (flag_debug) { std::vector> convex_faces, concave_faces; for (auto node : convex_node) { convex_faces.push_back(cur_faces[node]); } for (auto node : concave_node) { concave_faces.push_back(cur_faces[node]); } Mesh3d convexmesh, concavemesh; convexmesh.load_mesh(all_pts, convex_faces); concavemesh.load_mesh(all_pts, concave_faces); convexmesh.write_obj("convex.obj"); concavemesh.write_obj("concave.obj"); } //other parts for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { if (!sub_mesh.is_on_boundary(sub_mesh.get_edge(i)) && flag_he_type[i] == 0) { //not boundary, not convex or concave graph_edges.push_back(std::pair(sub_mesh.get_edge(i)->face->id, sub_mesh.get_edge(i)->pair->face->id)); graph_edgecap.push_back(1.0); } } std::vector part1, part2; MinCutfromMaxFlow(n_node, graph_edges, graph_edgecap, s, t, part1, part2); //for debugging: //visualze part1 and part2 if (flag_debug) { Mesh3d mesh_part1, mesh_part2; std::vector> part1_faces, part2_faces; for (auto f : part1) { part1_faces.push_back(cur_faces[f]); } for (auto f : part2) { part2_faces.push_back(cur_faces[f]); } mesh_part1.load_mesh(all_pts, part1_faces); mesh_part2.load_mesh(all_pts, part2_faces); mesh_part1.write_obj("part1.obj"); mesh_part2.write_obj("part2.obj"); } std::vector flag_part1(n_node - 2, false), flag_part2(n_node - 2, false); for (auto f : part1) flag_part1[f] = true; for (auto f : part2) flag_part2[f] = true; for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { auto edge = sub_mesh.get_edge(i); if (!sub_mesh.is_on_boundary(edge)) { int f0 = edge->face->id, f1 = edge->pair->face->id; if (flag_part1[f0] && flag_part2[f1]) { int id0 = edge->vert->id, id1 = edge->pair->vert->id; ungrouped_features_set.insert(std::pair(std::min(id0, id1), std::max(id0, id1))); } } } } } } else { //for (auto cid : invalid_colors) if (true) { std::vector> cur_faces; for (size_t i = 0; i < face_color.size(); i++) { if (invalid_color_map[face_color[i] - 1]) { cur_faces.push_back(all_faces[i]); } } Mesh3d sub_mesh; sub_mesh.load_mesh(all_pts, cur_faces); sub_mesh.write_obj("invalidpatch.obj"); //submesh might need to be adjusted std::vector flag_he_feature(sub_mesh.get_num_of_edges(), false); std::vector flag_he_type(sub_mesh.get_num_of_edges(), 0); for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; HE_edge* begin_edge = sub_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = sub_mesh.get_vertices_list()->at(id0)->edge; bool flag_found = false; if (edge != NULL && !sub_mesh.is_on_boundary(edge)) //not boundary //if (edge != NULL) { do { if (id1 == edge->vert->id) { flag_he_feature[edge->id] = true; flag_he_feature[edge->pair->id] = true; int edge_type = get_mesh_edge_type(sub_mesh, edge->id); flag_he_type[edge->id] = edge_type; flag_he_type[edge->pair->id] = edge_type; flag_found = true; break; } edge = edge->pair->next; } while (edge != begin_edge); } } //check mesh validness: a triangle should not have two nb edge with different convexity bool flag_mesh_valid = true; for (size_t i = 0; i < sub_mesh.get_num_of_faces(); i++) { if (sub_mesh.is_on_boundary(sub_mesh.get_face(i))) { int convex_count = 0, concave_count = 0; HE_edge* edge = sub_mesh.get_face(i)->edge; do { if (flag_he_feature[edge->id]) { if (flag_he_type[edge->id] == 1) { convex_count += 1; } else if (flag_he_type[edge->id] == 2) { concave_count += 1; } } edge = edge->next; } while (edge != sub_mesh.get_face(i)->edge); if (convex_count > 0 && concave_count > 0) { flag_mesh_valid = false; //edge flip edge = sub_mesh.get_face(i)->edge; //find no feature curve do { if (!flag_he_feature[edge->id]) { break; } } while (edge != sub_mesh.get_face(i)->edge); if (sub_mesh.is_on_boundary(edge)) { std::cout << "all 3 edge of triangle " << i << " are features" << std::endl; int a = 0; } else { int oppo_face_id = edge->pair->face->id; std::vector newf1, newf2; //edge flip newf1.push_back(edge->vert->id); newf1.push_back(edge->next->vert->id); newf1.push_back(edge->pair->next->id); edge = edge->pair; newf2.push_back(edge->vert->id); newf2.push_back(edge->next->vert->id); newf2.push_back(edge->pair->next->id); cur_faces[i] = newf1; cur_faces[oppo_face_id] = newf2; } } } } if (!flag_mesh_valid) { //std::cout << "input mesh for patch " << cid << " is invalid" << std::endl; sub_mesh.load_mesh(all_pts, cur_faces); sub_mesh.write_obj("invalidpatch_repair.obj"); //submesh might need to be adjusted std::vector flag_he_feature(sub_mesh.get_num_of_edges(), false); std::vector flag_he_type(sub_mesh.get_num_of_edges(), 0); flag_he_feature.clear(); flag_he_type.clear(); flag_he_feature.resize(sub_mesh.get_num_of_edges(), false); flag_he_type.resize(sub_mesh.get_num_of_edges(), 0); for (size_t i = 0; i < ungrouped_features.size(); i++) { int id0 = ungrouped_features[i].first; int id1 = ungrouped_features[i].second; //feature_degree_v[id0]++; //feature_degree_v[id1]++; HE_edge* begin_edge = sub_mesh.get_vertices_list()->at(id0)->edge; HE_edge* edge = sub_mesh.get_vertices_list()->at(id0)->edge; bool flag_found = false; if (edge != NULL && !sub_mesh.is_on_boundary(edge)) //not boundary //if (edge != NULL) { do { if (id1 == edge->vert->id) { flag_he_feature[edge->id] = true; flag_he_feature[edge->pair->id] = true; int edge_type = get_mesh_edge_type(sub_mesh, edge->id); flag_he_type[edge->id] = edge_type; flag_he_type[edge->pair->id] = edge_type; flag_found = true; break; } edge = edge->pair->next; } while (edge != begin_edge); } } } //mesh constructed, build max cut algorithm int n_node = sub_mesh.get_num_of_faces() + 2; std::set convex_node, concave_node; for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { if (flag_he_type[i] == 1) { //convex if (sub_mesh.get_edge(i)->face != NULL) { convex_node.insert(sub_mesh.get_edge(i)->face->id); } } else if (flag_he_type[i] == 2) { //concave if (sub_mesh.get_edge(i)->face != NULL) { concave_node.insert(sub_mesh.get_edge(i)->face->id); } } } assert(!convex_node.empty() && !concave_node.empty()); if (!convex_node.empty() && !concave_node.empty()) { int s = sub_mesh.get_num_of_faces(); int t = s + 1; double max_cap = 1.0 * sub_mesh.get_num_of_faces(); std::vector> graph_edges; std::vector graph_edgecap; //s to convex for (auto node : convex_node) { graph_edges.push_back(std::pair(s, node)); graph_edgecap.push_back(max_cap); } //concave to t for (auto node : concave_node) { graph_edges.push_back(std::pair(node, t)); graph_edgecap.push_back(max_cap); } //other parts for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { if (!sub_mesh.is_on_boundary(sub_mesh.get_edge(i)) && flag_he_type[i] == 0) { //not boundary, not convex or concave graph_edges.push_back(std::pair(sub_mesh.get_edge(i)->face->id, sub_mesh.get_edge(i)->pair->face->id)); graph_edgecap.push_back(1.0); } } std::vector part1, part2; MinCutfromMaxFlow(n_node, graph_edges, graph_edgecap, s, t, part1, part2); //get min cut by neighbor info std::vector flag_part1(n_node - 2, false), flag_part2(n_node - 2, false); for (auto f : part1) flag_part1[f] = true; for (auto f : part2) flag_part2[f] = true; for (size_t i = 0; i < sub_mesh.get_num_of_edges(); i++) { auto edge = sub_mesh.get_edge(i); if (!sub_mesh.is_on_boundary(edge)) { int f0 = edge->face->id, f1 = edge->pair->face->id; if (flag_part1[f0] && flag_part2[f1]) { int id0 = edge->vert->id, id1 = edge->pair->vert->id; ungrouped_features_set.insert(std::pair(std::min(id0, id1), std::max(id0, id1))); } } } //visualze part1 and part2 std::vector> part1_faces, part2_faces; for (auto f : part1) { part1_faces.push_back(cur_faces[f]); } for (auto f : part2) { part2_faces.push_back(cur_faces[f]); } Mesh3d mesh_part1, mesh_part2; mesh_part1.load_mesh(all_pts, part1_faces); mesh_part2.load_mesh(all_pts, part2_faces); mesh_part1.write_obj("part1.obj"); mesh_part2.write_obj("part2.obj"); } } } ungrouped_features = std::vector>(ungrouped_features_set.begin(), ungrouped_features_set.end()); if (!flipped_edges.empty()) { //update all faces //for (auto& pp : flipped_edges) for (size_t i = 0; i < flipped_edges.size(); i++) { auto pp = flipped_edges[i]; int id0 = pp.first; int id1 = pp.second; auto begin_edge = m.get_vertices_list()->at(id0)->edge; auto edge = m.get_vertices_list()->at(id0)->edge; do { if (id1 == edge->vert->id) { if (edge->next->vert->id != flipped_corner[i]) edge = edge->pair; assert(edge->next->vert->id == flipped_corner[i]); int cur_face_id = edge->face->id; int oppo_face_id = edge->pair->face->id; std::vector newf1, newf2; //edge flip newf1.push_back(edge->vert->id); newf1.push_back(edge->next->vert->id); newf1.push_back(edge->pair->next->vert->id); edge = edge->pair; newf2.push_back(edge->vert->id); newf2.push_back(edge->next->vert->id); newf2.push_back(edge->pair->next->vert->id); all_faces[cur_face_id] = newf1; all_faces[oppo_face_id] = newf2; break; } //edge = edge->next; edge = edge->pair->next; } while (edge != begin_edge); } m.load_mesh(all_pts, all_faces); } return; } bool load_xyz_file(const char* filename, std::vector& pts, std::vector& normals) { //assume normal is given int number_of_lines = 0; std::string line; std::ifstream myfile(filename); while (std::getline(myfile, line)) ++number_of_lines; //return 0; myfile.close(); pts.clear(); normals.clear(); pts.resize(number_of_lines); normals.resize(number_of_lines); myfile.open(filename); for (size_t i = 0; i < number_of_lines; i++) { for (size_t j = 0; j < 3; j++) { myfile >> pts[i][j]; } for (size_t j = 0; j < 3; j++) { myfile >> normals[i][j]; } } myfile.close(); return true; } TinyVector perturb_normal(const TinyVector normal, double angle_noise1, double angle_noise2) { //get angle from normal //assume normal is normalized double theta = acos(normal[2]); double phi = acos(normal[0] / (sqrt(1 - normal[2] * normal[2]) + DENOMINATOR_EPS)); double phi_ref = asin(normal[1] / (sqrt(1 - normal[2] * normal[2]) + DENOMINATOR_EPS)); if (phi_ref < 0) phi = 2 * M_PI - phi; theta = theta + angle_noise1 / 180.0 * M_PI; phi = phi + angle_noise2 / 180.0 * M_PI; TinyVector normal_new; normal_new[0] = sin(theta) * cos(phi); normal_new[1] = sin(theta) * sin(phi); normal_new[2] = cos(theta); return normal_new; } #include bool sample_pts_from_mesh(const std::vector>& tri_verts, const std::vector>& tri_faces, const std::vector>& tri_normals, const std::vector& tri_face_masks, int n_sample, std::vector>& output_pts, std::vector>& output_normals, std::vector& output_masks, double sigma, double sigma_n) { if (tri_faces.empty()) { std::cout << "!!!no face input for sampling" << std::endl; return false; } output_pts.clear(); output_normals.clear(); output_masks.clear(); bool flag_pos_noise = (sigma > 0.0); bool flag_normal_noise = (sigma_n > 0.0); //noise to be set if (!flag_pos_noise) sigma = 0.0; if (!flag_normal_noise) sigma_n = 0.0; //feature parts first std::random_device rd; std::mt19937 e2(rd()); std::uniform_real_distribution unif_dist(0, 1); //std::normal_distribution normal_dist(0, sigma); std::uniform_real_distribution normal_dist(-sigma, sigma); std::uniform_real_distribution angle_unif_dist(-sigma_n, sigma_n); //compute triangles std::vector tri_area(tri_faces.size(), 0.0); double total_tri_area = 0.0; for (size_t i = 0; i < tri_area.size(); i++) { tri_area[i] = std::abs(compute_tri_area(tri_verts[tri_faces[i][0]], tri_verts[tri_faces[i][1]], tri_verts[tri_faces[i][2]])); total_tri_area += tri_area[i]; } std::vector tri_bound(tri_area.size() + 1, 0.0); for (size_t i = 0; i < tri_area.size(); i++) { tri_bound[i + 1] = tri_bound[i] + tri_area[i] / total_tri_area; } for (size_t i = 0; i < n_sample; i++) { double u = unif_dist(e2); auto iter = std::upper_bound(tri_bound.begin(), tri_bound.end(), u); int fid = (int)std::distance(tri_bound.begin(), iter); fid = std::max(0, fid - 1); fid = std::min(fid, (int)tri_faces.size() - 1); double s = unif_dist(e2); double t = unif_dist(e2); if (s + t > 1) { s = 1 - s; t = 1 - t; } TinyVector facenormal = tri_normals[fid]; if (flag_pos_noise) { output_pts.push_back((1.0 - s - t) * tri_verts[tri_faces[fid][0]] + s * tri_verts[tri_faces[fid][1]] + t * tri_verts[tri_faces[fid][2]] + facenormal * normal_dist(e2)); } else output_pts.push_back((1.0 - s - t) * tri_verts[tri_faces[fid][0]] + s * tri_verts[tri_faces[fid][1]] + t * tri_verts[tri_faces[fid][2]]); if (flag_normal_noise) { output_normals.push_back(perturb_normal(facenormal, angle_unif_dist(e2), angle_unif_dist(e2))); } else { output_normals.push_back(facenormal); } output_masks.push_back(tri_face_masks[fid]); } } using namespace MeshLib; void sort_grouped_features(Mesh3d* m, std::vector>& grouped_features) { std::vector> grouped_features_new; for (size_t i = 0; i < grouped_features.size(); i++) { std::map> v2hes; std::map> v2vs; for (size_t j = 0; j < grouped_features[i].size(); j++) { int heid = grouped_features[i][j]; int vid[2]; vid[0] = m->get_edges_list()->at(heid)->vert->id; vid[1] = m->get_edges_list()->at(heid)->pair->vert->id; for (size_t k = 0; k < 2; k++) { if (v2hes.find(vid[k]) == v2hes.end()) { std::vector hes, vs; hes.push_back(heid); vs.push_back(vid[1 - k]); v2hes[vid[k]] = hes; v2vs[vid[k]] = vs; } else { v2hes[vid[k]].push_back(heid); v2vs[vid[k]].push_back(vid[1 - k]); } } } int cur_edge = -1; int cur_vert = -1; int prev_edge = -1; for (auto it : v2hes) { if (it.second.size() == 1) { cur_vert = it.first; } } if (cur_vert == -1) { //circular case cur_vert = v2hes.begin()->first; } std::vector one_group; //from start to end std::queue q; std::vector edge_color(m->get_num_of_edges(), false); while (true) { //get cur_edge cur_edge = -1; for (auto e : v2hes[cur_vert]) { if (e != prev_edge && edge_color[e] == false) { cur_edge = e; break; } } if (cur_edge == -1) break; edge_color[cur_edge] = true; //get next_vert assert(cur_vert == m->get_edges_list()->at(cur_edge)->vert->id || cur_vert == m->get_edges_list()->at(cur_edge)->pair->vert->id); int next_vert = m->get_edges_list()->at(cur_edge)->vert->id + m->get_edges_list()->at(cur_edge)->pair->vert->id - cur_vert; if (next_vert == m->get_edges_list()->at(cur_edge)->vert->id) { one_group.push_back(cur_edge); } else { one_group.push_back(m->get_edges_list()->at(cur_edge)->pair->id); } cur_vert = next_vert; prev_edge = cur_edge; } assert(one_group.size() == grouped_features[i].size()); grouped_features_new.push_back(one_group); } grouped_features = grouped_features_new; } int cluster_mesh_faces(Mesh3d* m, const std::vector& he_feature_flag, std::vector> &grouped_features, int cluster_begin_id, std::vector& face2cluster, std::vector>& feature_twoface_colors) { //return n_cluster + cluster_start_id face2cluster.clear(); feature_twoface_colors.clear(); face2cluster.resize(m->get_num_of_faces(), -1); std::vector> face_centers(m->get_num_of_faces(), TinyVector(0,0,0)); for (size_t i = 0; i < m->get_num_of_faces(); i++) { HE_edge* begin_edge = m->get_faces_list()->at(i)->edge; HE_edge* edge = begin_edge; do { face_centers[i] = face_centers[i] + edge->vert->pos; edge = edge->next; } while (edge != begin_edge); face_centers[i] = face_centers[i] / 3.0; } class face_cluster { public: face_cluster(int faceid = 0, TinyVector ori = TinyVector(0,0,0), TinyVector cur = TinyVector(0, 0, 0)) :fid(faceid), ori_face_center(ori) { e = (cur - ori).Length(); //e = energy; } public: int fid; TinyVector ori_face_center; double e; }; class compare_face_cluster { public: compare_face_cluster() { } bool operator()(const face_cluster& fc1, const face_cluster& fc2) const { return fc1.e > fc2.e; } }; std::vector energy(m->get_num_of_faces(), DBL_MAX); std::priority_queue, compare_face_cluster> m_queue; sort_grouped_features(m, grouped_features); std::vector flag_face_unchangable(m->get_num_of_faces(), false); for (size_t i = 0; i < grouped_features.size(); i++) { int color1 = cluster_begin_id; int color2 = cluster_begin_id + 1; feature_twoface_colors.push_back(std::pair(color1, color2)); for (size_t j = 0; j < grouped_features[i].size(); j++) { int heid = grouped_features[i][j]; int fid1 = m->get_edges_list()->at(heid)->face->id; int fid2 = m->get_edges_list()->at(heid)->pair->face->id; if (face2cluster[fid1] == -1) { face2cluster[fid1] = color1; m_queue.push(face_cluster(fid1, face_centers[fid1], face_centers[fid1])); energy[fid1] = 0; /*energy[fid1] = 1 - std::abs(m->get_faces_list()->at(fid1)->normal.Dot(m->get_faces_list()->at(fid2)->normal)); m_queue.push(face_cluster(fid1, face_centers[fid1], face_centers[fid1], energy[fid1]));*/ flag_face_unchangable[fid1] = true; } if (face2cluster[fid2] == -1) { face2cluster[fid2] = color2; m_queue.push(face_cluster(fid2, face_centers[fid2], face_centers[fid2])); energy[fid2] = 0; //energy[fid2] = energy[fid1]; //m_queue.push(face_cluster(fid2, face_centers[fid2], face_centers[fid2], energy[fid2])); flag_face_unchangable[fid2] = true; } } cluster_begin_id = cluster_begin_id + 2; } while (!m_queue.empty()) { face_cluster fc = m_queue.top(); m_queue.pop(); HE_edge* begin_edge = m->get_faces_list()->at(fc.fid)->edge; HE_edge* edge = begin_edge; do { if (he_feature_flag[edge->id]) { edge = edge->next; continue; } assert(fc.fid == edge->face->id || fc.fid == edge->pair->face->id); int otherface = edge->face->id + edge->pair->face->id - fc.fid; if (!flag_face_unchangable[otherface]) { double tmp_energy = (face_centers[otherface] - fc.ori_face_center).Length(); if (face2cluster[otherface] == -1) { face2cluster[otherface] = face2cluster[fc.fid]; m_queue.push(face_cluster(otherface, fc.ori_face_center, face_centers[otherface])); energy[otherface] = tmp_energy; } else { //colored if (tmp_energy < energy[otherface]) { face2cluster[otherface] = face2cluster[fc.fid]; m_queue.push(face_cluster(otherface, fc.ori_face_center, face_centers[otherface])); energy[otherface] = tmp_energy; } } } edge = edge->next; } while (edge != begin_edge); } return cluster_begin_id; } void get_cluster_from_coloring(const std::vector& face_color, int color_start_id, std::vector>& face_clusters) { int max_color = -1; for (auto c : face_color) { assert(c >= color_start_id); if (max_color < c) { max_color = c; } } face_clusters.clear(); for (size_t i = color_start_id; i <= max_color; i++) { std::vector one_cluster; for (size_t j = 0; j < face_color.size(); j++) { if (face_color[j] == i) { one_cluster.push_back(j); } } face_clusters.push_back(one_cluster); } } int merge_clusters(Mesh3d* m, const std::vector& he_feature_flag, int cluster_begin_id, int n_cluster, const std::vector>& feature_twoface_colors, std::vector& face_color) { //return num_clusters + cluster_begin_id std::vector feature_pair_mapping(n_cluster, -1); for (auto& p : feature_twoface_colors) { feature_pair_mapping[p.first - 1] = p.second - 1; feature_pair_mapping[p.second - 1] = p.first - 1; } std::vector cluster_o2n(n_cluster, -1); //starting from 0->0 std::vector> con(n_cluster); //split by features int cluster_id = 0; for (size_t i = 0; i < m->get_num_of_edges(); i++) { if (he_feature_flag[i]) continue; int color1 = face_color[m->get_edges_list()->at(i)->face->id] - 1; int color2 = face_color[m->get_edges_list()->at(i)->pair->face->id] - 1; if (color1 != color2) { con[color1].insert(color2); con[color2].insert(color1); } } for (size_t i = 0; i < n_cluster; i++) { if (cluster_o2n[i] != -1) continue; std::queue q; q.push(i); cluster_o2n[i] = cluster_id; std::set mset; while (!q.empty()) { int front = q.front(); q.pop(); for (auto nn : con[front]) { if (cluster_o2n[nn] != -1) continue; int pair_cluster = feature_pair_mapping[nn]; if (cluster_o2n[pair_cluster] == cluster_id) continue; cluster_o2n[nn] = cluster_id; q.push(nn); } } cluster_id = cluster_id + 1; } for (size_t i = 0; i < face_color.size(); i++) { face_color[i] = cluster_o2n[face_color[i] - 1] + 1; } return cluster_id + 1; } void get_grouped_edges(Mesh3d& mesh, const std::vector& he_feature_flag, const std::vector>& feature_v2he, std::vector>& grouped_features, std::vector& he2gid) { grouped_features.clear(); he2gid.clear(); he2gid.resize(mesh.get_num_of_edges(), -1); int groupid = 0; while (true) { int first_he = -1; for (size_t i = 0; i < he_feature_flag.size(); i++) { if (he_feature_flag[i] && he2gid[i] == -1) { first_he = i; break; } } if (first_he == -1) { break; } std::vector onegroup; std::queue q; q.push(first_he); he2gid[first_he] = groupid; he2gid[mesh.get_edges_list()->at(first_he)->pair->id] = groupid; while (!q.empty()) { size_t curhe = q.front(); onegroup.push_back(curhe); q.pop(); size_t curhe_pair = mesh.get_edges_list()->at(curhe)->pair->id; std::vector twoverts; twoverts.push_back(mesh.get_edges_list()->at(curhe)->vert->id); twoverts.push_back(mesh.get_edges_list()->at(curhe)->pair->vert->id); std::vector twoedges; twoedges.push_back(curhe_pair); twoedges.push_back(curhe); for (size_t i = 0; i < 2; i++) { //only accept points with degree 2 if (feature_v2he[twoverts[i]].size() == 2) { assert(twoedges[i] == feature_v2he[twoverts[i]][0] || twoedges[i] == feature_v2he[twoverts[i]][1]); size_t other_he = feature_v2he[twoverts[i]][0] + feature_v2he[twoverts[i]][1] - twoedges[i]; if (he2gid[other_he] == -1) { q.push(other_he); he2gid[other_he] = groupid; he2gid[mesh.get_edges_list()->at(other_he)->pair->id] = groupid; } } } } grouped_features.push_back(onegroup); groupid++; } }