TEX_NAMESPACE_BEGIN void find_seam_edges(UniGraph const & graph, mve::TriangleMesh::ConstPtr mesh, std::vector<MeshEdge> * seam_edges) { mve::TriangleMesh::FaceList const & faces = mesh->get_faces(); seam_edges->clear(); // Is it possible that a single edge is part of more than three faces whichs' label is non zero??? for (std::size_t node = 0; node < graph.num_nodes(); ++node) { std::vector<std::size_t> const & adj_nodes = graph.get_adj_nodes(node); for (std::size_t adj_node : adj_nodes) { /* Add each edge only once. */ if (node > adj_node) continue; int label1 = graph.get_label(node); int label2 = graph.get_label(adj_node); /* Add only seam edges. */ //if (label1 == 0 || label2 == 0 || label1 == label2) continue; if (label1 == label2) continue; /* Find shared edge of the faces. */ std::vector<std::size_t> shared_edge; for (int i = 0; i < 3; ++i){ std::size_t v1 = faces[3 * node + i]; for (int j = 0; j < 3; j++){ std::size_t v2 = faces[3 * adj_node + j]; if (v1 == v2) shared_edge.push_back(v1); } } assert(shared_edge.size() == 2); std::size_t v1 = shared_edge[0]; std::size_t v2 = shared_edge[1]; assert(v1 != v2); if (v1 > v2) std::swap(v1, v2); MeshEdge seam_edge = {v1, v2}; seam_edges->push_back(seam_edge); } } }
/** Setup the neighborhood of the MRF. */ void set_neighbors(UniGraph const & graph, std::vector<FaceInfo> const & face_infos, std::vector<mrf::Graph::Ptr> const & mrfs) { for (std::size_t i = 0; i < graph.num_nodes(); ++i) { std::vector<std::size_t> adj_faces = graph.get_adj_nodes(i); for (std::size_t j = 0; j < adj_faces.size(); ++j) { std::size_t adj_face = adj_faces[j]; /* The solver expects only one call of setNeighbours for two neighbours a and b. */ if (i < adj_face) { assert(face_infos[i].component == face_infos[adj_face].component); const std::size_t component = face_infos[i].component; const std::size_t cid1 = face_infos[i].id; const std::size_t cid2 = face_infos[adj_face].id; mrfs[component]->set_neighbors(cid1, cid2); } } } }
void generate_texture_patches(UniGraph const & graph, mve::TriangleMesh::ConstPtr mesh, mve::MeshInfo const & mesh_info, std::vector<TextureView> * texture_views, Settings const & settings, std::vector<std::vector<VertexProjectionInfo> > * vertex_projection_infos, std::vector<TexturePatch::Ptr> * texture_patches) { util::WallTimer timer; mve::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); mve::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); vertex_projection_infos->resize(vertices.size()); std::size_t num_patches = 0; std::cout << "\tRunning... " << std::flush; #pragma omp parallel for schedule(dynamic) for (std::size_t i = 0; i < texture_views->size(); ++i) { std::vector<std::vector<std::size_t> > subgraphs; int const label = i + 1; graph.get_subgraphs(label, &subgraphs); TextureView * texture_view = &texture_views->at(i); texture_view->load_image(); std::list<TexturePatchCandidate> candidates; for (std::size_t j = 0; j < subgraphs.size(); ++j) { candidates.push_back(generate_candidate(label, *texture_view, subgraphs[j], mesh)); } texture_view->release_image(); /* Merge candidates which contain the same image content. */ std::list<TexturePatchCandidate>::iterator it, sit; for (it = candidates.begin(); it != candidates.end(); ++it) { for (sit = candidates.begin(); sit != candidates.end();) { Rect<int> bounding_box = sit->bounding_box; if (it != sit && bounding_box.is_inside(&it->bounding_box)) { TexturePatch::Faces & faces = it->texture_patch->get_faces(); TexturePatch::Faces & ofaces = sit->texture_patch->get_faces(); faces.insert(faces.end(), ofaces.begin(), ofaces.end()); TexturePatch::Texcoords & texcoords = it->texture_patch->get_texcoords(); TexturePatch::Texcoords & otexcoords = sit->texture_patch->get_texcoords(); math::Vec2f offset; offset[0] = sit->bounding_box.min_x - it->bounding_box.min_x; offset[1] = sit->bounding_box.min_y - it->bounding_box.min_y; for (std::size_t i = 0; i < otexcoords.size(); ++i) { texcoords.push_back(otexcoords[i] + offset); } sit = candidates.erase(sit); } else { ++sit; } } } it = candidates.begin(); for (; it != candidates.end(); ++it) { std::size_t texture_patch_id; #pragma omp critical { texture_patches->push_back(it->texture_patch); texture_patch_id = num_patches++; } std::vector<std::size_t> const & faces = it->texture_patch->get_faces(); std::vector<math::Vec2f> const & texcoords = it->texture_patch->get_texcoords(); for (std::size_t i = 0; i < faces.size(); ++i) { std::size_t const face_id = faces[i]; std::size_t const face_pos = face_id * 3; for (std::size_t j = 0; j < 3; ++j) { std::size_t const vertex_id = mesh_faces[face_pos + j]; math::Vec2f const projection = texcoords[i * 3 + j]; VertexProjectionInfo info = {texture_patch_id, projection, {face_id}}; #pragma omp critical vertex_projection_infos->at(vertex_id).push_back(info); } } } } merge_vertex_projection_infos(vertex_projection_infos); { std::vector<std::size_t> unseen_faces; std::vector<std::vector<std::size_t> > subgraphs; graph.get_subgraphs(0, &subgraphs); #pragma omp parallel for schedule(dynamic) for (std::size_t i = 0; i < subgraphs.size(); ++i) { std::vector<std::size_t> const & subgraph = subgraphs[i]; bool success = false; if (settings.hole_filling) { success = fill_hole(subgraph, graph, mesh, mesh_info, vertex_projection_infos, texture_patches); } if (success) { num_patches += 1; } else { if (settings.keep_unseen_faces) { #pragma omp critical unseen_faces.insert(unseen_faces.end(), subgraph.begin(), subgraph.end()); } } } if (!unseen_faces.empty()) { mve::ByteImage::Ptr image = mve::ByteImage::create(3, 3, 3); std::vector<math::Vec2f> texcoords; for (std::size_t i = 0; i < unseen_faces.size(); ++i) { math::Vec2f projections[] = {{2.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 2.0f}}; texcoords.insert(texcoords.end(), &projections[0], &projections[3]); } TexturePatch::Ptr texture_patch = TexturePatch::create(0, unseen_faces, texcoords, image); texture_patches->push_back(texture_patch); std::size_t texture_patch_id = texture_patches->size() - 1; for (std::size_t i = 0; i < unseen_faces.size(); ++i) { std::size_t const face_id = unseen_faces[i]; std::size_t const face_pos = face_id * 3; for (std::size_t j = 0; j < 3; ++j) { std::size_t const vertex_id = mesh_faces[face_pos + j]; math::Vec2f const projection = texcoords[i * 3 + j]; VertexProjectionInfo info = {texture_patch_id, projection, {face_id}}; #pragma omp critical vertex_projection_infos->at(vertex_id).push_back(info); } } } } merge_vertex_projection_infos(vertex_projection_infos); std::cout << "done. (Took " << timer.get_elapsed_sec() << "s)" << std::endl; std::cout << "\t" << num_patches << " texture patches." << std::endl; }
bool fill_hole(std::vector<std::size_t> const & hole, UniGraph const & graph, mve::TriangleMesh::ConstPtr mesh, mve::MeshInfo const & mesh_info, std::vector<std::vector<VertexProjectionInfo> > * vertex_projection_infos, std::vector<TexturePatch::Ptr> * texture_patches) { mve::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); mve::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); std::map<std::size_t, std::set<std::size_t> > tmp; for (std::size_t const face_id : hole) { std::size_t const v0 = mesh_faces[face_id * 3]; std::size_t const v1 = mesh_faces[face_id * 3 + 1]; std::size_t const v2 = mesh_faces[face_id * 3 + 2]; tmp[v0].insert(face_id); tmp[v1].insert(face_id); tmp[v2].insert(face_id); } std::size_t const num_vertices = tmp.size(); /* Only fill small holes. */ if (num_vertices > MAX_HOLE_NUM_FACES) return false; /* Calculate 2D parameterization using the technique from libremesh/patch2d, * which was published as sourcecode accompanying the following paper: * * Isotropic Surface Remeshing * Simon Fuhrmann, Jens Ackermann, Thomas Kalbe, Michael Goesele */ std::size_t seed = -1; std::vector<bool> is_border(num_vertices, false); std::vector<std::vector<std::size_t> > adj_verts_via_border(num_vertices); /* Index structures to map from local <-> global vertex id. */ std::map<std::size_t, std::size_t> g2l; std::vector<std::size_t> l2g(num_vertices); /* Index structure to determine column in matrix/vector. */ std::vector<std::size_t> idx(num_vertices); std::size_t num_border_vertices = 0; bool disk_topology = true; std::map<std::size_t, std::set<std::size_t> >::iterator it = tmp.begin(); for (std::size_t j = 0; j < num_vertices; ++j, ++it) { std::size_t vertex_id = it->first; g2l[vertex_id] = j; l2g[j] = vertex_id; /* Check topology in original mesh. */ if (mesh_info[vertex_id].vclass != mve::MeshInfo::VERTEX_CLASS_SIMPLE) { /* Complex/Border vertex in original mesh */ disk_topology = false; break; } /* Check new topology and determine if vertex is now at the border. */ std::vector<std::size_t> const & adj_faces = mesh_info[vertex_id].faces; std::set<std::size_t> const & adj_hole_faces = it->second; std::vector<std::pair<std::size_t, std::size_t> > fan; for (std::size_t k = 0; k < adj_faces.size(); ++k) { std::size_t adj_face = adj_faces[k]; if (graph.get_label(adj_faces[k]) == 0 && adj_hole_faces.find(adj_face) != adj_hole_faces.end()) { std::size_t curr = adj_faces[k]; std::size_t next = adj_faces[(k + 1) % adj_faces.size()]; std::pair<std::size_t, std::size_t> pair(curr, next); fan.push_back(pair); } } std::size_t gaps = 0; for (std::size_t k = 0; k < fan.size(); k++) { std::size_t curr = fan[k].first; std::size_t next = fan[(k + 1) % fan.size()].first; if (fan[k].second != next) { ++gaps; for (std::size_t l = 0; l < 3; ++l) { if(mesh_faces[curr * 3 + l] == vertex_id) { std::size_t second = mesh_faces[curr * 3 + (l + 2) % 3]; adj_verts_via_border[j].push_back(second); } if(mesh_faces[next * 3 + l] == vertex_id) { std::size_t first = mesh_faces[next * 3 + (l + 1) % 3]; adj_verts_via_border[j].push_back(first); } } } } is_border[j] = gaps == 1; /* Check if vertex is now complex. */ if (gaps > 1) { /* Complex vertex in hole */ disk_topology = false; break; } if (is_border[j]) { idx[j] = num_border_vertices++; seed = vertex_id; } else { idx[j] = j - num_border_vertices; } } tmp.clear(); /* No disk or genus zero topology */ if (!disk_topology || num_border_vertices == 0) return false; std::vector<std::size_t> border; border.reserve(num_border_vertices); std::size_t prev = seed; std::size_t curr = seed; while (prev == seed || curr != seed) { std::size_t next = std::numeric_limits<std::size_t>::max(); std::vector<std::size_t> const & adj_verts = adj_verts_via_border[g2l[curr]]; for (std::size_t adj_vert : adj_verts) { assert(is_border[g2l[adj_vert]]); if (adj_vert != prev && adj_vert != curr) { next = adj_vert; break; } } if (next != std::numeric_limits<std::size_t>::max()) { prev = curr; curr = next; border.push_back(next); } else { /* No new border vertex */ border.clear(); break; } /* Loop within border */ if (border.size() > num_border_vertices) break; } if (border.size() != num_border_vertices) return false; float total_length = 0.0f; float total_projection_length = 0.0f; for (std::size_t j = 0; j < border.size(); ++j) { std::size_t vi0 = border[j]; std::size_t vi1 = border[(j + 1) % border.size()]; std::vector<VertexProjectionInfo> const & vpi0 = vertex_projection_infos->at(vi0); std::vector<VertexProjectionInfo> const & vpi1 = vertex_projection_infos->at(vi0); /* According to the previous checks (vertex class within the origial * mesh and boundary) there already has to be at least one projection * of each border vertex. */ assert(!vpi0.empty() && !vpi1.empty()); math::Vec2f vp0(0.0f), vp1(0.0f); for (VertexProjectionInfo const & info0 : vpi0) { for (VertexProjectionInfo const & info1 : vpi1) { if (info0.texture_patch_id == info1.texture_patch_id) { vp0 = info0.projection; vp1 = info1.projection; break; } } } total_projection_length += (vp0 - vp1).norm(); math::Vec3f const & v0 = vertices[vi0]; math::Vec3f const & v1 = vertices[vi1]; total_length += (v0 - v1).norm(); } float radius = total_projection_length / (2.0f * MATH_PI); if (total_length < std::numeric_limits<float>::epsilon()) return false; float length = 0.0f; std::vector<math::Vec2f> projections(num_vertices); for (std::size_t j = 0; j < border.size(); ++j) { float angle = 2.0f * MATH_PI * (length / total_length); projections[g2l[border[j]]] = math::Vec2f(std::cos(angle), std::sin(angle)); math::Vec3f const & v0 = vertices[border[j]]; math::Vec3f const & v1 = vertices[border[(j + 1) % border.size()]]; length += (v0 - v1).norm(); } typedef Eigen::Triplet<float, int> Triplet; std::vector<Triplet> coeff; std::size_t matrix_size = num_vertices - border.size(); Eigen::VectorXf xx(matrix_size), xy(matrix_size); if (matrix_size != 0) { Eigen::VectorXf bx(matrix_size); Eigen::VectorXf by(matrix_size); for (std::size_t j = 0; j < num_vertices; ++j) { if (is_border[j]) continue; std::size_t const vertex_id = l2g[j]; /* Calculate "Mean Value Coordinates" as proposed by Michael S. Floater */ std::map<std::size_t, float> weights; std::vector<std::size_t> const & adj_faces = mesh_info[vertex_id].faces; for (std::size_t adj_face : adj_faces) { std::size_t v0 = mesh_faces[adj_face * 3]; std::size_t v1 = mesh_faces[adj_face * 3 + 1]; std::size_t v2 = mesh_faces[adj_face * 3 + 2]; if (v1 == vertex_id) std::swap(v1, v0); if (v2 == vertex_id) std::swap(v2, v0); math::Vec3f v01 = vertices[v1] - vertices[v0]; float v01n = v01.norm(); math::Vec3f v02 = vertices[v2] - vertices[v0]; float v02n = v02.norm(); /* Ensure numerical stability */ if (v01n * v02n < std::numeric_limits<float>::epsilon()) return false; float alpha = std::acos(v01.dot(v02) / (v01n * v02n)); weights[g2l[v1]] += std::tan(alpha / 2.0f) / v01n; weights[g2l[v2]] += std::tan(alpha / 2.0f) / v02n; } std::map<std::size_t, float>::iterator it; float sum = 0.0f; for (it = weights.begin(); it != weights.end(); ++it) sum += it->second; assert(sum > 0.0f); for (it = weights.begin(); it != weights.end(); ++it) it->second /= sum; bx[idx[j]] = 0.0f; by[idx[j]] = 0.0f; for (it = weights.begin(); it != weights.end(); ++it) { if (is_border[it->first]) { std::size_t border_vertex_id = border[idx[it->first]]; bx[idx[j]] += projections[g2l[border_vertex_id]][0] * it->second; by[idx[j]] += projections[g2l[border_vertex_id]][1] * it->second; } else { coeff.push_back(Triplet(idx[j], idx[it->first], -it->second)); } } } for (std::size_t j = 0; j < matrix_size; ++j) { coeff.push_back(Triplet(j, j, 1.0f)); } typedef Eigen::SparseMatrix<float> SpMat; SpMat A(matrix_size, matrix_size); A.setFromTriplets(coeff.begin(), coeff.end()); Eigen::SparseLU<SpMat> solver; solver.analyzePattern(A); solver.factorize(A); xx = solver.solve(bx); xy = solver.solve(by); } float const max_hole_patch_size = MAX_HOLE_PATCH_SIZE; int image_size = std::min(std::floor(radius * 1.1f) * 2.0f, max_hole_patch_size); /* Ensure a minimum scale of one */ image_size += 2 * (1 + texture_patch_border); int scale = image_size / 2 - texture_patch_border; for (std::size_t j = 0, k = 0; j < num_vertices; ++j) { if (is_border[j]) { projections[j] = projections[j] * scale + image_size / 2; } else { projections[j] = math::Vec2f(xx[k], xy[k]) * scale + image_size / 2; ++k; } } mve::ByteImage::Ptr image = mve::ByteImage::create(image_size, image_size, 3); //DEBUG image->fill_color(*math::Vec4uc(0, 255, 0, 255)); std::vector<math::Vec2f> texcoords; texcoords.reserve(hole.size()); for (std::size_t const face_id : hole) { for (std::size_t j = 0; j < 3; ++j) { std::size_t const vertex_id = mesh_faces[face_id * 3 + j]; math::Vec2f const & projection = projections[g2l[vertex_id]]; texcoords.push_back(projection); } } TexturePatch::Ptr texture_patch = TexturePatch::create(0, hole, texcoords, image); std::size_t texture_patch_id; #pragma omp critical { texture_patches->push_back(texture_patch); texture_patch_id = texture_patches->size() - 1; } for (std::size_t j = 0; j < num_vertices; ++j) { std::size_t const vertex_id = l2g[j]; std::vector<std::size_t> const & adj_faces = mesh_info[vertex_id].faces; std::vector<std::size_t> faces; faces.reserve(adj_faces.size()); for (std::size_t adj_face : adj_faces) { if (graph.get_label(adj_face) == 0) { faces.push_back(adj_face); } } VertexProjectionInfo info = {texture_patch_id, projections[j], faces}; #pragma omp critical vertex_projection_infos->at(vertex_id).push_back(info); } return true; }
void generate_texture_patches(UniGraph const & graph, std::vector<TextureView> const & texture_views, mve::TriangleMesh::ConstPtr mesh, mve::VertexInfoList::ConstPtr vertex_infos, std::vector<std::vector<VertexProjectionInfo> > * vertex_projection_infos, std::vector<TexturePatch> * texture_patches) { util::WallTimer timer; mve::TriangleMesh::FaceList const & mesh_faces = mesh->get_faces(); mve::TriangleMesh::VertexList const & vertices = mesh->get_vertices(); vertex_projection_infos->resize(vertices.size()); std::size_t num_patches = 0; std::cout << "\tRunning... " << std::flush; #pragma omp parallel for schedule(dynamic) for (std::size_t i = 0; i < texture_views.size(); ++i) { std::vector<std::vector<std::size_t> > subgraphs; int const label = i + 1; graph.get_subgraphs(label, &subgraphs); std::list<TexturePatchCandidate> candidates; for (std::size_t j = 0; j < subgraphs.size(); ++j) { candidates.push_back(generate_candidate(label, texture_views[i], subgraphs[j], mesh)); } /* Merge candidates which contain the same image content. */ std::list<TexturePatchCandidate>::iterator it, sit; for (it = candidates.begin(); it != candidates.end(); ++it) { for (sit = candidates.begin(); sit != candidates.end();) { Rect<int> bounding_box = sit->bounding_box; if (it != sit && bounding_box.is_inside(&it->bounding_box)) { TexturePatch::Faces & faces = it->texture_patch.get_faces(); TexturePatch::Faces & ofaces = sit->texture_patch.get_faces(); faces.insert(faces.end(), ofaces.begin(), ofaces.end()); TexturePatch::Texcoords & texcoords = it->texture_patch.get_texcoords(); TexturePatch::Texcoords & otexcoords = sit->texture_patch.get_texcoords(); math::Vec2f offset; offset[0] = sit->bounding_box.min_x - it->bounding_box.min_x; offset[1] = sit->bounding_box.min_y - it->bounding_box.min_y; for (std::size_t i = 0; i < otexcoords.size(); ++i) { texcoords.push_back(otexcoords[i] + offset); } sit = candidates.erase(sit); } else { ++sit; } } } it = candidates.begin(); for (; it != candidates.end(); ++it) { std::size_t texture_patch_id; #pragma omp critical { texture_patches->push_back(it->texture_patch); texture_patch_id = num_patches++; } std::vector<std::size_t> const & faces = it->texture_patch.get_faces(); std::vector<math::Vec2f> const & texcoords = it->texture_patch.get_texcoords(); for (std::size_t i = 0; i < faces.size(); ++i) { std::size_t const face_id = faces[i]; std::size_t const face_pos = face_id * 3; for (std::size_t j = 0; j < 3; ++j) { std::size_t const vertex_id = mesh_faces[face_pos + j]; math::Vec2f const projection = texcoords[i * 3 + j]; VertexProjectionInfo info = {texture_patch_id, projection, {face_id}}; #pragma omp critical vertex_projection_infos->at(vertex_id).push_back(info); } } } } merge_vertex_projection_infos(vertex_projection_infos); std::size_t num_holes = 0; std::size_t num_hole_faces = 0; //if (!settings.skip_hole_filling) { { std::vector<std::vector<std::size_t> > subgraphs; graph.get_subgraphs(0, &subgraphs); #pragma omp parallel for schedule(dynamic) for (std::size_t i = 0; i < subgraphs.size(); ++i) { std::vector<std::size_t> const & subgraph = subgraphs[i]; std::map<std::size_t, std::set<std::size_t> > tmp; for (std::size_t const face_id : subgraph) { std::size_t const v0 = mesh_faces[face_id * 3]; std::size_t const v1 = mesh_faces[face_id * 3 + 1]; std::size_t const v2 = mesh_faces[face_id * 3 + 2]; tmp[v0].insert(face_id); tmp[v1].insert(face_id); tmp[v2].insert(face_id); } std::size_t const num_vertices = tmp.size(); /* Only fill small holes. */ if (num_vertices > 100) { //std::cerr << "Hole to large" << std::endl; continue; } /* Calculate 2D parameterization using the technique from libremesh/patch2d, * which was published as sourcecode accompanying the following paper: * * Isotropic Surface Remeshing * Simon Fuhrmann, Jens Ackermann, Thomas Kalbe, Michael Goesele */ std::size_t seed = -1; std::vector<bool> is_border(num_vertices, false); std::vector<std::vector<std::size_t> > adj_verts_via_border(num_vertices); /* Index structures to map from local <-> global vertex id. */ std::map<std::size_t, std::size_t> g2l; std::vector<std::size_t> l2g(num_vertices); /* Index structure to determine column in matrix/vector. */ std::vector<std::size_t> idx(num_vertices); std::size_t num_border_vertices = 0; bool disk_topology = true; std::map<std::size_t, std::set<std::size_t> >::iterator it = tmp.begin(); for (std::size_t j = 0; j < num_vertices; ++j, ++it) { std::size_t vertex_id = it->first; g2l[vertex_id] = j; l2g[j] = vertex_id; /* Check topology in original mesh. */ if (vertex_infos->at(vertex_id).vclass != mve::VERTEX_CLASS_SIMPLE) { //std::cerr << "Complex/Border vertex in original mesh" << std::endl; disk_topology = false; break; } /* Check new topology and determine if vertex is now at the border. */ std::vector<std::size_t> const & adj_faces = vertex_infos->at(vertex_id).faces; std::set<std::size_t> const & adj_hole_faces = it->second; std::vector<std::pair<std::size_t, std::size_t> > fan; for (std::size_t k = 0; k < adj_faces.size(); ++k) { std::size_t adj_face = adj_faces[k]; if (graph.get_label(adj_faces[k]) == 0 && adj_hole_faces.find(adj_face) != adj_hole_faces.end()) { std::size_t curr = adj_faces[k]; std::size_t next = adj_faces[(k + 1) % adj_faces.size()]; std::pair<std::size_t, std::size_t> pair(curr, next); fan.push_back(pair); } } std::size_t gaps = 0; for (std::size_t k = 0; k < fan.size(); k++) { std::size_t curr = fan[k].first; std::size_t next = fan[(k + 1) % fan.size()].first; if (fan[k].second != next) { ++gaps; for (std::size_t l = 0; l < 3; ++l) { if(mesh_faces[curr * 3 + l] == vertex_id) { std::size_t second = mesh_faces[curr * 3 + (l + 2) % 3]; adj_verts_via_border[j].push_back(second); } if(mesh_faces[next * 3 + l] == vertex_id) { std::size_t first = mesh_faces[next * 3 + (l + 1) % 3]; adj_verts_via_border[j].push_back(first); } } } } is_border[j] = gaps == 1; /* Check if vertex is now complex. */ if (gaps > 1) { //std::cerr << "Complex vertex in hole" << std::endl; disk_topology = false; break; } if (is_border[j]) { idx[j] = num_border_vertices++; seed = vertex_id; } else { idx[j] = j - num_border_vertices; } } tmp.clear(); if (!disk_topology) continue; if (num_border_vertices == 0) { //std::cerr << "Genus zero topology" << std::endl; continue; } std::vector<std::size_t> border; border.reserve(num_border_vertices); std::size_t prev = seed; std::size_t curr = seed; while (prev == seed || curr != seed) { std::size_t next = std::numeric_limits<std::size_t>::max(); std::vector<std::size_t> const & adj_verts = adj_verts_via_border[g2l[curr]]; for (std::size_t adj_vert : adj_verts) { assert(is_border[g2l[adj_vert]]); if (adj_vert != prev && adj_vert != curr) { next = adj_vert; break; } } if (next != std::numeric_limits<std::size_t>::max()) { prev = curr; curr = next; border.push_back(next); } else { //std::cerr << "No new border vertex" << std::endl; border.clear(); break; } if (border.size() > num_border_vertices) { //std::cerr << "Loop within border" << std::endl; break; } } if (border.size() != num_border_vertices) { continue; } float total_length = 0.0f; float total_projection_length = 0.0f; for (std::size_t j = 0; j < border.size(); ++j) { std::size_t vi0 = border[j]; std::size_t vi1 = border[(j + 1) % border.size()]; std::vector<VertexProjectionInfo> const & vpi0 = vertex_projection_infos->at(vi0); std::vector<VertexProjectionInfo> const & vpi1 = vertex_projection_infos->at(vi0); /* According to the previous checks (vertex class within the origial * mesh and boundary) there already has to be at least one projection * of each border vertex. */ assert(!vpi0.empty() && !vpi1.empty()); math::Vec2f vp0(0.0f), vp1(0.0f); for (VertexProjectionInfo const & info0 : vpi0) { for (VertexProjectionInfo const & info1 : vpi1) { if (info0.texture_patch_id == info1.texture_patch_id) { vp0 = info0.projection; vp1 = info1.projection; break; } } } total_projection_length += (vp0 - vp1).norm(); math::Vec3f const & v0 = vertices[vi0]; math::Vec3f const & v1 = vertices[vi1]; total_length += (v0 - v1).norm(); } float radius = total_projection_length / (2.0f * MATH_PI); float length = 0.0f; std::vector<math::Vec2f> projections(num_vertices); for (std::size_t j = 0; j < border.size(); ++j) { float angle = 2.0f * MATH_PI * (length / total_length); projections[g2l[border[j]]] = math::Vec2f(std::cos(angle), std::sin(angle)); math::Vec3f const & v0 = vertices[border[j]]; math::Vec3f const & v1 = vertices[border[(j + 1) % border.size()]]; length += (v0 - v1).norm(); } typedef Eigen::Triplet<float, int> Triplet; std::vector<Triplet> coeff; std::size_t matrix_size = num_vertices - border.size(); Eigen::VectorXf xx(matrix_size), xy(matrix_size); if (matrix_size != 0) { Eigen::VectorXf bx(matrix_size); Eigen::VectorXf by(matrix_size); for (std::size_t j = 0; j < num_vertices; ++j) { if (is_border[j]) continue; std::size_t const vertex_id = l2g[j]; /* Calculate "Mean Value Coordinates" as proposed by Michael S. Floater */ std::map<std::size_t, float> weights; std::vector<std::size_t> const & adj_faces = vertex_infos->at(vertex_id).faces; for (std::size_t adj_face : adj_faces) { std::size_t v0 = mesh_faces[adj_face * 3]; std::size_t v1 = mesh_faces[adj_face * 3 + 1]; std::size_t v2 = mesh_faces[adj_face * 3 + 2]; if (v1 == vertex_id) std::swap(v1, v0); if (v2 == vertex_id) std::swap(v2, v0); math::Vec3f v01 = vertices[v1] - vertices[v0]; float v01n = v01.norm(); math::Vec3f v02 = vertices[v2] - vertices[v0]; float v02n = v02.norm(); float alpha = std::acos(v01.dot(v02) / (v01n * v02n)); weights[g2l[v1]] += std::tan(alpha / 2.0f) / v01n; weights[g2l[v2]] += std::tan(alpha / 2.0f) / v02n; } std::map<std::size_t, float>::iterator it; float sum = 0.0f; for (it = weights.begin(); it != weights.end(); ++it) sum += it->second; for (it = weights.begin(); it != weights.end(); ++it) it->second /= sum; bx[idx[j]] = 0.0f; by[idx[j]] = 0.0f; for (it = weights.begin(); it != weights.end(); ++it) { if (is_border[it->first]) { std::size_t border_vertex_id = border[idx[it->first]]; bx[idx[j]] += projections[g2l[border_vertex_id]][0] * it->second; by[idx[j]] += projections[g2l[border_vertex_id]][1] * it->second; } else { coeff.push_back(Triplet(idx[j], idx[it->first], -it->second)); } } } for (std::size_t j = 0; j < matrix_size; ++j) { coeff.push_back(Triplet(j, j, 1.0f)); } typedef Eigen::SparseMatrix<float> SpMat; SpMat A(matrix_size, matrix_size); A.setFromTriplets(coeff.begin(), coeff.end()); Eigen::SparseLU<SpMat> solver; solver.analyzePattern(A); solver.factorize(A); xx = solver.solve(bx); xy = solver.solve(by); } int image_size = std::floor(radius * 1.1f) * 2 + 4; int scale = image_size / 2 - texture_patch_border; for (std::size_t j = 0, k = 0; j < num_vertices; ++j) { if (!is_border[j]) { projections[j] = math::Vec2f(xx[k], xy[k]) * scale + image_size / 2; ++k; } else { projections[j] = projections[j] * scale + image_size / 2; } } mve::ByteImage::Ptr image = mve::ByteImage::create(image_size, image_size, 3); //DEBUG image->fill_color(*math::Vec4uc(0, 255, 0, 255)); std::vector<math::Vec2f> texcoords; texcoords.reserve(subgraph.size()); for (std::size_t const face_id : subgraph) { for (std::size_t j = 0; j < 3; ++j) { std::size_t const vertex_id = mesh_faces[face_id * 3 + j]; math::Vec2f const & projection = projections[g2l[vertex_id]]; texcoords.push_back(projection); } } TexturePatch texture_patch(0, subgraph, texcoords, image); std::size_t texture_patch_id; #pragma omp critical { texture_patches->push_back(texture_patch); texture_patch_id = num_patches++; num_hole_faces += subgraph.size(); ++num_holes; } for (std::size_t j = 0; j < num_vertices; ++j) { std::size_t const vertex_id = l2g[j]; std::vector<std::size_t> const & adj_faces = vertex_infos->at(vertex_id).faces; std::vector<std::size_t> faces; faces.reserve(adj_faces.size()); for (std::size_t adj_face : adj_faces) { if (graph.get_label(adj_face) == 0) { faces.push_back(adj_face); } } VertexProjectionInfo info = {texture_patch_id, projections[j], faces}; #pragma omp critical vertex_projection_infos->at(vertex_id).push_back(info); } } } merge_vertex_projection_infos(vertex_projection_infos); std::cout << "done. (Took " << timer.get_elapsed_sec() << "s)" << std::endl; std::cout << "\t" << num_patches << " texture patches." << std::endl; std::cout << "\t" << num_holes << " holes (" << num_hole_faces << " faces)." << std::endl; }