原文链接:https://doc.cgal.org/latest/Polygon_mesh_processing/index.html#title24

此包提供了一种算法,用于填充一个封闭孔,该封闭孔要么位于三角曲面网格中,要么由一系列描述折线的点定义。

算法的主要步骤在 [8]中描述,总结如下:

  1. 首先,在不引入任何新顶点的情况下,生成三角化孔洞边界的最大patch(补片、面片)
  2. 选择patch是为了使所有可能的三角形patch的质量函数最小化。该质量函数首先最小化patch三角形之间的最坏二面角,然后将Patch的总表面积作为平分点。
  3. 根据[12]的建议,通过将搜索空间中所有可能的补片缩小到孔边界顶点的3D Delaunay三角剖分的面,同时搜索与前述质量标准相关的最佳补丁,该算法的性能得到显著提高。

对于某些复杂的孔边界,生成的patch可能存在自相交

算法主要步骤的结果。从左到右:(a) 孔洞,(b) 三角化后的洞,(c) 三角剖分和细分后, (d) 三角剖分、细分和光顺后。

1. API

This package provides four functions for hole filling:

该程序包提供了四个补洞方法:

2. 示例

2.1 Triangulate a Polyline(对折线进行三角剖分)

以下示例将对输入的折线(孔边界)进行三角剖分。

File Polygon_mesh_processing/triangulate_polyline_example.cpp

  1. #include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
  2. #include <CGAL/Polygon_mesh_processing/triangulate_hole.h>
  3. #include <CGAL/utility.h>
  4. #include <vector>
  5. #include <iterator>
  6. typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
  7. typedef Kernel::Point_3 Point;
  8. int main() {
  9. std::vector<Point> polyline;
  10. polyline.push_back(Point( 1.,0.,0.));
  11. polyline.push_back(Point( 0.,1.,0.));
  12. polyline.push_back(Point(-1.,0.,0.));
  13. polyline.push_back(Point( 1.,1.,0.));
  14. // repeating first point (i.e. polyline.push_back(Point(1.,0.,0.)) ) is optional
  15. // 回头点可选
  16. // any type, having Type(int, int, int) constructor available, can be used to hold output triangles
  17. //任何类型, 有Type(int, int, int)构造函数可用,可以用于保存输出三角形
  18. typedef CGAL::Triple<int, int, int> Triangle_int;
  19. std::vector<Triangle_int> patch;
  20. patch.reserve(polyline.size() -2); // there will be exactly n-2 triangles in the patch
  21. CGAL::Polygon_mesh_processing::triangulate_hole_polyline(
  22. polyline,
  23. std::back_inserter(patch));
  24. for(std::size_t i = 0; i < patch.size(); ++i) {
  25. std::cout << "Triangle " << i << ": "
  26. << patch[i].first << " " << patch[i].second << " " << patch[i].third
  27. << std::endl;
  28. }
  29. // note that no degenerate triangles are generated in the patch
  30. // 另一个空的例子,面片中不会生成退化三角形
  31. std::vector<Point> polyline_collinear;
  32. polyline_collinear.push_back(Point(1.,0.,0.));
  33. polyline_collinear.push_back(Point(2.,0.,0.));
  34. polyline_collinear.push_back(Point(3.,0.,0.));
  35. polyline_collinear.push_back(Point(4.,0.,0.));
  36. std::vector<Triangle_int> patch_will_be_empty;
  37. CGAL::Polygon_mesh_processing::triangulate_hole_polyline(polyline_collinear,
  38. back_inserter(patch_will_be_empty));
  39. CGAL_assertion(patch_will_be_empty.empty());
  40. return 0;
  41. }

2.2 Hole Filling From the Border of the Hole (识别洞,并迭代填充)

If the input polygon mesh has a hole or more than one hole, it is possible to iteratively fill them by detecting border edges (i.e. with only one incident non-null face) after each hole filling step.

如果输入多边形网格有一个洞或多个洞,则可以在每个洞填充步骤之后,通过检测边界边来实现迭代填充

  • 孔洞被一个接一个地填充,当没有边界边缘留下时,该过程停止。

下面这个例子说明了这个过程,其中孔洞被反复填充、细化和光顺。

  • 可选地,只有不超过一定直径或边数的孔可以被填充

此示例假设网格存储在Surface_mesh数据结构中。使用Polyhedron_3 类和其他几个类时的类似示例是代码库的一部分。

File Polygon_mesh_processing/hole_filling_example_SM.cpp

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/triangulate_hole.h>
#include <CGAL/Polygon_mesh_processing/border.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <set>

typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef Kernel::Point_3                                     Point;
typedef CGAL::Surface_mesh<Point>                           Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor        vertex_descriptor;
typedef boost::graph_traits<Mesh>::halfedge_descriptor      halfedge_descriptor;
typedef boost::graph_traits<Mesh>::face_descriptor          face_descriptor;
namespace PMP = CGAL::Polygon_mesh_processing;

bool is_small_hole(halfedge_descriptor h, Mesh & mesh,
                   double max_hole_diam, int max_num_hole_edges) {
  int num_hole_edges = 0;
  CGAL::Bbox_3 hole_bbox;
  for (halfedge_descriptor hc : CGAL::halfedges_around_face(h, mesh)) {
    const Point& p = mesh.point(target(hc, mesh));
    hole_bbox += p.bbox();
    ++num_hole_edges;
    // Exit early, to avoid unnecessary traversal of large holes
    if (num_hole_edges > max_num_hole_edges) return false;
    if (hole_bbox.xmax() - hole_bbox.xmin() > max_hole_diam) return false;
    if (hole_bbox.ymax() - hole_bbox.ymin() > max_hole_diam) return false;
    if (hole_bbox.zmax() - hole_bbox.zmin() > max_hole_diam) return false;
  }
  return true;
}

// Incrementally fill the holes that are no larger than given diameter
// 逐渐填充不大于给定直径的孔
// and with no more than a given number of edges (if specified).
// 并且不超过给定数量的边(如果指定的话)。
int main(int argc, char* argv[]) {
  //从文件中读取一个PolygonMesh
  const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/mech-holes-shark.off");
  Mesh mesh;
  if(!PMP::IO::read_polygon_mesh(filename, mesh)) {
    std::cerr << "Invalid input." << std::endl;
    return 1;
  }

  // Both of these must be positive in order to be considered
  // 这两个参数必须都是正数才会生效
  double max_hole_diam   = (argc > 2) ? boost::lexical_cast<double>(argv[2]): -1.0;
  int max_num_hole_edges = (argc > 3) ? boost::lexical_cast<int>(argv[3]) : -1;
  unsigned int nb_holes = 0;
  std::vector<halfedge_descriptor> border_cycles;

  // collect one halfedge per boundary cycle
  //每一个边界循环收集一个半边 ?
  PMP::extract_boundary_cycles(mesh, std::back_inserter(border_cycles));
  for(halfedge_descriptor h : border_cycles) {
    if(max_hole_diam > 0 && max_num_hole_edges > 0 &&
       !is_small_hole(h, mesh, max_hole_diam, max_num_hole_edges))
      continue;

    std::vector<face_descriptor>  patch_facets;
    std::vector<vertex_descriptor> patch_vertices;
    bool success = std::get<0>(PMP::triangulate_refine_and_fair_hole(mesh,
                                                                     h,
                                                                     std::back_inserter(patch_facets),
                                                                     std::back_inserter(patch_vertices)));
    std::cout << "* Number of facets in constructed patch: " << patch_facets.size() << std::endl;
    std::cout << "  Number of vertices in constructed patch: " << patch_vertices.size() << std::endl;
    std::cout << "  Is fairing successful: " << success << std::endl;
    ++nb_holes;
  }

  std::cout << std::endl;
  std::cout << nb_holes << " holes have been filled" << std::endl;
  CGAL::IO::write_polygon_mesh("filled_SM.off", mesh, CGAL::parameters::stream_precision(17));
  std::cout << "Mesh written to: filled_SM.off" << std::endl;
  return 0;
}

image.png

3. Performance(性能)

孔洞填充算法的复杂度与顶点个数有关

  • [8] 的时间复杂度 O(n3) ,而 [12] 大多数情况下是O(nlogn)

我们测试了函数triangulate_refine_and_fair_hole()对于下面的两个网格(以及另外两个小孔更小的网格)

  • 这台电脑运行的是Windows 10操作系统,处理器是英特尔酷睿i7,频率为2.70 GHz。该程序是用带有O2选项的Visual c++ 2013编译器编译的,这样可以最大限度地提高速度。

elephants-with-holes.png
Figure 66.12 左边/右边的大象有一个有963/7657个顶点的洞。

观察到的运行时间如下:

# vertices(点) without Delaunay (sec.) with Delaunay (sec.)
565 8.5 0.03
774 21 0.035
967 43 0.06
7657 na 0.4

参考

[8] P. Liepa. Filling holes in meshes. In Proceedings of the 2003 Eurographics/ACM SIGGRAPH Symposium on Geometry Processing, pages 200–205. Eurographics Association, 2003.

[12] M. Zou, T. Ju, and N. Carr. An algorithm for triangulating multiple 3d polygons. In Computer Graphics Forum, volume 32, pages 157–166. Wiley Online Library, 2013.