11

Currently I try to join different parts of the Mesh, which are not connected. From the example I found this ( blobby_3cc.off ).

With the keep_large_connected_components and the keep_largest_connected_components I remove all the smaller components. Which keeps these 3 below.

I can't find a way in the documentation to join them together and fill the missing parts. One solution Is to create 1 triangle and the fill the holes (since then it is 1 object, with enormous holes). But I can't find a way to join these together.

Anyone has an solution for this?

I am using CGAL for C++.

enter image description here

Niels
  • 48,601
  • 4
  • 62
  • 81

2 Answers2

3

When I started with CGAL, I almost immediately ran into this problem. I was able to find a solution after carefully reading over the polygon mesh documentation. Essentially, through a modified version of Corefinement, you can smoothly mesh together two separate geometries, no matter their poly count or shape (however, the greater the difference of polygons, the less effective it will become).

What you have to do is first, make sure the geometry does not self-intersect. Secondly, make sure that CGAL::Polygon_mesh_processing::clip() is active on the two geometries (I suggest using close_volumes=false). Next, compute the union of the two new meshes:

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <fstream>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Surface_mesh<K::Point_3>             Mesh;
namespace PMP = CGAL::Polygon_mesh_processing;
int main(int argc, char* argv[])
{
  const char* filename1 = (argc > 1) ? argv[1] : "data/blobby.off";
  const char* filename2 = (argc > 2) ? argv[2] : "data/eight.off";
  std::ifstream input(filename1);
  Mesh mesh1, mesh2;
  if (!input || !(input >> mesh1))
  {
    std::cerr << "First mesh is not a valid off file." << std::endl;
    return 1;
  }
  input.close();
  input.open(filename2);
  if (!input || !(input >> mesh2))
  {
    std::cerr << "Second mesh is not a valid off file." << std::endl;
    return 1;
  }
  Mesh out;
  bool valid_union = PMP::corefine_and_compute_union(mesh1,mesh2, out);
  if (valid_union)
  {
    std::cout << "Union was successfully computed\n";
    std::ofstream output("union.off");
    output << out;
    return 0;
  }
  std::cout << "Union could not be computed\n";
  return 1;
}

Instead of using a mesh with a point from a kernel with exact constructions, the exact points are a property of the mesh vertices that we can reuse in a later operations. With that property, we can manipulate a mesh with points having floating point coordinates but benefit from the robustness provided by the exact constructions.:

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <fstream>
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Exact_predicates_exact_constructions_kernel EK;
typedef CGAL::Surface_mesh<K::Point_3> Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;
typedef Mesh::Property_map<vertex_descriptor,EK::Point_3> Exact_point_map;
typedef Mesh::Property_map<vertex_descriptor,bool> Exact_point_computed;
namespace PMP = CGAL::Polygon_mesh_processing;
namespace params = PMP::parameters;
struct Coref_point_map
{
  // typedef for the property map
  typedef boost::property_traits<Exact_point_map>::value_type value_type;
  typedef boost::property_traits<Exact_point_map>::reference reference;
  typedef boost::property_traits<Exact_point_map>::category category;
  typedef boost::property_traits<Exact_point_map>::key_type key_type;
  // exterior references
  Exact_point_computed* exact_point_computed_ptr;
  Exact_point_map* exact_point_ptr;
  Mesh* mesh_ptr;
  Exact_point_computed& exact_point_computed() const
  {
    CGAL_assertion(exact_point_computed_ptr!=NULL);
    return *exact_point_computed_ptr;
  }
  Exact_point_map& exact_point() const
  {
    CGAL_assertion(exact_point_ptr!=NULL);
    return *exact_point_ptr;
  }
  Mesh& mesh() const
  {
    CGAL_assertion(mesh_ptr!=NULL);
    return *mesh_ptr;
  }
  // Converters
  CGAL::Cartesian_converter<K, EK> to_exact;
  CGAL::Cartesian_converter<EK, K> to_input;
  Coref_point_map()
    : exact_point_computed_ptr(NULL)
    , exact_point_ptr(NULL)
    , mesh_ptr(NULL)
  {}
  Coref_point_map(Exact_point_map& ep,
                  Exact_point_computed& epc,
                  Mesh& m)
    : exact_point_computed_ptr(&epc)
    , exact_point_ptr(&ep)
    , mesh_ptr(&m)
  {}
  friend
  reference get(const Coref_point_map& map, key_type k)
  {
    // create exact point if it does not exist
    if (!map.exact_point_computed()[k]){
      map.exact_point()[k]=map.to_exact(map.mesh().point(k));
      map.exact_point_computed()[k]=true;
    }
    return map.exact_point()[k];
  }
  friend
  void put(const Coref_point_map& map, key_type k, const EK::Point_3& p)
  {
    map.exact_point_computed()[k]=true;
    map.exact_point()[k]=p;
    // create the input point from the exact one
    map.mesh().point(k)=map.to_input(p);
  }
};
int main(int argc, char* argv[])
{
  const char* filename1 = (argc > 1) ? argv[1] : "data/blobby.off";
  const char* filename2 = (argc > 2) ? argv[2] : "data/eight.off";
  std::ifstream input(filename1);
  Mesh mesh1, mesh2;
  if (!input || !(input >> mesh1))
  {
    std::cerr << "First mesh is not a valid off file." << std::endl;
    return 1;
  }
  input.close();
  input.open(filename2);
  if (!input || !(input >> mesh2))
  {
    std::cerr << "Second mesh is not a valid off file." << std::endl;
    return 1;
  }
  Exact_point_map mesh1_exact_points =
    mesh1.add_property_map<vertex_descriptor,EK::Point_3>("e:exact_point").first;
  Exact_point_computed mesh1_exact_points_computed =
    mesh1.add_property_map<vertex_descriptor,bool>("e:exact_points_computed").first;
  Exact_point_map mesh2_exact_points =
    mesh2.add_property_map<vertex_descriptor,EK::Point_3>("e:exact_point").first;
  Exact_point_computed mesh2_exact_points_computed =
    mesh2.add_property_map<vertex_descriptor,bool>("e:exact_points_computed").first;
  Coref_point_map mesh1_pm(mesh1_exact_points, mesh1_exact_points_computed, mesh1);
  Coref_point_map mesh2_pm(mesh2_exact_points, mesh2_exact_points_computed, mesh2);
  if ( PMP::corefine_and_compute_intersection(mesh1,
                                              mesh2,
                                              mesh1,
                                              params::vertex_point_map(mesh1_pm),
                                              params::vertex_point_map(mesh2_pm),
                                              params::vertex_point_map(mesh1_pm) ) )
  {
    if ( PMP::corefine_and_compute_union(mesh1,
                                         mesh2,
                                         mesh2,
                                         params::vertex_point_map(mesh1_pm),
                                         params::vertex_point_map(mesh2_pm),
                                         params::vertex_point_map(mesh2_pm) ) )
    {
      std::cout << "Intersection and union were successfully computed\n";
      std::ofstream output("inter_union.off");
      output << mesh2;
      return 0;
    }
    std::cout << "Union could not be computed\n";
    return 1;
  }
  std::cout << "Intersection could not be computed\n";
  return 1;
}
Vendetta
  • 2,078
  • 3
  • 13
  • 31
  • And to fill any holes, see [Combinatorial Repairing](https://doc.cgal.org/latest/Polygon_mesh_processing/index.html#title41) and [hole filling](https://doc.cgal.org/latest/Polygon_mesh_processing/index.html#PMPHoleFilling) – Vendetta Feb 27 '20 at 15:23
  • Thank you for your reply. I try to understand your code, but some functions I don't seem to understand `corefine_and_compute_union`, `corefine_and_compute_intersection`. I don't get any clear understanding in the docs. Can you explain a bit? – Niels Feb 27 '20 at 19:15
  • Essentially, `corefine_and_compute_union` calculates segments of the mesh that overlap, and need to be removed and replaced with a polygon fill. `corefine_and_compute_intersection` is close to the same thing, but uses existing mesh to fill the cut instead of generating a smooth mesh fill. The first function usually requires an exact input to work, but the second one allows it to pass itself as a parameter. – Vendetta Feb 27 '20 at 21:08
  • I do have to check it out this weekend and see the result so I know how it works. I will accept this answer as the correct answer before the bounty runs out. – Niels Feb 28 '20 at 08:28
0

How does the mesh look originally? would it be feasible to merge the different components rather than remove the smallest parts? See CGAL combinatorical repairing for more info.

Connecting different components is a rather difficult problem. i believe the regular hole filling algorithms only works on holes that are bounded, i.e. there is a open edge that goes around the hole and ends up at the start.

My recommendation would be to analyze the mesh to find the open edges-lists that need to be connected , i.e. the red, green, blue and purple lines. Find a way to pair these with each other, i.e. reg-green and blue-purple. In the example it should be sufficient to just use the average of the edges for the pairing.

Then you would need some method to triangulate the gap between the edges. As you mention, it should be sufficient to create a triangle (or two) to connect the parts, and use something like CGAL::Polygon_mesh_processing::triangulate_refine_and_fair_hole to fill the rest.

To do this you could try to find the two edges of each list that are close to each other. I.e. the sum of the point distances are as small as possible. So pick one edge from one list and find the closest edge in the other. When you have two edges, add a pair of triangles and use CGAL to fill the rest. The different parts should have the same surface orientation for this to work, but that is probably the case.

Another approach would be to just use the vertices to create a mesh from a point cloud, but this is not guaranteed to match your current mesh. The easiest solution is probably to try to avoid the problem altogether, i.e. ensure the source of the meshes produce well defined connected meshes.

example of edges to connect

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thank you for your reply, this is indeed the approach that I have been working on for a while, I almost finished programming, currently having issues with faces in the wrong direction so the fill holes fails. – Niels Feb 27 '20 at 10:42